fs/write.bend

cswap-bend-cli@0.1.0.0 · 94 lines · 17 definitions

1import Base
2import cswap-bend-cli@0.1.0.0/fs.bend as Fs
3
4# ---- write ---------------------------------------------------------------------
5
6def create.done(r: Fs.Res(File)) -> IO(Fs.Res(Unit)):
7  match r:
8    case Fail{e}:
9      IO.pure(Fs.Res(Unit), Fail{e})
10    case Done{f}:
11      IO.bind(Unit, Fs.Res(Unit), File.close(f), u => IO.pure(Fs.Res(Unit), Done{Unit{}}))
12
13# Create an empty file (a no-op on an existing one).
14def create(path: String) -> IO(Fs.Res(Unit)):
15  IO.bind(Fs.Res(File), Fs.Res(Unit), File.open(path, "a"), create.done)
16
17def put.close(fr: File & Fs.Res(Unit)) -> IO(Fs.Res(Unit)):
18  (f, r) = fr
19  IO.bind(Unit, Fs.Res(Unit), File.close(f), u => IO.pure(Fs.Res(Unit), r))
20
21def put.write(data: String, r: Fs.Res(File)) -> IO(Fs.Res(Unit)):
22  match r:
23    case Fail{e}:
24      IO.pure(Fs.Res(Unit), Fail{e})
25    case Done{f}:
26      IO.bind(File & Fs.Res(Unit), Fs.Res(Unit), File.write(f, data), put.close)
27
28# Truncate and write `data` (the file keeps its mode if it exists).
29def put(path: String, data: String) -> IO(Fs.Res(Unit)):
30  IO.bind(Fs.Res(File), Fs.Res(Unit), File.open(path, "w"), r => put.write(data, r))
31
32def chmod(mode: String, path: String) -> IO(Fs.Res(Unit)):
33  Fs.tool_unit("/bin/chmod", ["--", mode, path])
34
35def rename(src: String, dst: String) -> IO(Fs.Res(Unit)):
36  Fs.tool_unit("/bin/mv", ["-f", "--", src, dst])
37
38# Delete a file; a missing file is not an error.
39def remove(path: String) -> IO(Fs.Res(Unit)):
40  Fs.tool_unit("/bin/rm", ["-f", "--", path])
41
42def mkdir_p(path: String) -> IO(Fs.Res(Unit)):
43  Fs.tool_unit("/bin/mkdir", ["-p", "--", path])
44
45def publish.fail(r: Fs.Res(Unit), +tmp: String) -> IO(Fs.Res(Unit)):
46  match r:
47    case Done{u}:
48      IO.pure(Fs.Res(Unit), Done{u})
49    case Fail{e}:
50      IO.bind(Fs.Res(Unit), Fs.Res(Unit), remove(tmp), c => IO.pure(Fs.Res(Unit), Fail{e}))
51
52def publish.mv(r: Fs.Res(Unit), +tmp: String, +path: String) -> IO(Fs.Res(Unit)):
53  match r:
54    case Fail{e}:
55      publish.fail(Fail{e}, tmp)
56    case Done{u}:
57      IO.bind(Fs.Res(Unit), Fs.Res(Unit), rename(tmp, path), m => publish.fail(m, tmp))
58
59def publish.put(r: Fs.Res(Unit), +tmp: String, +path: String, +data: String) -> IO(Fs.Res(Unit)):
60  match r:
61    case Fail{e}:
62      publish.fail(Fail{e}, tmp)
63    case Done{u}:
64      IO.bind(Fs.Res(Unit), Fs.Res(Unit), put(tmp, data), w => publish.mv(w, tmp, path))
65
66def publish.chmod(r: Fs.Res(Unit), +tmp: String, +path: String, +data: String, +mode: String) -> IO(Fs.Res(Unit)):
67  match r:
68    case Fail{e}:
69      publish.fail(Fail{e}, tmp)
70    case Done{u}:
71      IO.bind(Fs.Res(Unit), Fs.Res(Unit), chmod(mode, tmp), c => publish.put(c, tmp, path, data))
72
73def publish.create(+tmp: String, +path: String, +data: String, +mode: String) -> IO(Fs.Res(Unit)):
74  IO.bind(Fs.Res(Unit), Fs.Res(Unit), create(tmp), c => publish.chmod(c, tmp, path, data, mode))
75
76def publish.tmp(r: Fs.Res(U32), +path: String, +data: String, +mode: String) -> IO(Fs.Res(Unit)):
77  match r:
78    case Fail{e}:
79      IO.pure(Fs.Res(Unit), Fail{e})
80    case Done{n}:
81      publish.create(path ++ ".tmp-" ++ U32.show(n), path, data, mode)
82
83# Write `data` to a sibling temp file, give it `mode` (an octal string such
84# as "600") while it is still empty, then rename it over `path`. The
85# secret is never on disk at another mode, and readers see the old file or
86# the new one, never half of it.
87def write_atomic(path: String, data: String, mode: String) -> IO(Fs.Res(Unit)):
88  IO.bind(Fs.Res(U32), Fs.Res(Unit), IO.random_u32(), r => publish.tmp(r, path, data, mode))
89
90# ---- append ---------------------------------------------------------------------------
91
92# Append `data` to a file, creating it (a log line at a time).
93def append(path: String, data: String) -> IO(Fs.Res(Unit)):
94  IO.bind(Fs.Res(File), Fs.Res(Unit), File.open(path, "a"), r => put.write(data, r))