hare

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

dirfdfs.ha (11176B)


      1 // SPDX-License-Identifier: MPL-2.0
      2 // (c) Hare authors <https://harelang.org>
      3 
      4 use encoding::utf8;
      5 use errors;
      6 use fs;
      7 use io;
      8 use path;
      9 use rt;
     10 use strings;
     11 use time;
     12 use types::c;
     13 
     14 type os_filesystem = struct {
     15 	fs: fs::fs,
     16 	dirfd: int,
     17 	getdents_bufsz: size,
     18 };
     19 
     20 // Opens a file descriptor as an [[fs::fs]]. This file descriptor must be a
     21 // directory file. The file will be closed when the fs is closed.
     22 export fn dirfdopen(fd: io::file) *fs::fs = {
     23 	let ofs = alloc(os_filesystem { ... });
     24 	let fs = static_dirfdopen(fd, ofs);
     25 	fs.close = &fs_close;
     26 	return fs;
     27 };
     28 
     29 fn static_dirfdopen(fd: io::file, filesystem: *os_filesystem) *fs::fs = {
     30 	*filesystem = os_filesystem {
     31 		fs = fs::fs {
     32 			open = &fs_open,
     33 			openfile = &fs_open_file,
     34 			create = &fs_create,
     35 			createfile = &fs_create_file,
     36 			remove = &fs_remove,
     37 			rename = &fs_rename,
     38 			iter = &fs_iter,
     39 			stat = &fs_stat,
     40 			readlink = &fs_readlink,
     41 			mkdir = &fs_mkdir,
     42 			rmdir = &fs_rmdir,
     43 			chmod = &fs_chmod,
     44 			chown = &fs_chown,
     45 			chtimes = &fs_chtimes,
     46 			fchtimes = &fs_fchtimes,
     47 			resolve = &fs_resolve,
     48 			...
     49 		},
     50 		dirfd = fd,
     51 		getdents_bufsz = 32768, // 32 KiB
     52 	};
     53 	return &filesystem.fs;
     54 };
     55 
     56 // Sets the buffer size to use with the getdents(2) system call, for use with
     57 // [[fs::iter]]. A larger buffer requires a larger runtime allocation, but can
     58 // scan large directories faster. The default buffer size is 32 KiB.
     59 //
     60 // This function is not portable.
     61 export fn dirfdfs_set_getdents_bufsz(fs: *fs::fs, sz: size) void = {
     62 	assert(fs.open == &fs_open);
     63 	let fs = fs: *os_filesystem;
     64 	fs.getdents_bufsz = sz;
     65 };
     66 
     67 // Returns an [[io::file]] for this filesystem. This function is not portable.
     68 export fn dirfile(fs: *fs::fs) io::file = {
     69 	assert(fs.open == &fs_open);
     70 	let fs = fs: *os_filesystem;
     71 	return fs.dirfd;
     72 };
     73 
     74 fn errno_to_fs(err: rt::errno) fs::error = {
     75 	switch (err) {
     76 	case rt::ENOENT =>
     77 		return errors::noentry;
     78 	case rt::EEXIST =>
     79 		return errors::exists;
     80 	case rt::EACCES =>
     81 		return errors::noaccess;
     82 	case rt::EBUSY =>
     83 		return errors::busy;
     84 	case rt::ENOTDIR =>
     85 		return fs::wrongtype;
     86 	case rt::EOPNOTSUPP, rt::ENOSYS =>
     87 		return errors::unsupported;
     88 	case rt::EXDEV =>
     89 		return fs::cannotrename;
     90 	case =>
     91 		return errors::errno(err);
     92 	};
     93 };
     94 
     95 fn _fs_open(
     96 	fs: *fs::fs,
     97 	path: str,
     98 	flags: int,
     99 	mode: uint,
    100 ) (io::file | fs::error) = {
    101 	let fs = fs: *os_filesystem;
    102 
    103 	let fd = match (rt::openat(fs.dirfd, path, flags, mode)) {
    104 	case let err: rt::errno =>
    105 		return errno_to_fs(err);
    106 	case let fd: int =>
    107 		yield fd;
    108 	};
    109 
    110 	return io::fdopen(fd);
    111 };
    112 
    113 fn fsflags_to_bsd(flags: fs::flag) (int | errors::unsupported) = {
    114 	let out = rt::O_NOCTTY | rt::O_CLOEXEC;
    115 	if (flags & fs::flag::WRONLY == fs::flag::WRONLY) {
    116 		out |= rt::O_WRONLY;
    117 	};
    118 	if (flags & fs::flag::RDWR == fs::flag::RDWR) {
    119 		out |= rt::O_RDWR;
    120 	};
    121 	if (flags & fs::flag::CREATE == fs::flag::CREATE) {
    122 		out |= rt::O_CREAT;
    123 	};
    124 	if (flags & fs::flag::EXCL == fs::flag::EXCL) {
    125 		out |= rt::O_EXCL;
    126 	};
    127 	if (flags & fs::flag::CTTY == fs::flag::CTTY) {
    128 		out &= ~rt::O_NOCTTY;
    129 	};
    130 	if (flags & fs::flag::TRUNC == fs::flag::TRUNC) {
    131 		out |= rt::O_TRUNC;
    132 	};
    133 	if (flags & fs::flag::APPEND == fs::flag::APPEND) {
    134 		out |= rt::O_APPEND;
    135 	};
    136 	if (flags & fs::flag::NONBLOCK == fs::flag::NONBLOCK) {
    137 		out |= rt::O_NONBLOCK;
    138 	};
    139 	if (flags & fs::flag::DSYNC == fs::flag::DSYNC) {
    140 		out |= rt::O_DSYNC;
    141 	};
    142 	if (flags & fs::flag::SYNC == fs::flag::SYNC) {
    143 		out |= rt::O_SYNC;
    144 	};
    145 	if (flags & fs::flag::RSYNC == fs::flag::RSYNC) {
    146 		out |= rt::O_SYNC;
    147 	};
    148 	if (flags & fs::flag::DIRECTORY == fs::flag::DIRECTORY) {
    149 		out |= rt::O_DIRECTORY;
    150 	};
    151 	if (flags & fs::flag::NOFOLLOW == fs::flag::NOFOLLOW) {
    152 		out |= rt::O_NOFOLLOW;
    153 	};
    154 	if (flags & fs::flag::NOCLOEXEC == fs::flag::NOCLOEXEC) {
    155 		out &= ~rt::O_CLOEXEC;
    156 	};
    157 	if (flags & fs::flag::PATH == fs::flag::PATH
    158 			|| flags & fs::flag::NOATIME == fs::flag::NOATIME
    159 			|| flags & fs::flag::TMPFILE == fs::flag::TMPFILE) {
    160 		return errors::unsupported;
    161 	};
    162 	return out;
    163 };
    164 
    165 fn fs_open_file(
    166 	fs: *fs::fs,
    167 	path: str,
    168 	flags: fs::flag,
    169 ) (io::file | fs::error) = {
    170 	return _fs_open(fs, path, fsflags_to_bsd(flags)?, 0);
    171 };
    172 
    173 fn fs_open(
    174 	fs: *fs::fs,
    175 	path: str,
    176 	flags: fs::flag,
    177 ) (io::handle | fs::error) = fs_open_file(fs, path, flags)?;
    178 
    179 fn fs_create_file(
    180 	fs: *fs::fs,
    181 	path: str,
    182 	mode: fs::mode,
    183 	flags: fs::flag,
    184 ) (io::file | fs::error) = {
    185 	flags |= fs::flag::CREATE;
    186 	return _fs_open(fs, path, fsflags_to_bsd(flags)?, mode)?;
    187 };
    188 
    189 fn fs_create(
    190 	fs: *fs::fs,
    191 	path: str,
    192 	mode: fs::mode,
    193 	flags: fs::flag,
    194 ) (io::handle | fs::error) = {
    195 	return fs_create_file(fs, path, mode, flags)?;
    196 };
    197 
    198 fn fs_remove(fs: *fs::fs, path: str) (void | fs::error) = {
    199 	let fs = fs: *os_filesystem;
    200 	match (rt::unlinkat(fs.dirfd, path, 0)) {
    201 	case let err: rt::errno =>
    202 		return errno_to_fs(err);
    203 	case void => void;
    204 	};
    205 };
    206 
    207 fn fs_rename(fs: *fs::fs, oldpath: str, newpath: str) (void | fs::error) = {
    208 	let fs = fs: *os_filesystem;
    209 	match (rt::renameat(fs.dirfd, oldpath, fs.dirfd, newpath)) {
    210 	case let err: rt::errno =>
    211 		return errno_to_fs(err);
    212 	case void => void;
    213 	};
    214 };
    215 
    216 fn fs_stat(fs: *fs::fs, path: str) (fs::filestat | fs::error) = {
    217 	let fs = fs: *os_filesystem;
    218 	let st = rt::st { ... };
    219 	match (rt::fstatat(fs.dirfd, path, &st, rt::AT_SYMLINK_NOFOLLOW)) {
    220 	case let err: rt::errno =>
    221 		return errno_to_fs(err);
    222 	case void => void;
    223 	};
    224 	return fs::filestat {
    225 		mask = fs::stat_mask::UID
    226 			| fs::stat_mask::GID
    227 			| fs::stat_mask::SIZE
    228 			| fs::stat_mask::INODE
    229 			| fs::stat_mask::ATIME
    230 			| fs::stat_mask::MTIME
    231 			| fs::stat_mask::CTIME,
    232 		mode = st.mode: fs::mode,
    233 		uid = st.uid,
    234 		gid = st.gid,
    235 		sz = st.sz: size,
    236 		inode = st.ino,
    237 		atime = time::instant {
    238 			sec = st.atime.tv_sec,
    239 			nsec = st.atime.tv_nsec,
    240 		},
    241 		mtime = time::instant {
    242 			sec = st.mtime.tv_sec,
    243 			nsec = st.mtime.tv_nsec,
    244 		},
    245 		ctime = time::instant {
    246 			sec = st.ctime.tv_sec,
    247 			nsec = st.ctime.tv_nsec,
    248 		},
    249 	};
    250 };
    251 
    252 fn fs_readlink(fs: *fs::fs, path: str) (str | fs::error) = {
    253 	let fs = fs: *os_filesystem;
    254 	static let buf: [rt::PATH_MAX]u8 = [0...];
    255 	let z = match (rt::readlinkat(fs.dirfd, path, buf[..])) {
    256 	case let err: rt::errno =>
    257 		switch (err) {
    258 		case rt::EINVAL =>
    259 			return fs::wrongtype;
    260 		case =>
    261 			return errno_to_fs(err);
    262 		};
    263 	case let z: size =>
    264 		yield z;
    265 	};
    266 	return strings::fromutf8(buf[..z])!;
    267 };
    268 
    269 fn fs_rmdir(fs: *fs::fs, path: str) (void | fs::error) = {
    270 	let fs = fs: *os_filesystem;
    271 	match (rt::unlinkat(fs.dirfd, path, rt::AT_REMOVEDIR)) {
    272 	case let err: rt::errno =>
    273 		return errno_to_fs(err);
    274 	case void => void;
    275 	};
    276 };
    277 
    278 fn fs_mkdir(fs: *fs::fs, path: str, mode: fs::mode) (void | fs::error) = {
    279 	let fs = fs: *os_filesystem;
    280 	match (rt::mkdirat(fs.dirfd, path, mode: uint)) {
    281 	case let err: rt::errno =>
    282 		switch (err) {
    283 		case rt::EISDIR =>
    284 			return errors::exists;
    285 		case =>
    286 			return errno_to_fs(err);
    287 		};
    288 	case void => void;
    289 	};
    290 };
    291 
    292 fn fs_chmod(fs: *fs::fs, path: str, mode: fs::mode) (void | fs::error) = {
    293 	let fs = fs: *os_filesystem;
    294 	match (rt::fchmodat(fs.dirfd, path, mode: uint, 0)) {
    295 	case let err: rt::errno =>
    296 		return errno_to_fs(err);
    297 	case void => void;
    298 	};
    299 };
    300 
    301 fn fs_fchmod(fd: io::file, mode: fs::mode) (void | fs::error) = {
    302 	match (rt::fchmod(fd, mode: uint)) {
    303 	case let err: rt::errno =>
    304 		return errno_to_fs(err);
    305 	case void => void;
    306 	};
    307 };
    308 
    309 fn fs_chown(fs: *fs::fs, path: str, uid: uint, gid: uint) (void | fs::error) = {
    310 	let fs = fs: *os_filesystem;
    311 	match (rt::fchownat(fs.dirfd, path, uid, gid, 0)) {
    312 	case let err: rt::errno =>
    313 		return errno_to_fs(err);
    314 	case void => void;
    315 	};
    316 };
    317 
    318 fn fs_fchown(fd: io::file, uid: uint, gid: uint) (void | fs::error) = {
    319 	match (rt::fchown(fd, uid, gid)) {
    320 	case let err: rt::errno =>
    321 		return errno_to_fs(err);
    322 	case void => void;
    323 	};
    324 };
    325 
    326 fn instant_to_timespec(time: (time::instant | void)) rt::timespec = {
    327 	match (time) {
    328 	case let t: time::instant =>
    329 		return time::instant_to_timespec(t);
    330 	case void =>
    331 		return rt::timespec{
    332 			tv_sec = rt::UTIME_OMIT,
    333 			tv_nsec = rt::UTIME_OMIT
    334 		};
    335 	};
    336 };
    337 
    338 fn fs_chtimes(fs: *fs::fs, path: str, atime: (time::instant | void),
    339 		mtime: (time::instant | void)) (void | fs::error) = {
    340 	let utimes: [2]rt::timespec = [
    341 		instant_to_timespec(atime),
    342 		instant_to_timespec(mtime),
    343 	];
    344         let fs = fs: *os_filesystem;
    345         match (rt::utimensat(fs.dirfd, path, &utimes, 0)) {
    346         case let err: rt::errno =>
    347                 return errno_to_fs(err);
    348         case void => void;
    349         };
    350 };
    351 
    352 fn fs_fchtimes(fd: io::file, atime: (time::instant | void),
    353 		mtime: (time::instant | void)) (void | fs::error) = {
    354 	let utimes: [2]rt::timespec = [
    355 		instant_to_timespec(atime),
    356 		instant_to_timespec(mtime),
    357 	];
    358         match (rt::futimens(fd, &utimes)) {
    359         case let err: rt::errno =>
    360                 return errno_to_fs(err);
    361         case void => void;
    362         };
    363 };
    364 
    365 fn fs_resolve(fs: *fs::fs, path: str) str = {
    366 	if (path::abs(path)) {
    367 		return path;
    368 	};
    369 	// XXX: This approach might not be right if this fs is based on a subdir
    370 	static let buf = path::buffer { ... };
    371 	path::set(&buf, getcwd(), path)!;
    372 	return path::string(&buf);
    373 };
    374 
    375 fn fs_close(fs: *fs::fs) void = {
    376 	let fs = fs: *os_filesystem;
    377 	rt::close(fs.dirfd)!;
    378 };
    379 
    380 // Based on musl's readdir
    381 type os_iterator = struct {
    382 	iter: fs::iterator,
    383 	fd: int,
    384 	buf_pos: size,
    385 	buf_end: size,
    386 	buf: []u8,
    387 };
    388 
    389 fn fs_iter(fs: *fs::fs, path: str) (*fs::iterator | fs::error) = {
    390 	let fs = fs: *os_filesystem;
    391 	let flags = rt::O_RDONLY | rt::O_CLOEXEC | rt::O_DIRECTORY;
    392 	let fd: int = match (rt::openat(fs.dirfd, path, flags, 0)) {
    393 	case let err: rt::errno =>
    394 		return errno_to_fs(err);
    395 	case let fd: int =>
    396 		yield fd;
    397 	};
    398 
    399 	let buf = match (rt::malloc(fs.getdents_bufsz)) {
    400 	case let v: *opaque =>
    401 		yield v: *[*]u8;
    402 	case null =>
    403 		return errors::nomem;
    404 	};
    405 	let iter = alloc(os_iterator {
    406 		iter = fs::iterator {
    407 			next = &iter_next,
    408 			finish = &iter_finish,
    409 		},
    410 		fd = fd,
    411 		buf = buf[..fs.getdents_bufsz],
    412 		...
    413 	});
    414 	return &iter.iter;
    415 };
    416 
    417 fn iter_next(iter: *fs::iterator) (fs::dirent | done | fs::error) = {
    418 	let iter = iter: *os_iterator;
    419 	if (iter.buf_pos >= iter.buf_end) {
    420 		let n = match (rt::getdents(iter.fd,
    421 			iter.buf: *[*]u8, len(iter.buf))) {
    422 		case let err: rt::errno =>
    423 			return errno_to_fs(err);
    424 		case let n: size =>
    425 			yield n;
    426 		};
    427 		if (n == 0) {
    428 			return done;
    429 		};
    430 		iter.buf_end = n;
    431 		iter.buf_pos = 0;
    432 	};
    433 	let de = &iter.buf[iter.buf_pos]: *rt::freebsd11_dirent;
    434 	iter.buf_pos += de.d_reclen;
    435 	let name = c::tostr(&de.d_name: *const c::char)?;
    436 	if (name == "." || name == "..") {
    437 		return iter_next(iter);
    438 	};
    439 
    440 	let ftype: fs::mode = switch (de.d_type) {
    441 	case rt::DT_UNKNOWN =>
    442 		yield fs::mode::UNKNOWN;
    443 	case rt::DT_FIFO =>
    444 		yield fs::mode::FIFO;
    445 	case rt::DT_CHR =>
    446 		yield fs::mode::CHR;
    447 	case rt::DT_DIR =>
    448 		yield fs::mode::DIR;
    449 	case rt::DT_BLK =>
    450 		yield fs::mode::BLK;
    451 	case rt::DT_REG =>
    452 		yield fs::mode::REG;
    453 	case rt::DT_LNK =>
    454 		yield fs::mode::LINK;
    455 	case rt::DT_SOCK =>
    456 		yield fs::mode::SOCK;
    457 	case =>
    458 		yield fs::mode::UNKNOWN;
    459 	};
    460 	return fs::dirent {
    461 		name = name,
    462 		ftype = ftype,
    463 	};
    464 };
    465 
    466 fn iter_finish(iter: *fs::iterator) void = {
    467 	let iter = iter: *os_iterator;
    468 	rt::close(iter.fd)!;
    469 	free(iter.buf);
    470 	free(iter);
    471 };