The first one is putting a lot of trust in the cp command: we need to give it read/write access to the file system, and we hope that it will only access those paths we asked it to, and that it will access them in the right ways (reading from one, writing to the other).
In the second command, the cat program doesn’t need any access to the filesystem, and the only thing we need to trust is that it will send through the data unchanged. That trust seems pretty much required though, since it’s the reason to use cat in the first place.
A similar example is how Haskell programs tend to implement most of their logic using pure functions, and limit potentially-dangerous IO actions to the periphery. For example, I’ve written many programs which look like this:
main :: IO ()
main = interact someFunction
someFunction :: String -> String
someFunction input = ...
Here the main function has type IO (), meaning that it can perform arbitrary effects. It calls the interact function, which reads from stdin and writes to stdout, transforming one into the other using the given function (here called someFunction). The nice part is: someFunction is just a pure function from Strings to Strings, it doesn’t care where these Strings come from or go to. The only “capability” available to someFunction (and hence to any other functions that it calls) is to affect the contents of its output String (assuming we’re using the “Safe Haskell” option in GHC).
Compared to the above cp/cat example, we can think of main as acting like the shell, and interact as acting like < /dev/stdin > /dev/stdout.
A nice introductory example of capabilities vs access control lists is to compare the following commands:
The first one is putting a lot of trust in the
cpcommand: we need to give it read/write access to the file system, and we hope that it will only access those paths we asked it to, and that it will access them in the right ways (reading from one, writing to the other).In the second command, the
catprogram doesn’t need any access to the filesystem, and the only thing we need to trust is that it will send through the data unchanged. That trust seems pretty much required though, since it’s the reason to usecatin the first place.A similar example is how Haskell programs tend to implement most of their logic using pure functions, and limit potentially-dangerous
IOactions to the periphery. For example, I’ve written many programs which look like this:Here the
mainfunction has typeIO (), meaning that it can perform arbitrary effects. It calls theinteractfunction, which reads from stdin and writes to stdout, transforming one into the other using the given function (here calledsomeFunction). The nice part is:someFunctionis just a pure function fromStrings toStrings, it doesn’t care where theseStrings come from or go to. The only “capability” available tosomeFunction(and hence to any other functions that it calls) is to affect the contents of its outputString(assuming we’re using the “Safe Haskell” option in GHC).Compared to the above
cp/catexample, we can think ofmainas acting like the shell, andinteractas acting like< /dev/stdin > /dev/stdout.