1 import Base 2 import ../DbLock.bend as DbLock 3 import ../DurableError.bend as DurableError 4 import ../Fs.bend as Fs 5 import ../StorageBytes.bend as StorageBytes 6 import ./FormatInspect.bend as FormatInspect 7 import bend-kit-bytes@0.3.2.0/bytes.bend as Bytes 8 9 # Separates lock ownership failures from read-only filesystem failures. 10 type Error is Data: 11 Lock{error: DurableError.Error} 12 Host{code: U32, message: String} 13 ResourceLimit{} 14 15 def identity.unknown() -> FormatInspect.Identity: 16 FormatInspect.Identity{"unknown", "unknown", 0} 17 18 def inspection.resource_limit() -> FormatInspect.Inspection: 19 FormatInspect.Inspection{identity.unknown(), FormatInspect.ResourceLimit{}} 20 21 def read.failed.released( 22 error: U32 & String, 23 result: Result<&1, &1, DurableError.Error, Unit> 24 ) -> Result<&1, &1, Error, FormatInspect.Inspection>: 25 match error: 26 case (code, message): 27 match result: 28 case Fail{lock_error}: Fail{Lock{lock_error}} 29 case Done{Unit{}}: Fail{Host{code, message}} 30 31 def read.success.released( 32 bytes: Bytes.Bytes, 33 result: Result<&1, &1, DurableError.Error, Unit> 34 ) -> Result<&1, &1, Error, FormatInspect.Inspection>: 35 match result: 36 case Fail{error}: Fail{Lock{error}} 37 case Done{Unit{}}: 38 Done{FormatInspect.inspect_manifest_bytes(bytes)} 39 40 def read.result( 41 +path: String, 42 lock: DbLock.Lock, 43 file_result: File & Result<&1, &1, U32 & String, Bytes.Bytes> 44 ) -> IO(Result<&1, &1, Error, FormatInspect.Inspection>): 45 match file_result: 46 case (file, Fail{error}): 47 do IO>: 48 _closed : Unit <- File.close(file) 49 released : Result<&1, &1, DurableError.Error, Unit> <- 50 DbLock.release(lock, "inspect", path) 51 return read.failed.released(error, released) 52 case (file, Done{bytes}): 53 do IO>: 54 _closed : Unit <- File.close(file) 55 released : Result<&1, &1, DurableError.Error, Unit> <- 56 DbLock.release(lock, "inspect", path) 57 return read.success.released(bytes, released) 58 59 def read.opened( 60 +path: String, 61 lock: DbLock.Lock, 62 size: Nat, 63 opened: Result<&1, &1, U32 & String, File> 64 ) -> IO(Result<&1, &1, Error, FormatInspect.Inspection>): 65 match opened: 66 case Fail{error}: 67 do IO>: 68 released : Result<&1, &1, DurableError.Error, Unit> <- 69 DbLock.release(lock, "inspect", path) 70 return read.failed.released(error, released) 71 case Done{file}: 72 do IO>: 73 result : File & Result<&1, &1, U32 & String, Bytes.Bytes> <- 74 Fs.read_at(file, 0, U32.from_nat(size)) 75 read.result(path, lock, result) 76 77 def read.limit.released( 78 result: Result<&1, &1, DurableError.Error, Unit> 79 ) -> IO(Result<&1, &1, Error, FormatInspect.Inspection>): 80 match result: 81 case Fail{error}: 82 IO.pure(Result<&1, &1, Error, FormatInspect.Inspection>, Fail{Lock{error}}) 83 case Done{Unit{}}: 84 IO.pure(Result<&1, &1, Error, FormatInspect.Inspection>, 85 Done{inspection.resource_limit()}) 86 87 def read.size.checked( 88 +path: String, 89 lock: DbLock.Lock, 90 +size: Nat, 91 within_limit: Bool 92 ) -> IO(Result<&1, &1, Error, FormatInspect.Inspection>): 93 match within_limit: 94 case True{}: 95 do IO>: 96 opened : Result<&1, &1, U32 & String, File> <- 97 File.open(path ++ "/MANIFEST", "r") 98 read.opened(path, lock, size, opened) 99 case False{}: 100 do IO>: 101 released : Result<&1, &1, DurableError.Error, Unit> <- 102 DbLock.release(lock, "inspect", path) 103 read.limit.released(released) 104 105 def read.size( 106 +path: String, 107 lock: DbLock.Lock, 108 result: Result<&1, &1, U32 & String, Nat> 109 ) -> IO(Result<&1, &1, Error, FormatInspect.Inspection>): 110 match result: 111 case Fail{error}: 112 do IO>: 113 released : Result<&1, &1, DurableError.Error, Unit> <- 114 DbLock.release(lock, "inspect", path) 115 return read.failed.released(error, released) 116 case Done{+size}: 117 read.size.checked(path, lock, size, 118 Nat.is_le(size, U32.to_nat(StorageBytes.MAX_MANIFEST_BYTES()))) 119 120 def inspect.locked( 121 +path: String, 122 result: Result<&1, &1, DurableError.Error, DbLock.Lock> 123 ) -> IO(Result<&1, &1, Error, FormatInspect.Inspection>): 124 match result: 125 case Fail{error}: 126 IO.pure(Result<&1, &1, Error, FormatInspect.Inspection>, Fail{Lock{error}}) 127 case Done{lock}: 128 do IO>: 129 size : Result<&1, &1, U32 & String, Nat> <- Fs.file_size(path ++ "/MANIFEST") 130 read.size(path, lock, size) 131 132 def inspect.lock.error( 133 error: U32 & String 134 ) -> IO(Result<&1, &1, Error, FormatInspect.Inspection>): 135 match error: 136 case (code, message): 137 IO.pure(Result<&1, &1, Error, FormatInspect.Inspection>, 138 Fail{Host{code, message}}) 139 140 def inspect.lock.present( 141 +path: String, 142 result: Result<&1, &1, U32 & String, Bool> 143 ) -> IO(Result<&1, &1, Error, FormatInspect.Inspection>): 144 match result: 145 case Fail{error}: 146 inspect.lock.error(error) 147 case Done{False{}}: 148 IO.pure(Result<&1, &1, Error, FormatInspect.Inspection>, 149 Fail{Host{2, "database LOCK file is missing"}}) 150 case Done{True{}}: 151 do IO>: 152 acquired : Result<&1, &1, DurableError.Error, DbLock.Lock> <- 153 DbLock.acquire(path, "inspect") 154 inspect.locked(path, acquired) 155 156 def inspect.canonical( 157 result: Result<&1, &1, U32 & String, String> 158 ) -> IO(Result<&1, &1, Error, FormatInspect.Inspection>): 159 match result: 160 case Fail{error}: 161 match error: 162 case (code, message): 163 IO.pure(Result<&1, &1, Error, FormatInspect.Inspection>, 164 Fail{Host{code, message}}) 165 case Done{+path}: 166 do IO>: 167 exists : Result<&1, &1, U32 & String, Bool> <- Fs.exists(path ++ "/LOCK") 168 inspect.lock.present(path, exists) 169 170 # Reads and validates a database manifest under an existing exclusive lock. 171 def inspect( 172 +path: String 173 ) -> 174 IO(Result<&1, &1, Error, FormatInspect.Inspection>): 175 do IO>: 176 canonical : Result<&1, &1, U32 & String, String> <- Fs.canonical_path(path) 177 inspect.canonical(canonical)