hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

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);