fs.ha (10077B)
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 (cannotrename | errors::unsupported) => void; // Fallback 115 case let err: error => 116 return err; 117 case void => 118 return; // Success 119 }; 120 // TODO: 121 // - Move non-regular files 122 let st = stat(fs, oldpath)?; 123 assert(isfile(st.mode), "TODO: move non-regular files"); 124 let old = open(fs, oldpath)?; 125 let new = match (create(fs, newpath, st.mode)) { 126 case let h: io::handle => 127 yield h; 128 case let err: error => 129 io::close(old): void; 130 return err; 131 }; 132 match (io::copy(new, old)) { 133 case let err: io::error => 134 io::close(new): void; 135 io::close(old): void; 136 remove(fs, newpath)?; 137 return err; 138 case size => void; 139 }; 140 io::close(new)?; 141 io::close(old)?; 142 remove(fs, oldpath)?; 143 }; 144 145 // Returns an iterator for a path, which yields the contents of a directory. 146 // Pass empty string to yield from the root. The order in which entries are 147 // returned is undefined. The return value must be finished with [[finish]]. 148 export fn iter(fs: *fs, path: str) (*iterator | error) = { 149 match (fs.iter) { 150 case null => 151 return errors::unsupported; 152 case let f: *iterfunc => 153 return f(fs, path); 154 }; 155 }; 156 157 // Frees state associated with an [[iterator]]. 158 export fn finish(iter: *iterator) void = { 159 match (iter.finish) { 160 case null => void; 161 case let f: *finishfunc => 162 return f(iter); 163 }; 164 }; 165 166 // Obtains information about a file or directory. If the target is a symlink, 167 // information is returned about the link, not its target. 168 export fn stat(fs: *fs, path: str) (filestat | error) = { 169 match (fs.stat) { 170 case null => 171 return errors::unsupported; 172 case let f: *statfunc => 173 return f(fs, path); 174 }; 175 }; 176 177 // Obtains information about an [[io::file]]. 178 export fn fstat(fs: *fs, fd: io::file) (filestat | error) = { 179 match (fs.fstat) { 180 case null => 181 return errors::unsupported; 182 case let f: *fstatfunc => 183 return f(fs, fd); 184 }; 185 }; 186 187 // Returns true if a node exists at the given path, or false if not. 188 // 189 // Note that testing for file existence before using the file can often lead to 190 // race conditions. If possible, prefer to simply attempt to use the file (e.g. 191 // via "open"), and handle the resulting error should the file not exist. 192 export fn exists(fs: *fs, path: str) bool = { 193 match (stat(fs, path)) { 194 case filestat => 195 return true; 196 case error => 197 return false; 198 }; 199 }; 200 201 // Returns the path referred to by a symbolic link. The return value is 202 // statically allocated and will be overwritten on subsequent calls. 203 export fn readlink(fs: *fs, path: str) (str | error) = { 204 match (fs.readlink) { 205 case null => 206 return errors::unsupported; 207 case let f: *readlinkfunc => 208 return f(fs, path); 209 }; 210 }; 211 212 // Creates a directory. 213 export fn mkdir(fs: *fs, path: str, mode: mode) (void | error) = { 214 match (fs.mkdir) { 215 case null => 216 return errors::unsupported; 217 case let f: *mkdirfunc => 218 return f(fs, path, mode); 219 }; 220 }; 221 222 // Makes a directory, and all non-extant directories in its path. 223 export fn mkdirs(fs: *fs, path: str, mode: mode) (void | error) = { 224 let parent = path::dirname(path); 225 if (path != parent) { 226 match (mkdirs(fs, parent, mode)) { 227 case errors::exists => void; 228 case void => void; 229 case let err: error => 230 return err; 231 }; 232 }; 233 match (mkdir(fs, path, mode)) { 234 case errors::exists => void; 235 case void => void; 236 case let err: error => 237 return err; 238 }; 239 }; 240 241 // Removes a directory. The target directory must be empty; see [[rmdirall]] to 242 // remove its contents as well. 243 export fn rmdir(fs: *fs, path: str) (void | error) = { 244 if (path == "") { 245 return errors::invalid; 246 }; 247 match (fs.rmdir) { 248 case null => 249 return errors::unsupported; 250 case let f: *rmdirfunc => 251 return f(fs, path); 252 }; 253 }; 254 255 // Changes mode flags on a file or directory. 256 export fn chmod(fs: *fs, path: str, mode: mode) (void | error) = { 257 match (fs.chmod) { 258 case null => 259 return errors::unsupported; 260 case let f: *chmodfunc => 261 return f(fs, path, mode); 262 }; 263 }; 264 265 // Changes mode flags on a [[io::file]]. 266 export fn fchmod(fs: *fs, fd: io::file, mode: mode) (void | error) = { 267 match (fs.fchmod) { 268 case null => 269 return errors::unsupported; 270 case let f: *fchmodfunc => 271 return f(fd, mode); 272 }; 273 }; 274 275 // Changes ownership of a file. 276 export fn chown(fs: *fs, path: str, uid: uint, gid: uint) (void | error) = { 277 match (fs.chown) { 278 case null => 279 return errors::unsupported; 280 case let f: *chownfunc => 281 return f(fs, path, uid, gid); 282 }; 283 }; 284 285 // Changes ownership of a [[io::file]]. 286 export fn fchown(fs: *fs, fd: io::file, uid: uint, gid: uint) (void | error) = { 287 match (fs.fchown) { 288 case null => 289 return errors::unsupported; 290 case let f: *fchownfunc => 291 return f(fd, uid, gid); 292 }; 293 }; 294 295 // Changes the access and modification time of a file. A void value will leave 296 // the corresponding time unchanged. 297 export fn chtimes( 298 fs: *fs, 299 path: str, 300 atime: (time::instant | void), 301 mtime: (time::instant | void) 302 ) (void | error) = { 303 match (fs.chtimes) { 304 case null => 305 return errors::unsupported; 306 case let f: *chtimesfunc => 307 return f(fs, path, atime, mtime); 308 }; 309 }; 310 311 // Changes the access and modification time of an [[io::file]]. A void value 312 // will leave the corresponding time unchanged. 313 export fn fchtimes( 314 fs: *fs, 315 fd: io::file, 316 atime: (time::instant | void), 317 mtime: (time::instant | void) 318 ) (void | error) = { 319 match (fs.fchtimes) { 320 case null => 321 return errors::unsupported; 322 case let f: *fchtimesfunc => 323 return f(fd, atime, mtime); 324 }; 325 }; 326 327 // Resolves a path to its absolute, normalized value. Relative paths will be 328 // rooted (if supported by the fs implementation), and "." and ".." components 329 // will be reduced. This function does not follow symlinks; see [[realpath]] if 330 // you need this behavior. The return value is statically allocated; use 331 // [[strings::dup]] to extend its lifetime. 332 export fn resolve(fs: *fs, path: str) str = { 333 match (fs.resolve) { 334 case null => void; 335 case let f: *resolvefunc => 336 return f(fs, path); 337 }; 338 static let buf = path::buffer { ... }; 339 path::set(&buf, path)!; 340 return path::string(&buf); 341 }; 342 343 // Creates a new (hard) link at 'new' for the file at 'old'. 344 export fn link(fs: *fs, old: str, new: str) (void | error) = { 345 match (fs.link) { 346 case null => 347 return errors::unsupported; 348 case let f: *linkfunc => 349 return f(fs, old, new); 350 }; 351 }; 352 353 // Creates a new symbolic link at 'path' which points to 'target'. 354 export fn symlink(fs: *fs, target: str, path: str) (void | error) = { 355 match (fs.symlink) { 356 case null => 357 return errors::unsupported; 358 case let f: *symlinkfunc => 359 return f(fs, target, path); 360 }; 361 }; 362 363 // Returns the next directory entry from an iterator, or void if none remain. 364 // '.' and '..' are skipped. It is a programming error to call this again after 365 // it has returned void. Calling this again after an error is safe. The list is 366 // not guaranteed to be complete when an error has been returned. The file stat 367 // returned may only have the type bits set on the file mode; callers should 368 // call [[stat]] to obtain the detailed file mode. 369 export fn next(iter: *iterator) (dirent | done | error) = iter.next(iter);