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