Examples
On this page:
2.1 Copy
2.1.1 Capability-safe script
2.1.2 Ambient script
2.2 Grep
2.2.1 Capability-safe script
2.2.2 Ambient script

2 Examples

This section provides a quick introduction to Shill with a series of examples.

2.1 Copy

This example implements a Shill script that copies files from one directory to another. You can find the full source in the "examples/copy" directory of the source distribution.

The first line of every Shill script specifies whether it is a capability-safe or ambient script. Capability-safe scripts can only create capabilities from capabilities they are passed as arguments. Ambient scripts can create capabilities for any system resource, but can only use those capabilities by invoking a capability-safe script.

2.1.1 Capability-safe script

We’ll start by implementing a capability-safe script "copy.cap". The first line of this script indicates that it is a capability-safe script.
#lang shill/cap

The body of the script will contain a single definition for a function called copy. This function takes three arguments: a boolean specifying whether the copy should be recursive, a directory capability to copy from, and a directory capability to copy to.

val copy = fun(recur,from,to) {
  val files = contents(from);
  for f in files do {
    val source = lookup(from,f);
    if file?(source) then {
      val target = create-file(to,f);
      write(target,read(source));
    } else if dir?(source) /\ recur then {
      existing = lookup(to,f);
      dir = if sys-error?(existing) then create-dir(to,f) else existing;
      copy(recur,source,dir);
    };
  };
};

For this function to be callable from other capability-safe scripts or an ambient script we must export its definition using a provide statement at the top of the file (below #lang shill/cap).

provide
  { copy : { recur : boolean?,
             from : dir/c(+contents, +lookup, +read),
             to : dir/c(+create-file with { +write }, +create-dir, +lookup) }
             -> any };

This statement exports the definition of copy along with a contract. This contract specifies that copy expects that its first argument satisfies the predicate boolean? and that the from and to arguments should be directory capabilities. In addition, this contract says that the from directory must have at least the privileges +contents, +lookup, and +read. It also guarantees that at runtime the copy function cannot invoke any other operation on this capability. Similarly, the contract says that the to capability should have the privileges +create-file with { +write }, +create-dir, and +lookup. The with { +write } annotation says that capabilities derived using the +create-file privilege should have just the +write privilege. By default, capabilities deriving using privileges like +create-file and +lookup have the same set of privileges as their parent.

2.1.2 Ambient script

To invoke this script, we need to write an ambient script that creates capabilities for the resources we want to use. The following script, "copy.amb" invokes "copy.cap" with capabilities for the directories "source" and "target".

#lang shill/ambient
 
require "copy.cap";
 
val dir = open-dir("source");
val tmp = open-dir("target");
copy(true,dir,tmp);
 

The script starts with #lang shill/ambient to indicate that it is an ambient script. It imports definitions from "copy.cap" with a require statement, opens two directories to create capabilities, and then passes them to copy.

To run this script, invoke shill copy.amb at the command line.

2.2 Grep

This examples shows how Shill’s standard libraries can be used to easily execute programs in a capability-safe sandbox. You can find the full source in the "examples/grep" directory of the source distribution.

2.2.1 Capability-safe script

The following capability-safe script creates a wrapper for invoking the native grep application in a sandbox. It relies on the "shill/native" library to search for the grep executable in a capability wallet, similar to the familiar path-based resolution in other langauges. The next section, Ambient script shows how to create a wallet to use with the library.

The call to pkg-native resolves grep in the provided wallet and returns a function that encapsulates the process of creating a sandbox. We can then invoke grep by calling this function with the appropriate arguments.

#lang shill/cap
 
provide { grep : { wallet : native-wallet/c,
           args   : listof(arg/c) }
   +optional
 { stdin  : maybe/c(readable/c),
   stdout : maybe/c(writeable/c),
   stderr : maybe/c(writeable/c),
   timeout : timeout/c}
   -> integer? };
 
require shill/contracts shill/native;
 
# grep takes as input a wallet containing capabilities for
# running the grep executable, a list of arguments to pass
# to grep, and optional arguments for specifying input and
# output streams as well as a timeout.
grep = fun (wallet, args,
            stdin = false, stdout = false, stderr = false,
    timeout = false) {
  # pkg-native uses the capabilities in wallet to create
  # a closure that will execute the requested executable
  # in a capability-based sandbox
  executable = pkg-native("grep",wallet);
  executable(args,timeout = timeout,
             stdin = stdin, stdout = stdout, stderr = stderr);
}
 
2.2.2 Ambient script

To invoke the capability-safe grep function, we need to write an ambient script. The script begins by importing the "shill/native" library and the capability-safe script "grep.cap".

#lang shill/ambient
 
require shill/native;
 
require "grep.cap";

The grep function requires a capability wallet that encapsulates the capabilities for libraries and executables needed to invoke grep in a sandbox. We can create this wallet using the standard library function populate-native-wallet, which takes as input an empty wallet, a capability for the root directory paths should be resolved against, a path specificaiton, a library-path specification, and a pipe-factory capability that allows the script to create pipes.

populate-native-wallet will resolve the path and library specifications provided with respect to the root capability, and package the resulting capabilities in the wallet.

# Create a wallet with the necessary capabilities
# for running the 'grep' executable.
wallet = create-wallet();
populate-native-wallet(wallet,
        open-dir("/"),
        "/usr/bin",
        "/libexec:/lib:/usr/lib",
        pipe-factory);

Finally, we can invoke the grep function passing in this capability wallet along with the arguments to pass to grep, and a capability on which to write the output.

# Equivalent to 'grep -R require ../'
grep(wallet,["-R","require",open-dir("../")],stdout = stdout);