fs.ha (8527B)
1 // License: MPL-2.0 2 // (c) 2021 Alexey Yerin <yyp@disroot.org> 3 // (c) 2021-2022 Bor Grošelj Simić <bor.groseljsimic@telemach.net> 4 // (c) 2021-2022 Drew DeVault <sir@cmpwn.com> 5 // (c) 2021 Ember Sawady <ecs@d2evs.net> 6 // (c) 2021 Thomas Bracht Laumann Jespersen <t@laumann.xyz> 7 // (c) 2021 Umar Getagazov <umar@handlerug.me> 8 // (c) 2022 Jon Eskin <eskinjp@gmail.com> 9 use errors; 10 use io; 11 use path; 12 13 // Closes a filesystem. The fs cannot be used after this function is called. 14 export fn close(fs: *fs) void = { 15 match (fs.close) { 16 case null => void; 17 case let f: *closefunc => 18 f(fs); 19 }; 20 }; 21 22 // Opens a file. If no flags are provided, the default read/write mode is 23 // RDONLY. 24 export fn open(fs: *fs, path: str, flags: flags...) (io::handle | error) = { 25 match (fs.open) { 26 case null => 27 return errors::unsupported; 28 case let f: *openfunc => 29 return f(fs, path, flags...); 30 }; 31 }; 32 33 // Opens a file, as an [[io::file]]. This file will be backed by an open file 34 // handle on the host operating system, which may not be possible with all 35 // filesystem implementations (such cases will return [[io::unsupported]]). 36 // 37 // If no flags are provided, the default read/write mode is RDONLY. 38 export fn open_file(fs: *fs, path: str, flags: flags...) (io::file | error) = { 39 match (fs.openfile) { 40 case null => 41 return errors::unsupported; 42 case let f: *openfilefunc => 43 return f(fs, path, flags...); 44 }; 45 }; 46 47 // Creates a new file and opens it for writing. If no flags are provided, the 48 // default read/write mode is WRONLY. 49 export fn create( 50 fs: *fs, 51 path: str, 52 mode: mode, 53 flags: flags... 54 ) (io::handle | error) = { 55 match (fs.create) { 56 case null => 57 return errors::unsupported; 58 case let f: *createfunc => 59 return f(fs, path, mode, flags...); 60 }; 61 }; 62 63 // Creates a new file, as an [[io::file]], and opens it for writing. This file 64 // will be backed by an open file handle on the host operating system, which may 65 // not be possible with all filesystem implementations (such cases will return 66 // [[io::unsupported]]). 67 // 68 // If no flags are provided, the default read/write mode is WRONLY. 69 export fn create_file( 70 fs: *fs, 71 path: str, 72 mode: mode, 73 flags: flags... 74 ) (io::file | error) = { 75 match (fs.createfile) { 76 case null => 77 return errors::unsupported; 78 case let f: *createfilefunc => 79 return f(fs, path, mode, flags...); 80 }; 81 }; 82 83 // Removes a file. 84 export fn remove(fs: *fs, path: str) (void | error) = { 85 match (fs.remove) { 86 case null => 87 return errors::unsupported; 88 case let f: *removefunc => 89 return f(fs, path); 90 }; 91 }; 92 93 // Renames a file. This generally only works if the source and destination path 94 // are both on the same filesystem. See [[move]] for an implementation which 95 // falls back on a "copy & remove" procedure in this situation. 96 export fn rename(fs: *fs, oldpath: str, newpath: str) (void | error) = { 97 match (fs.rename) { 98 case null => 99 return errors::unsupported; 100 case let f: *renamefunc => 101 return f(fs, oldpath, newpath); 102 }; 103 }; 104 105 // Moves a file. This will use [[rename]] if possible, and will fall back to 106 // copy and remove if necessary. 107 export fn move(fs: *fs, oldpath: str, newpath: str) (void | error) = { 108 match (rename(fs, oldpath, newpath)) { 109 case (cannotrename | errors::unsupported) => void; // Fallback 110 case let err: error => 111 return err; 112 case void => 113 return; // Success 114 }; 115 // TODO: 116 // - Move non-regular files 117 let st = stat(fs, oldpath)?; 118 assert(isfile(st.mode), "TODO: move non-regular files"); 119 let old = open(fs, oldpath)?; 120 let new = match (create(fs, newpath, st.mode)) { 121 case let h: io::handle => 122 yield h; 123 case let err: error => 124 io::close(old): void; 125 return err; 126 }; 127 match (io::copy(new, old)) { 128 case let err: io::error => 129 io::close(new): void; 130 io::close(old): void; 131 remove(fs, newpath)?; 132 return err; 133 case size => void; 134 }; 135 io::close(new)?; 136 io::close(old)?; 137 remove(fs, oldpath)?; 138 }; 139 140 // Returns an iterator for a path, which yields the contents of a directory. 141 // Pass empty string to yield from the root. The order in which entries are 142 // returned is undefined. The return value must be finished with [[fs::finish]]. 143 export fn iter(fs: *fs, path: str) (*iterator | error) = { 144 match (fs.iter) { 145 case null => 146 return errors::unsupported; 147 case let f: *iterfunc => 148 return f(fs, path); 149 }; 150 }; 151 152 // Frees state associated with an [[iterator]]. 153 export fn finish(iter: *iterator) void = { 154 match (iter.finish) { 155 case null => 156 yield; 157 case let f: *finishfunc => 158 return f(iter); 159 }; 160 }; 161 162 // Obtains information about a file or directory. If the target is a symlink, 163 // information is returned about the link, not its target. 164 export fn stat(fs: *fs, path: str) (filestat | error) = { 165 match (fs.stat) { 166 case null => 167 return errors::unsupported; 168 case let f: *statfunc => 169 return f(fs, path); 170 }; 171 }; 172 173 // Returns true if a node exists at the given path, or false if not. 174 // 175 // Note that testing for file existence before using the file can often lead to 176 // race conditions. If possible, prefer to simply attempt to use the file (e.g. 177 // via "open"), and handle the resulting error should the file not exist. 178 export fn exists(fs: *fs, path: str) bool = { 179 match (stat(fs, path)) { 180 case filestat => 181 return true; 182 case error => 183 return false; 184 }; 185 }; 186 187 // Returns the path referred to by a symbolic link. The return value is 188 // statically allocated and will be overwritten on subsequent calls. 189 export fn readlink(fs: *fs, path: str) (str | error) = { 190 match (fs.readlink) { 191 case null => 192 return errors::unsupported; 193 case let f: *readlinkfunc => 194 return f(fs, path); 195 }; 196 }; 197 198 // Creates a directory. 199 export fn mkdir(fs: *fs, path: str, mode: mode) (void | error) = { 200 match (fs.mkdir) { 201 case null => 202 return errors::unsupported; 203 case let f: *mkdirfunc => 204 return f(fs, path, mode); 205 }; 206 }; 207 208 // Makes a directory, and all non-extant directories in its path. 209 export fn mkdirs(fs: *fs, path: str, mode: mode) (void | error) = { 210 let parent = path::dirname(path); 211 if (path != parent) { 212 match (mkdirs(fs, parent, mode)) { 213 case errors::exists => void; 214 case void => void; 215 case let err: error => 216 return err; 217 }; 218 }; 219 match (mkdir(fs, path, mode)) { 220 case errors::exists => void; 221 case void => void; 222 case let err: error => 223 return err; 224 }; 225 }; 226 227 // Removes a directory. The target directory must be empty; see [[rmdirall]] to 228 // remove its contents as well. 229 export fn rmdir(fs: *fs, path: str) (void | error) = { 230 if (path == "") { 231 return errors::invalid; 232 }; 233 match (fs.rmdir) { 234 case null => 235 return errors::unsupported; 236 case let f: *rmdirfunc => 237 return f(fs, path); 238 }; 239 }; 240 241 // Changes mode flags on a file or directory. 242 export fn chmod(fs: *fs, path: str, mode: mode) (void | error) = { 243 match (fs.chmod) { 244 case null => 245 return errors::unsupported; 246 case let f: *chmodfunc => 247 return f(fs, path, mode); 248 }; 249 }; 250 251 // Changes ownership of a file. 252 export fn chown(fs: *fs, path: str, uid: uint, gid: uint) (void | error) = { 253 match (fs.chown) { 254 case null => 255 return errors::unsupported; 256 case let f: *chownfunc => 257 return f(fs, path, uid, gid); 258 }; 259 }; 260 261 // Resolves a path to its absolute, normalized value. Relative paths will be 262 // rooted (if supported by the fs implementation), and "." and ".." components 263 // will be reduced. This function does not follow symlinks; see [[realpath]] if 264 // you need this behavior. The return value is statically allocated; use 265 // [[strings::dup]] to extend its lifetime. 266 export fn resolve(fs: *fs, path: str) str = { 267 match (fs.resolve) { 268 case null => void; 269 case let f: *resolvefunc => 270 return f(fs, path); 271 }; 272 static let buf = path::buffer { ... }; 273 path::set(&buf, path)!; 274 return path::string(&buf); 275 }; 276 277 // Creates a new (hard) link at 'new' for the file at 'old'. 278 export fn link(fs: *fs, old: str, new: str) (void | error) = { 279 match (fs.link) { 280 case null => 281 return errors::unsupported; 282 case let f: *linkfunc => 283 return f(fs, old, new); 284 }; 285 }; 286 287 // Creates a new symbolic link at 'path' which points to 'target'. 288 export fn symlink(fs: *fs, target: str, path: str) (void | error) = { 289 match (fs.symlink) { 290 case null => 291 return errors::unsupported; 292 case let f: *symlinkfunc => 293 return f(fs, target, path); 294 }; 295 }; 296 297 // Returns the next directory entry from an iterator, or void if none remain. 298 // It is a programming error to call this again after it has returned void. The 299 // file stat returned may only have the type bits set on the file mode; callers 300 // should call [[stat]] to obtain the detailed file mode. 301 export fn next(iter: *iterator) (dirent | void) = iter.next(iter);