1 import Base 2 import ./ByteManifest.bend as Manifest 3 import ./Fs.bend as Fs 4 import bend-kit-bytes@0.3.2.0/bytes.bend as Bytes 5 6 # Separates invalid manifest bytes from filesystem failures while opening it. 7 type Error is Data: 8 Invalid{error: Manifest.Error} 9 Host{code: U32, message: String} 10 11 def path.reverse(path: String, reversed: String) -> String: 12 match path: 13 case SNil{}: reversed 14 case SCon{character, rest}: path.reverse(rest, SCon{character, reversed}) 15 16 def path.clone(path: String) -> String: 17 path.reverse(path.reverse(path, SNil{}), SNil{}) 18 19 def read.result( 20 result: Result<&1, &1, Manifest.Error, Manifest.ManifestValue> 21 ) -> IO(Result<&1, &1, Error, Manifest.ManifestValue>): 22 match result: 23 case Fail{error}: 24 IO.pure(Result<&1, &1, Error, Manifest.ManifestValue>, Fail{Invalid{error}}) 25 case Done{value}: 26 IO.pure(Result<&1, &1, Error, Manifest.ManifestValue>, Done{value}) 27 28 def read.host(error: U32 & String) -> IO(Result<&1, &1, Error, Manifest.ManifestValue>): 29 match error: 30 case (code, message): 31 IO.pure(Result<&1, &1, Error, Manifest.ManifestValue>, Fail{Host{code, message}}) 32 33 def read.decoded( 34 pair: File & Result<&1, &1, U32 & String, Bytes.Bytes> 35 ) -> IO(Result<&1, &1, Error, Manifest.ManifestValue>): 36 match pair: 37 case (file, Fail{error}): 38 do IO>: 39 _closed : Unit <- File.close(file) 40 read.host(error) 41 case (file, Done{bytes}): 42 do IO>: 43 _closed : Unit <- File.close(file) 44 read.result(Manifest.decode(bytes)) 45 46 def read.sized(file: File, size: Nat) -> IO(Result<&1, &1, Error, Manifest.ManifestValue>): 47 match size: 48 case 56n: 49 do IO>: 50 pair : File & Result<&1, &1, U32 & String, Bytes.Bytes> <- Fs.read_at(file, 0, 56) 51 read.decoded(pair) 52 case 63n: 53 do IO>: 54 pair : File & Result<&1, &1, U32 & String, Bytes.Bytes> <- Fs.read_at(file, 0, 63) 55 read.decoded(pair) 56 case 60n: 57 do IO>: 58 pair : File & Result<&1, &1, U32 & String, Bytes.Bytes> <- Fs.read_at(file, 0, 60) 59 read.decoded(pair) 60 case _: 61 do IO>: 62 _closed : Unit <- File.close(file) 63 read.result(Fail{Manifest.Malformed{}}) 64 65 def read.size.result( 66 file: File, 67 result: Result<&1, &1, U32 & String, Nat> 68 ) -> IO(Result<&1, &1, Error, Manifest.ManifestValue>): 69 match result: 70 case Done{size}: read.sized(file, size) 71 case Fail{error}: 72 do IO>: 73 _closed : Unit <- File.close(file) 74 read.host(error) 75 76 def read.path.opened( 77 path: String, 78 opened: Result<&1, &1, U32 & String, File> 79 ) -> IO(Result<&1, &1, Error, Manifest.ManifestValue>): 80 match opened: 81 case Fail{error}: 82 read.host(error) 83 case Done{file}: 84 do IO>: 85 size : Result<&1, &1, U32 & String, Nat> <- Fs.file_size(path) 86 read.size.result(file, size) 87 88 # Reads and validates one bounded generation-four or generation-five manifest. 89 def read( 90 +path: String 91 ) -> IO(Result<&1, &1, Error, Manifest.ManifestValue>): 92 do IO>: 93 opened : Result<&1, &1, U32 & String, File> <- File.open(path, "r") 94 read.path.opened(path, opened) 95 96 def write.directory.synced( 97 directory: String, 98 result: Result<&1, &1, U32 & String, Unit> 99 ) -> IO(Result<&1, &1, U32 & String, Unit>): 100 match result: 101 case Fail{error}: IO.pure(Result<&1, &1, U32 & String, Unit>, Fail{error}) 102 case Done{Unit{}}: Fs.fsync(directory) 103 104 def write.renamed( 105 +directory: String, 106 result: Result<&1, &1, U32 & String, Unit> 107 ) -> IO(Result<&1, &1, U32 & String, Unit>): 108 match result: 109 case Fail{error}: IO.pure(Result<&1, &1, U32 & String, Unit>, Fail{error}) 110 case Done{Unit{}}: 111 do IO>: 112 renamed : Result<&1, &1, U32 & String, Unit> <- Fs.rename(directory ++ "/MANIFEST.tmp", directory ++ "/MANIFEST") 113 write.directory.synced(directory, renamed) 114 115 def write.closed( 116 +directory: String, 117 result: Result<&1, &1, U32 & String, Unit> 118 ) -> IO(Result<&1, &1, U32 & String, Unit>): 119 match result: 120 case Fail{error}: IO.pure(Result<&1, &1, U32 & String, Unit>, Fail{error}) 121 case Done{Unit{}}: 122 do IO>: 123 synced : Result<&1, &1, U32 & String, Unit> <- Fs.fsync(directory ++ "/MANIFEST.tmp") 124 write.renamed(directory, synced) 125 126 def write.file.synced( 127 directory: String, 128 result: File & Result<&1, &1, U32 & String, Unit> 129 ) -> IO(Result<&1, &1, U32 & String, Unit>): 130 match result: 131 case (file, result): 132 do IO>: 133 _closed : Unit <- File.close(file) 134 write.closed(directory, result) 135 136 def write.bytes( 137 directory: String, 138 bytes: Bytes.Bytes, 139 opened: Result<&1, &1, U32 & String, File> 140 ) -> IO(Result<&1, &1, U32 & String, Unit>): 141 match opened: 142 case Fail{error}: IO.pure(Result<&1, &1, U32 & String, Unit>, Fail{error}) 143 case Done{file}: 144 do IO>: 145 result : File & Result<&1, &1, U32 & String, Unit> <- Fs.write_bytes(file, bytes) 146 write.file.synced(directory, result) 147 148 # Publishes a manifest through a synced temporary and atomic rename. 149 def write_atomic( 150 +directory: String, 151 value: Manifest.ManifestValue 152 ) -> IO(Result<&1, &1, U32 & String, Unit>): 153 do IO>: 154 opened : Result<&1, &1, U32 & String, File> <- File.open(directory ++ "/MANIFEST.tmp", "w") 155 write.bytes(directory, Manifest.encode(value), opened)