LIO.FS
Contents
Description
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:
LabelHash/OpaqueName
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
LabelHash/LABEL
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:
LabelHash/OpaqueName/UserName
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 Name
s as
well as to do handle-based IO on protected files.
The second is Node
, which refers to one of the OpaqueName
s
that Name
s point to. Currently, any functions that operate on
Node
s 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:
- temporary files or directories whose names end with the
"
~
" character, or - 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.
- data Name l
- rootDir :: Label l => LIO l s (Name l)
- getRootDir :: Label l => l -> Name l
- mkRootDir :: Priv l p => p -> l -> LIO l s (Name l)
- lookupName :: Priv l p => p -> Name l -> FilePath -> LIO l s (Name l)
- mkTmpDirL :: Priv l p => p -> l -> Name l -> String -> LIO l s (FilePath, Name l)
- initFS :: Label l => l -> IO ()
- data Node
- labelOfName :: Label l => Name l -> IO l
- labelOfNode :: Label l => Node -> IO l
- nodeOfName :: Label l => Name l -> IO Node
- mkNode :: Label l => l -> (FilePath -> String -> IO (a, FilePath)) -> IO (a, NewNode)
- mkNodeDir :: Label l => l -> IO NewNode
- mkNodeReg :: Label l => IOMode -> l -> IO (Handle, NewNode)
- linkNode :: Label l => NewNode -> Name l -> IO Node
- lookupNode :: Priv l p => p -> Name l -> FilePath -> Bool -> LIO l s Node
- openNode :: Node -> IOMode -> IO Handle
- getDirectoryContentsNode :: Node -> IO [FilePath]
- tryPred :: Exception e => (e -> Bool) -> IO a -> IO (Either e a)
The opaque name object
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.
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.
Arguments
:: 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
.
Arguments
:: Priv l p | |
=> p | Privileges to minimize tainting |
-> l | Label for the new directory |
-> Name l |
|
-> String | Suffix for name of directory |
-> LIO l s (FilePath, Name l) | Returns both name in directory and |
Creates a temporary directory in an existing directory (or
label-specific root directory, if the Name
argument comes from
getRootDir
).
Initializing the file system
Internal data structures
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.
Arguments
:: Label l | |
=> l | Label for the new node |
-> (FilePath -> String -> IO (a, FilePath)) | |
-> 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.
mkNodeReg :: Label l => IOMode -> l -> IO (Handle, NewNode)Source
Wrapper around mkNode to create a regular file.
getDirectoryContentsNode :: Node -> IO [FilePath]Source
Thie function is a wrapper around getDirectoryContents
that
tries to fixup errors analogously to openNode
.