hare

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

fs.ha (8526B)


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