fs.ha (10102B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use errors; 5 use io; 6 use path; 7 use time; 8 9 // Closes a filesystem. The fs cannot be used after this function is called. 10 export fn close(fs: *fs) void = { 11 match (fs.close) { 12 case null => void; 13 case let f: *closefunc => 14 f(fs); 15 }; 16 }; 17 18 // Opens a file. 19 // 20 // [[flag::CREATE]] isn't very useful with this function, since the new file's 21 // mode is set to zero. For this use-case, use [[create]] instead. 22 export fn open( 23 fs: *fs, 24 path: str, 25 flags: flag = flag::RDONLY, 26 ) (io::handle | error) = { 27 match (fs.open) { 28 case null => 29 return errors::unsupported; 30 case let f: *openfunc => 31 return f(fs, path, flags); 32 }; 33 }; 34 35 // Opens a file, as an [[io::file]]. This file will be backed by an open file 36 // handle on the host operating system, which may not be possible with all 37 // filesystem implementations (such cases will return [[io::unsupported]]). 38 // 39 // [[flag::CREATE]] isn't very useful with this function, since the new file's 40 // mode is set to zero. For this use-case, use [[create_file]] instead. 41 export fn open_file( 42 fs: *fs, 43 path: str, 44 flags: flag = flag::RDONLY, 45 ) (io::file | error) = { 46 match (fs.openfile) { 47 case null => 48 return errors::unsupported; 49 case let f: *openfilefunc => 50 return f(fs, path, flags); 51 }; 52 }; 53 54 // Creates a new file with the given mode if it doesn't already exist, and opens 55 // it for writing. 56 export fn create( 57 fs: *fs, 58 path: str, 59 mode: mode, 60 flags: flag = flag::WRONLY | flag::TRUNC, 61 ) (io::handle | error) = { 62 match (fs.create) { 63 case null => 64 return errors::unsupported; 65 case let f: *createfunc => 66 return f(fs, path, mode, flags); 67 }; 68 }; 69 70 // Creates a new file with the given mode if it doesn't already exist, and opens 71 // it as an [[io::file]] for writing. This file will be backed by an open file 72 // handle on the host operating system, which may not be possible with all 73 // filesystem implementations (such cases will return [[io::unsupported]]). 74 export fn create_file( 75 fs: *fs, 76 path: str, 77 mode: mode, 78 flags: flag = flag::WRONLY | flag::TRUNC, 79 ) (io::file | error) = { 80 match (fs.createfile) { 81 case null => 82 return errors::unsupported; 83 case let f: *createfilefunc => 84 return f(fs, path, mode, flags); 85 }; 86 }; 87 88 // Removes a file. 89 export fn remove(fs: *fs, path: str) (void | error) = { 90 match (fs.remove) { 91 case null => 92 return errors::unsupported; 93 case let f: *removefunc => 94 return f(fs, path); 95 }; 96 }; 97 98 // Renames a file. This generally only works if the source and destination path 99 // are both on the same filesystem. See [[move]] for an implementation which 100 // falls back on a "copy & remove" procedure in this situation. 101 export fn rename(fs: *fs, oldpath: str, newpath: str) (void | error) = { 102 match (fs.rename) { 103 case null => 104 return errors::unsupported; 105 case let f: *renamefunc => 106 return f(fs, oldpath, newpath); 107 }; 108 }; 109 110 // Moves a file. This will use [[rename]] if possible, and will fall back to 111 // copy and remove if necessary. 112 export fn move(fs: *fs, oldpath: str, newpath: str) (void | error) = { 113 match (rename(fs, oldpath, newpath)) { 114 case let err: error => 115 match (err) { 116 case (cannotrename | errors::unsupported) => void; // Fallback 117 case => 118 return err; 119 }; 120 case void => 121 return; // Success 122 }; 123 // TODO: 124 // - Move non-regular files 125 let st = stat(fs, oldpath)?; 126 assert(isfile(st.mode), "TODO: move non-regular files"); 127 let old = open(fs, oldpath)?; 128 let new = match (create(fs, newpath, st.mode)) { 129 case let h: io::handle => 130 yield h; 131 case let err: error => 132 io::close(old): void; 133 return err; 134 }; 135 match (io::copy(new, old)) { 136 case let err: io::error => 137 io::close(new): void; 138 io::close(old): void; 139 remove(fs, newpath)?; 140 return err; 141 case size => void; 142 }; 143 io::close(new)?; 144 io::close(old)?; 145 remove(fs, oldpath)?; 146 }; 147 148 // Returns an iterator for a path, which yields the contents of a directory. 149 // Pass empty string to yield from the root. The order in which entries are 150 // returned is undefined. The return value must be finished with [[finish]]. 151 export fn iter(fs: *fs, path: str) (*iterator | error) = { 152 match (fs.iter) { 153 case null => 154 return errors::unsupported; 155 case let f: *iterfunc => 156 return f(fs, path); 157 }; 158 }; 159 160 // Frees state associated with an [[iterator]]. 161 export fn finish(iter: *iterator) void = { 162 match (iter.finish) { 163 case null => void; 164 case let f: *finishfunc => 165 return f(iter); 166 }; 167 }; 168 169 // Obtains information about a file or directory. If the target is a symlink, 170 // information is returned about the link, not its target. 171 export fn stat(fs: *fs, path: str) (filestat | error) = { 172 match (fs.stat) { 173 case null => 174 return errors::unsupported; 175 case let f: *statfunc => 176 return f(fs, path); 177 }; 178 }; 179 180 // Obtains information about an [[io::file]]. 181 export fn fstat(fs: *fs, fd: io::file) (filestat | error) = { 182 match (fs.fstat) { 183 case null => 184 return errors::unsupported; 185 case let f: *fstatfunc => 186 return f(fs, fd); 187 }; 188 }; 189 190 // Returns true if a node exists at the given path, or false if not. 191 // 192 // Note that testing for file existence before using the file can often lead to 193 // race conditions. If possible, prefer to simply attempt to use the file (e.g. 194 // via "open"), and handle the resulting error should the file not exist. 195 export fn exists(fs: *fs, path: str) bool = { 196 match (stat(fs, path)) { 197 case filestat => 198 return true; 199 case error => 200 return false; 201 }; 202 }; 203 204 // Returns the path referred to by a symbolic link. The return value is 205 // statically allocated and will be overwritten on subsequent calls. 206 export fn readlink(fs: *fs, path: str) (str | error) = { 207 match (fs.readlink) { 208 case null => 209 return errors::unsupported; 210 case let f: *readlinkfunc => 211 return f(fs, path); 212 }; 213 }; 214 215 // Creates a directory. 216 export fn mkdir(fs: *fs, path: str, mode: mode) (void | error) = { 217 match (fs.mkdir) { 218 case null => 219 return errors::unsupported; 220 case let f: *mkdirfunc => 221 return f(fs, path, mode); 222 }; 223 }; 224 225 // Makes a directory, and all non-extant directories in its path. 226 export fn mkdirs(fs: *fs, path: str, mode: mode) (void | error) = { 227 let parent = path::dirname(path); 228 if (path != parent) { 229 match (mkdirs(fs, parent, mode)) { 230 case errors::exists => void; 231 case void => void; 232 case let err: error => 233 return err; 234 }; 235 }; 236 match (mkdir(fs, path, mode)) { 237 case errors::exists => void; 238 case void => void; 239 case let err: error => 240 return err; 241 }; 242 }; 243 244 // Removes a directory. The target directory must be empty; see [[rmdirall]] to 245 // remove its contents as well. 246 export fn rmdir(fs: *fs, path: str) (void | error) = { 247 if (path == "") { 248 return errors::invalid; 249 }; 250 match (fs.rmdir) { 251 case null => 252 return errors::unsupported; 253 case let f: *rmdirfunc => 254 return f(fs, path); 255 }; 256 }; 257 258 // Changes mode flags on a file or directory. 259 export fn chmod(fs: *fs, path: str, mode: mode) (void | error) = { 260 match (fs.chmod) { 261 case null => 262 return errors::unsupported; 263 case let f: *chmodfunc => 264 return f(fs, path, mode); 265 }; 266 }; 267 268 // Changes mode flags on a [[io::file]]. 269 export fn fchmod(fs: *fs, fd: io::file, mode: mode) (void | error) = { 270 match (fs.fchmod) { 271 case null => 272 return errors::unsupported; 273 case let f: *fchmodfunc => 274 return f(fd, mode); 275 }; 276 }; 277 278 // Changes ownership of a file. 279 export fn chown(fs: *fs, path: str, uid: uint, gid: uint) (void | error) = { 280 match (fs.chown) { 281 case null => 282 return errors::unsupported; 283 case let f: *chownfunc => 284 return f(fs, path, uid, gid); 285 }; 286 }; 287 288 // Changes ownership of a [[io::file]]. 289 export fn fchown(fs: *fs, fd: io::file, uid: uint, gid: uint) (void | error) = { 290 match (fs.fchown) { 291 case null => 292 return errors::unsupported; 293 case let f: *fchownfunc => 294 return f(fd, uid, gid); 295 }; 296 }; 297 298 // Changes the access and modification time of a file. A void value will leave 299 // the corresponding time unchanged. 300 export fn chtimes( 301 fs: *fs, 302 path: str, 303 atime: (time::instant | void), 304 mtime: (time::instant | void) 305 ) (void | error) = { 306 match (fs.chtimes) { 307 case null => 308 return errors::unsupported; 309 case let f: *chtimesfunc => 310 return f(fs, path, atime, mtime); 311 }; 312 }; 313 314 // Changes the access and modification time of an [[io::file]]. A void value 315 // will leave the corresponding time unchanged. 316 export fn fchtimes( 317 fs: *fs, 318 fd: io::file, 319 atime: (time::instant | void), 320 mtime: (time::instant | void) 321 ) (void | error) = { 322 match (fs.fchtimes) { 323 case null => 324 return errors::unsupported; 325 case let f: *fchtimesfunc => 326 return f(fd, atime, mtime); 327 }; 328 }; 329 330 // Resolves a path to its absolute, normalized value. Relative paths will be 331 // rooted (if supported by the fs implementation), and "." and ".." components 332 // will be reduced. This function does not follow symlinks; see [[realpath]] if 333 // you need this behavior. The return value is statically allocated; use 334 // [[strings::dup]] to extend its lifetime. 335 export fn resolve(fs: *fs, path: str) str = { 336 match (fs.resolve) { 337 case null => void; 338 case let f: *resolvefunc => 339 return f(fs, path); 340 }; 341 static let p = path::path { ... }; 342 path::set(&p, path)!; 343 return path::string(&p); 344 }; 345 346 // Creates a new (hard) link at 'new' for the file at 'old'. 347 export fn link(fs: *fs, old: str, new: str) (void | error) = { 348 match (fs.link) { 349 case null => 350 return errors::unsupported; 351 case let f: *linkfunc => 352 return f(fs, old, new); 353 }; 354 }; 355 356 // Creates a new symbolic link at 'path' which points to 'target'. 357 export fn symlink(fs: *fs, target: str, path: str) (void | error) = { 358 match (fs.symlink) { 359 case null => 360 return errors::unsupported; 361 case let f: *symlinkfunc => 362 return f(fs, target, path); 363 }; 364 }; 365 366 // Returns the next directory entry from an iterator, or done if none remain. 367 // '.' and '..' are skipped. It is a programming error to call this again after 368 // it has returned void. Calling this again after an error is safe. The list is 369 // not guaranteed to be complete when an error has been returned. The file stat 370 // returned may only have the type bits set on the file mode; callers should 371 // call [[stat]] to obtain the detailed file mode. 372 export fn next(iter: *iterator) (dirent | done | error) = iter.next(iter);