0
votes

I would like a way to limit what packages can be loaded into tcl. I can't just remove them from the path, as it has some packages that should be loaded.

Is there a way to handle this without separating the packages to different directories ?

Also, is there a way to add a trace to every package being loaded ? So I can see after the fact which packages have been loaded ?

Thanks

1

1 Answers

2
votes

Tcl loads packages in several stages, which are described in a reasonable amount of detail here:

  1. If you ask for a package it does not already known about, it calls the handler registered with package unknown to search the package path (with various origins of the bits of the path) for package index files (pkgIndex.tcl) which provide descriptors of the packages that can be loaded. These descriptors are files that call package ifneeded to give a script to call to load a particular version of a particular package name. If it still doesn't know about the package after doing the loading, you get an error message.

    Most pkgIndex.tcl scripts are incredibly simple, though they tend to be dynamically generated during the package installation process. A couple of minor points though; they are not evaluated in the global context, but rather are inside a scope that defines a local dir variable that says the name of the directory containing the pkgIndex.tcl script, and they are always assumed to be UTF-8 encoded on all platforms (I recommend only using ASCII characters in them anyway, which tends to be pretty easy to do precisely because the script is entirely under programmer control).

  2. If you ask for a package it does know about, because the package is either already loaded or there is a descriptor present for it, it checks the version numbers for a match and if the package isn't actually loaded, it evaluates the script provided through package ifneeded in the global context to actually load the package. That will usually source Tcl scripts and/or load DLLs. Some packages do more complicated things than that, but it's common to not put too much complexity in the descriptor scripts themselves: complicated stuff goes into the code they load in, which can be as complex as the package needs it to be.

Now, there is another package subcommand that is useful to you, package forget, which makes Tcl forget the package descriptor and whether the package is loaded. It does not forget the implementation of the package if it is actually loaded but if you can forget the descriptor before stage two, you deny the Tcl interpreter the ability to load the package. If you've got a whitelist of allowed package names and versions, all you need to do is intercept the package unknown handler, delegate to the old implementation, and then strip out anything you don't want before returning. Fortunately, the unknown package handler system is designed to be interceptable like this.

# These packages are to not be version-checked
set builtins {zlib TclOO tcl::tommath Tcl}
# These are the external packages we want to support filtering; THIS IS A WHITELIST!
set whitelist {
    Thread {2.8.1}
    yaml {0.3.1}
    http {2.8.11}
}

proc filterPackages {wrappedCall args} {
    # Do the delegation first
    uplevel 1 [list {*}$wrappedCall {*}$args]

    # Now filter out anything unexpected
    global builtins whitelist
    foreach p [package names] {
        # Check if this is a package we should just tolerate first
        if {$p in $builtins} continue
        set scripts {}
        foreach v [package versions $p] {
            if {[dict exists $whitelist $p] && $v in [dict get $whitelist $p]} {
                # This is a version we want to keep
                lappend scripts [list \
                    package ifneeded $p $v [package ifneeded $p $v]]
                # Everything else is going to be forgotten in a few moments
            }
        }
        # Forget all those package descriptors...
        package forget $p
        # ...but put back the ones we want.
        foreach s $scripts { eval $s }
    }
}

# Now install our filter
package unknown [list filterPackages [package unknown]]

As long as install this filter early in your script, before any package require commands have been done (except of the small list of true builtins), you'll have a system which can only package require the packages that you want to allow.