hare

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

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