tcl7.6 User Commands - namespace
NAME
namespace - evaluate scripts in a particular namespace con-
text
SYNOPSIS
namespace name ?-local? ?-enforced state? ?--? script?
DESCRIPTION
The namespace command finds or creates a namespace name and
executes an optional script code string in that context.
This command is usually used to add commands and variables,
or to redefine commands and variables, in a namespace. When
the script is executed, the namespace command adds a call
frame to the stack, so the namespace command counts as
another level for the uplevel and upvar commands.
If the -local flag is included, the namespace name will be
found or created in the local namespace context. Otherwise,
name could reference any namespace found by following the
import list (see below for rules regarding name resolution).
The -local flag allows a child namespace to be created even
if another namespace can be found with the same name.
If the -enforced flag is included, its argument state is a
boolean value enabling or disabling special rules for
command/variable name resolution. This makes it possible to
create "safe" namespaces with restricted access to commands.
See the section on safe namespaces below for more details.
The optional "--" marks the end of the option list. The
next argument is treated as a command script, even if it
starts with a "-" character.
OVERVIEW
A namespace is a collection of commands and global variables
that is kept apart from the usual global scope. This allows
Tcl code libraries to be packaged in a well-defined manner,
and prevents unwanted interactions with other libraries. A
namespace can also have child namespaces within it, so one
library can contain its own private copy of many other
libraries. The global scope (named "::") is the root
namespace for an interpreter; all other namespaces are con-
tained within it.
Commands and variables local to a namespace can be refer-
enced using simple names within the namespace. But in
another context, they must be qualified by the namespace
name. Namespace qualifiers leading up to a command or vari-
able name are separated by the "::" string.
If a command, variable or namespace name is absolutely qual-
ified (i.e., it starts with "::") then it is accessed
directly. For example, the name "::foo::bar::x" refers to
the variable "x", which is in the namespace "bar", which is
in the namespace "foo", which is in the global namespace.
If the name does not start with "::", it is treated relative
to the current namespace context. Lookup starts in the
current namespace, then continues through all other
namespaces included on the "import" list. The "info which"
command can be used to check the results of name resolution.
Whenever a name is resolved, the result is cached to keep
namespace performance on par with vanilla Tcl.
By default, the import list for each namespace includes the
parent namespace, so commands at the global scope can be
accessed transparently. The import list can be modified by
using the import command within a namespace.
By default, commands and variables are "public" and can be
accessed from any other namespace context. The commands
"protected" and "private" can be used when defining commands
and variables to restrict access to them. These commands
designate which parts of a library are meant to be accessed
by users, and which parts are not.
A namespace can be deleted using the "delete namespace" com-
mand. This deletes all commands and variables in the
namespace, and deletes all child namespaces as well.
EXAMPLE
The following namespace implements a simple counter facil-
ity:
namespace counter {
variable x 0
proc restart {{by 1}} {
global x
set x 0
next $by
}
proc next {{by 1}} {
global x
incr x $by
return $x
}
}
puts "Count up by 2..."
puts " count: [counter::next 2]"
puts " count: [counter::next 2]"
puts " count: [counter::next 2]"
puts "restart: [counter::restart 2]"
puts " count: [counter::next 2]"
The counter namespace contains two commands (restart and
next) and one global variable (x). All of these elements
are protected by the namespace, so there is no conflict, for
example, between "counter::x" and another variable named "x"
in the global namespace.
Procedures execute in the context of the namespace that con-
tains them. Within counter::restart and counter::next, the
variable name "x" refers to "::counter::x". Within
counter::restart, the command "next" refers to
"::counter::next". Outside of the namespace, these names
must be explicitly qualified with the "counter::" namespace
path.
SAFE NAMESPACES
Namespaces include a special enforcement feature that can be
activated using the -enforced flag. When enforcement is
turned on, command and variable references can be inter-
cepted, and the usual lookup rules can be modified. This
supports the construction of "safe" namespaces, which inter-
pret code from an untrusted source and deny access to com-
mands which could damage the system.
Whenever a command name is encountered, the namespace facil-
ity checks to see if the current namespace context is
enforced. If it is not, the usual name resolution rules are
carried out. If it is, the namespace facility executes the
following command in that context:
enforce_cmd name
If this procedure returns an error, access to that command
is denied. If it returns a null string, name resolution
continues according to the usual rules. Otherwise, it
should return the same string name, or the name of another
command to be substituted in its place. This procedure is
only invoked the first time a command name is encountered.
The results are cached in the name resolution tables, so
performance is not adversely affected.
Variable references are handled the same way, except that
the following command is invoked to resolve the reference:
enforce_var name
Note that enforcement is carried out before any of the usual
name resolution rules come into play. Because of this, even
absolute references like "::exec" or "::counter::next" can
be intercepted and dealt with.
Because the enforcement procedures apply to all of the
command/variable references in a namespace, it can be diffi-
cult to define procedures in an enforced namespace and have
them work correctly. If you deny access to the "proc" com-
mand, for example, you will not be able to define any pro-
cedures in the namespace. To avoid problems like this, it
is usually better to use enforced namespaces as follows.
Set up a namespace containing the enforce_cmd and
enforce_var procedures, along with any other code needed to
enforce the namespace. Within that namespace, include a
child namespace that is empty, but has enforcement turned
on. Commands can be fed to the child namespace, which will
automatically look to its parent for the enforcement pro-
cedures and all other commands/variables. Procedures may be
referenced from the child, but they will actually execute in
the parent namespace, which is not enforced.
In the following example, a "safe" namespace is constructed
which will interpret any command string, but will guard
access to commands like "exec" and "open" which are con-
sidered harmful. Calls to "exec" are intercepted and sent
to "safe_exec" for execution. This logs the offending com-
mand in a file "security.log" and returns the null string.
Calls to "open" are intercepted and sent to "safe_open".
This allows read access to ordinary files, but blocks write
operations and execution of processes. Note that the inter-
ception and redirection of commands happens only when com-
mands are interpreted in the namespace "safe::isolated". In
procedures like "safe_exec" and "safe_open", which are
interpreted in namespace "safe", access to "exec" and "open"
is allowed.
namespace safe {
proc interpret {cmds} {
namespace isolated $cmds
}
proc safe_exec {args} {
set mesg "access denied: $args"
puts stderr $mesg
catch {
set fid [open "security.log" a]
puts $fid $mesg
close $fid
}
}
proc safe_open {args} {
set file [lindex $args 0]
if {[string match |* $file]} {
error "cannot open process: $file"
}
set access [lindex $args 1]
if {$access == "r"} {
return [eval open $args]
}
error "cannot open with write access: [lindex $args 0]"
}
proc enforce_cmd {name} {
global commands
if {[info exists commands($name)]} {
return $commands($name)
}
return $name
}
set commands(exec) safe_exec
set commands(::exec) safe_exec
set commands(open) safe_open
set commands(::open) safe_open
proc enforce_var {name} {
if {[string match *::* $name]} {
error "variable access denied: $name"
}
return $name
}
namespace isolated -local -enforced yes
}
#
# Use this to interpret a nasty script:
#
safe::interpret {
#
# Files can be read but not written.
#
set cmd {
set fid [open "/etc/passwd" r]
set info [read $fid]
close $fid
}
puts "read: [catch $cmd result] => $result"
set cmd {
set fid [open "$env(HOME)/.cshrc" w]
puts $fid "# ha! ha!
puts $fid "# make the user think his files have been erased"
puts $fid "alias ls 'echo "total 0"'
close $fid
}
puts "write: [catch $cmd result] => $result"
#
# Kill all of the jobs we can find!
#
set processInfo [lrange [split [exec ps -gx] 0 1 end]
foreach line $processInfo {
set pid [lindex $line 0]
exec kill -9 $pid
}
}
KEYWORDS
delete, import, private, protected, public, variable