lio-0.0.1: Labeled IO library




This module manages a file store in which a label is associated with every file and directory. The file store is grouped into directories by label. Files are stored under names like:


where LabelHash is a SHA-224 hash of the label, and OpaqueName is either a regular file (containing contents) or a directory populated exclusively by symbolic links pointing back into LabelHash directories. Each LabelHash directory also has a file called


which actually contains the label of all the files in that directory.

There is also a symbolic link root, pointing to the root directory. For efficiency, LabelHash actually consists of multiple directories.

There are two externally-visible abstractions. The first is Name, which refers to a file name in a user directory, of the form:


There is also a special Name, rootDir, which refers to the root directory. Untrusted user code has access to the rootDir Name, and can walk the tree from there using the lookupName function. The LIO.Handle module contains functions mkDir and mkLHandle which permit untrusted code to make new Names as well as to do handle-based IO on protected files.

The second is Node, which refers to one of the OpaqueNames that Names point to. Currently, any functions that operate on Nodes are in the IO Monad so as not to be executable by untrusted code. This is important because in order to use a file, someone must have the right to know know that the file exists, and this requires read permission on the file's Name. It would be insecure if untrusted code could execute openNode in the LIO Monad.

Note that if a machine crashes, the code in this module could leave the filesystem in an inconsistent state. However, the code tries to maitain the invariant that any inconsistencies will either be:

  1. temporary files or directories whose names end with the "~" character, or
  2. dangling symbolic links.

Both of these inconsistencies can be checked and cleaned up locally without examining the whole file system. The code tries to fix up these inconsistencies on-the-fly as it encounters them. However, it could possibly lieave some stranded temporary LABEL...~ files. You could also end up with some weirdness like a file that shows up in getDirectoryContents, but that you can't open for reading.

To keep from having to examine the whole file system to fix errors, the code tries to maintain the invariant that if a 'Node'\'s file name doesn't end with ~, then there must be a link pointing to it somewhere. This is why the code uses a separate NewNode type to represent a Node whose name ends ~. The function linkNode renames the NewNode to a name without a trailing ~ only after creating a Name that points to the permenent Node path.

Assuming a file system that preserves the order of metadata operations, the code should mostly be okay to recover from any crashes. If using soft updates, which can re-order metadata operations, you could end up with symbolic links that point nowhere.

In the worst case scenario if inconsistencies develop, you can manually fix up the file system by deleting all danglinng symbolic links and all files and directories ending ~. Make sure no application is concurrently accessing the file system, however.


The opaque name object

data Name l Source

The Name type represents user-chosen (non-opaque) filenames of symbolic links, either "root" or pathnames of the form LabelHash/OpaqueName/filename. Intermediary components of the file name must not be symbolic links.


Show l => Show (Name l) 

rootDir :: Label l => LIO l s (Name l)Source

Return the root directory for the default root label. (There is a root directory for each label, but only one label is the default.)

getRootDir :: Label l => l -> Name lSource

Get the root directory for a particular label.

mkRootDir :: Priv l p => p -> l -> LIO l s (Name l)Source

Creates a root directory for a particular label.



:: Priv l p 
=> p

Privileges to limit tainting

-> Name l

Start point

-> FilePath

Name to look up

-> LIO l s (Name l) 

Looks up a FilePath, turning it into a Name, and raising to current label to reflect all directories traversed. Note that this only looks up a Name; it does not ensure the Name actually exists. The intent is that you call lookupName before creating or opening files.

Note that this function will touch bad parts of the file system if it is supplied with a malicous Name. Thus, it is important to keep the constructor of Name private, so that the only way for user code to generate names is to start with rootDir and call lookupName.



:: Priv l p 
=> p

Privileges to minimize tainting

-> l

Label for the new directory

-> Name l

Name of dir in which to create directory

-> String

Suffix for name of directory

-> LIO l s (FilePath, Name l)

Returns both name in directory and Name of new directory

Creates a temporary directory in an existing directory (or label-specific root directory, if the Name argument comes from getRootDir).

Initializing the file system

initFS :: Label l => l -> IO ()Source

Internal data structures

data Node Source

The Node type represents filenames of the form LabelHash/OpaqueName. These names must always point to regular files or directories (not symbolic links). There must always exist a file LabalHash/LABEL specifying the label of a Node.


Helper functions in the IO Monad

labelOfName :: Label l => Name l -> IO lSource

Label protecting the name of a file. Note that this is the label of the directory containing the file name, not the label of the Node that the file name designates.

labelOfNode :: Label l => Node -> IO lSource

Label protecting the contents of a node.

nodeOfName :: Label l => Name l -> IO NodeSource

Node that a Name is pointing to.



:: Label l 
=> l

Label for the new node

-> (FilePath -> String -> IO (a, FilePath))

Either mkTmpDir or mkTmpFile with curried IOMode

-> IO (a, NewNode)

Returns file handle or () and destination path

Create new Node in the appropriate directory for a given label. The node gets created with an extra ~ appended, and wrapped in the type NewNode to reflect this fact.

mkNodeDir :: Label l => l -> IO NewNodeSource

Wrapper around mkNode to create a directory.

mkNodeReg :: Label l => IOMode -> l -> IO (Handle, NewNode)Source

Wrapper around mkNode to create a regular file.

linkNode :: Label l => NewNode -> Name l -> IO NodeSource

Assign a Name to a NewNode, turning it into a Node. Note that unlike the Unix file system, only a single link may be created to each node.



:: Priv l p 
=> p

Privileges to limit tainting

-> Name l

Start point (e.g., rootDir)

-> FilePath

Name to look up

-> Bool

True if you want to write it

-> LIO l s Node 

openNode :: Node -> IOMode -> IO HandleSource

Thie function just calls openFile on the filename in a Node. However, on the off chance that the file system is in an inconsistent state (e.g., because of a crash during a call to linkNode), it tries to finish creating a partially created Node.

getDirectoryContentsNode :: Node -> IO [FilePath]Source

Thie function is a wrapper around getDirectoryContents that tries to fixup errors analogously to openNode.

Misc. utility functions

tryPred :: Exception e => (e -> Bool) -> IO a -> IO (Either e a)Source