hare

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

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