# Tiny UNIX in Links. ## ## Functional utils ## typename Option(a) = [|None|Some:a|]; sig modify : (a, b, [(a, b)]) ~> [(a, b)] fun modify(x, y, xs) { switch (xs) { case [] -> [] case (x', y') :: xs' -> if (x == x') (x, y) :: xs' else (x', y') :: modify(x, y, xs') } } ### ### Generic queue ### module Queue { typename T(a) = (front:[a], rear:[a]); sig empty : T(a) var empty = (front=[], rear=[]); sig enqueue : (a, T(a)) -> T(a) fun enqueue(x, q) { (q with rear = x :: q.rear) } sig dequeue : (T(a)) ~> (Option(a), T(a)) fun dequeue(q) { switch(q.front) { case [] -> switch (q.rear) { case [] -> (None, q) case rear -> dequeue((front=reverse(rear),rear=[])) } case x :: xs -> (Some(x), (q with front = xs)) } } sig singleton : (a) -> T(a) fun singleton(x) { enqueue(x, empty) } } ## ## Environment ## sig getenv : (String) {Getenv:(String) -> String |_}-> String fun getenv(s) { do Getenv(s) } sig environment : ([(String,String)], () {Getenv:(String) -> String |e}~> a) {Getenv{_} |e}~> a fun environment(env', m) { handle(m()) { case Return(x) -> x case Getenv(s, resume) -> switch (lookup(s, env')) { case Nothing -> resume("") case Just(s') -> resume(s') } } } typename Environment = [(String, String)]; ## ## User management ## typename User = [|Root|Alice|Bob|]; typename EnvironmentStore = [(User, Environment)]; sig envs : [(User, Environment)] var envs = [ (Root , [("USER", "root") , ("UID", "0")]) , (Alice, [("USER", "alice"), ("UID", "1")]) , (Bob , [("USER", "bob") , ("UID", "2")]) ]; sig envOf : (User, [(User, Environment)]) ~> Environment fun envOf(user, envs) { switch (envs) { case [] -> error("No environment") # TODO: could interpret as something "fun" such as a kernel panic. case (user', env) :: envs -> if (user == user') env else envOf(user, envs) } } sig su : (User) {Su:(User) -> () |_}-> () fun su(user) { do Su(user) } sig usermgr : (User, EnvironmentStore, Comp(a, {Getenv:(String) -> String,Su:(User) -> () |e})) {Getenv{_},Su{_} |e}~> a fun usermgr(user, envs, m) { environment(envOf(user, envs), fun() { handle(m()) { case Su(user, resume) -> environment(envOf(user, envs), fun(){ resume(()) }) } }) } ## ## Basic IO ## typename FileDescr = Int; typename FileCursor = Int; module File { typename T = [String]; sig empty : T var empty = []; sig read : (FileCursor, T) ~> Option(String) fun read(start, file) { switch (drop(start, file)) { case [] -> None case x :: _ -> Some(x) } } sig write : (String, FileCursor, T) ~> T fun write(contents, fptr, file) { take(fptr, file) ++ [contents] ++ drop(fptr, file) } } sig stdout : FileDescr var stdout = 1; sig puts : (FileDescr,String) {Puts:(FileDescr,String) -> () |_}-> () fun puts(fd, s) { do Puts(fd, s) } sig basicIO : (Comp(a, {Puts:(FileDescr,String) -> () |e})) {Puts{_} |e}~> File.T fun basicIO(m) { handle(m()) { case Return(_) -> [] case Puts(_, s, resume) -> s :: resume(()) } } ## ## Generic state handling. ## sig get : () {Get:s |_}-> s fun get() { do Get } sig put : (s) {Put:(s) -> () |_}-> () fun put(st) { do Put(st) } sig runState : (s, Comp(a, {Get:() -> s,Put:(s) -> () |e})) {Get{_},Put{_} |e}~> (a, s) fun runState(st0, m) { var f = handle(m()) { case Return(x) -> fun(st) { (x, st) } case Get(resume) -> fun(st) { resume(st)(st) } case Put(st,resume) -> fun(_) { resume(())(st) } }; f(st0) } ## ## File IO ## sig stdin : FileDescr var stdin = 0; sig stderr : FileDescr var stderr = 2; sig eof : String var eof = "\x00"; typename Mode = [|Read|Write|]; typename FileDescr = Int; typename INode = (loc:Option(Int),refc:Int); typename INodeTable = [(INode, File.T)]; typename FileTable = [(Mode, INode)]; typename # sig gets : (FileDescr) {Gets:(FileDescr) -> String |_}-> String # fun gets(fd) { do Gets(fd) } # sig fopen : (Mode, String) {Fopen:(Mode, String) -> FileDescr |_}-> FileDescr # fun fopen(mode, filename) { do Fopen(mode, filename) } # sig fclose : (FileDescr) {Fclose:(FileDescr) -> () |_}-> () # fun fclose(fd) { do Fclose(fd) } # typename File = Queue.T(String); # sig emptyFile : File # var emptyFile = Queue.empty; # sig writeFile : (String, File) -> File # fun writeFile(s, file) { Queue.enqueue(s, file) } # sig readFile : (File) ~> (String, File) # fun readFile(file) { # switch (Queue.dequeue(file)) { # case (None, file) -> (eof, file) # case (Some(s), file) -> (s, file) # } # } # typename FileTable = [(FileDescr, File)]; # typename FileStore = [(String, FileDescr)]; # typename FileSystem = (next:Int,ft:FileTable,fs:FileStore); # sig defaultFileSystem : () -> FileSystem # fun defaultFileSystem() { # var defaultTable = [ (stdin , emptyFile) # , (stdout, emptyFile) # , (stderr, emptyFile) ]; # var defaultStore = [ ("stdin" , stdin) # , ("stdout", stdout) # , ("stderr", stderr) ]; # (next=3,ft=defaultTable,fs=defaultStore) # } # sig lookupFile : (FileDescr, FileSystem) ~> File # fun lookupFile(fd, fsys) { # switch (lookup(fd, fsys.ft)) { # case Nothing -> error("err: No such file(" ^^ intToString(fd) ^^ ")") # case Just(file) -> file # } # } # sig replaceFile : (FileDescr, File, FileSystem) ~> FileSystem # fun replaceFile(fd, file, fsys) { # var ft = modify(fd, file, fsys.ft); # (fsys with ft = ft) # TODO handle nonexistent file. # } # sig createFile : (String, FileSystem) -> (FileDescr, FileSystem) # fun createFile(filename, fsys) { # var fd = fsys.next; # (fd, (next = fd + 1, fs = (filename, fd) :: fsys.fs, ft = (fd, emptyFile) :: fsys.ft)) # } # sig openFile : (Mode, String, FileSystem) ~> (FileDescr, FileSystem) # fun openFile(mode, filename, fsys) { # var (fd, fsys') = switch (lookup(filename, fsys.fs)) { # case Nothing -> createFile(filename, fsys) # case Just(fd) -> (fd, fsys) # }; # switch (mode) { # case Create -> error("erase") # case Append -> (fd, fsys') # } # } # sig closeFile : (File) ~> File # fun closeFile((=front,=rear)) { # (front=front ++ reverse(rear), rear=[]) # } # sig allowState : (() {Get-,Put- |e}~> a) -> () {Get:s,Put:(s) -> () |e}~> a # fun allowState(f) { (f : (() {Get:s,Put:(s) -> () |e}~> a) <- (() {Get-,Put- |e}~> a)) } # sig fileIO : (Comp(a, {Get-,Put-,Gets:(FileDescr) -> String,Puts:(FileDescr,String) -> (),Fclose:(FileDescr) -> (),Fopen:(Mode,String) -> FileDescr |e})) {Get:() {}-> FileSystem,Put:(FileSystem) -> (),Gets{_},Puts{_},Fclose{_},Fopen{_} |e}~> a # fun fileIO(m) { # handle(allowState(m)()) { # case Gets(fd, resume) -> # var fsys = get(); # var (ch, file) = readFile(lookupFile(fd, fsys)); # put(replaceFile(fd, file, fsys)); resume(ch) # case Puts(fd, ch, resume) -> # var fsys = get(); # var fsys' = replaceFile(fd, writeFile(ch, lookupFile(fd, fsys)), fsys); # put(fsys'); resume(()) # case Fopen(mode, filename, resume) -> # var fsys = get(); # var (fd, fsys') = openFile(mode, filename, fsys); # put(fsys'); resume(fd) # case Fclose(fd, resume) -> # var fsys = get(); # var fsys' = replaceFile(fd, closeFile(lookupFile(fd, fsys)), fsys); # put(fsys'); resume(()) # } # } # sig redirect : (Comp(a, {Puts:(FileDescr,String) -> () |e}), FileDescr) {Puts:(FileDescr,String) -> () |e}~> a # fun redirect(m, fd) { # handle(m()) { # case Puts(_,s,resume) -> resume(puts(fd, s)) # } # } ## ## Processes ## sig yield : () {Yield:() |_}-> () fun yield() { do Yield() } sig fork : () {Fork:Bool |_}-> Bool fun fork() { do Fork } mutual { typename Process(e::Eff) = forall p::Presence,q::Presence. [|R:(()) {Fork{p},Yield{q} |e}~> PList({ |e})|]; typename PList(e::Eff) = [Process({ |e})]; } sig runNext : (PList({ |e})) {Yield{_},Fork{_} |e}~> () fun runNext(pending) { switch (concatMap(fun(R(r)) { r(()) }, pending)) { case [] -> () case pending -> runNext(pending) } } sig timeshare : (Comp(a, {Fork:Bool,Yield:() |e})) {Fork{_},Yield{_} |e}~> PList({ |e}) fun timeshare(proc) { handle(proc()) { case Return(_) -> [] case Yield(resume) -> [R(resume)] case Fork(resume) -> resume(true) ++ resume(false) } } sig schedule : (Comp(a, {Fork:Bool,Yield:() |e})) {Fork{_},Yield{_} |e}~> () fun schedule(m) { runNext(timeshare(m)) } # sig replace : (() {Getenv{p},Exece:(() {Getenv:(String) -> String,Exece{q} |e}~> (), [(String, String)]) -> Zero |e}~> a) {Getenv{p},Exece{q} |e}~> () # fun replace(proc) { # handle(proc()) { # case Return(_) -> () # case Exece(f, env, _) -> # environment(env, f) # } # } ## ## Utilities ## sig echo : (String) {Puts:(FileDescr,String) -> (), Yield:() |_}-> () fun echo(s) { yield(); puts(stdout, s) } sig amiroot : () {Getenv:(String) -> String |_}-> Bool fun amiroot() { getenv("UID") == "0" } sig whoami : () {Getenv:(String) -> String |_}-> String fun whoami() { getenv("USER") } ## ## Example ## # Tags puts with the name of the current user. # sig provenance : (Comp(a, {Getenv:(String) -> String,Puts:(FileDescr,String) -> () |e})) {Getenv:(String) -> String,Puts:(FileDescr,String) -> () |e}~> a # fun provenance(m) { # handle(m()) { # case Puts(fd, s, resume) -> # var user = whoami(); # resume(do Puts(fd, user ^^ "> " ^^ s)) # } # } # # An example of everything plugged together: a time-shared 'Hello World'. # sig example : () {Fork:Bool,Getenv:(String) -> String,Su:(User) -> (),Puts:(FileDescr,String) -> (),Yield:() |_}~> () # fun example() { # var pid = fork(); # var () = { # if (pid) redirect(fun(){puts(stdout, "dummy")}, stderr) # else if (fork()) su(Alice) # else su(Bob) # }; # var user = whoami(); # puts(stdout, "Hello World!"); # var uid = getenv("UID"); # echo("My UID is " ^^ uid); # (if (amiroot()) { yield(); echo(user ^^ " is running as root.") } else ()); # echo("My home dir is /home/" ^^ user) # } # # Wiring of handlers. # sig init : () {Fork{_},Getenv{_},Su{_},Puts{_},Yield{_} |_}~> [String] # fun init() { # basicIO(fun() { # schedule(fun() { # usermgr(Root, envs, fun() { # provenance(example) # }) # }) # }) # } # sig example' : () {Fork:Bool,Fclose:(FileDescr) -> (),Fopen:(Mode,String) -> FileDescr,Getenv:(String) -> String,Su:(User) -> (),Puts:(FileDescr,String) -> (),Yield:() |_}~> () # fun example'() { # var pid = fork(); # var () = { # if (pid) redirect(fun(){puts(stdout, "dummy")}, stderr) # else if (fork()) su(Alice) # else su(Bob) # }; # var user = whoami(); # var fd = fopen(Append, user ^^ ".txt"); # puts(fd, "Hello World!"); # var uid = getenv("UID"); # echo("My UID is " ^^ uid); # (if (amiroot()) { yield(); echo(user ^^ " is running as root.") } else ()); # echo("My home dir is /home/" ^^ user); # fclose(fd) # } # sig init' : (FileSystem) {Fclose{_},Fopen{_},Fork{_},Get{_},Getenv{_},Gets{_},Put{_},Puts{_},Su{_},Yield{_}|_}~> ((), FileSystem) # fun init'(fsys) { # runState(fsys, fun() { # fileIO(fun() { # schedule(fun() { # usermgr(Root, envs, example') # }) # }) # }) # }