hare

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

dirfdfs.ha (12751B)


      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 // Controls how symlinks are followed (or not) in a dirfd filesystem. Support
     15 // for this feature varies, you should gate usage of this enum behind a build
     16 // tag.
     17 //
     18 // Note that on Linux, specifying BENEATH or IN_ROOT will also disable magic
     19 // symlinks.
     20 export type resolve_flag = enum u64 {
     21 	NORMAL = 0,
     22 
     23 	// Does not allow symlink resolution to occur for any symlinks which
     24 	// would refer to any anscestor of the fd directory. This disables all
     25 	// absolute symlinks, and any call to open or create with an absolute
     26 	// path.
     27 	BENEATH = rt::RESOLVE_BENEATH | rt::RESOLVE_NO_MAGICLINKS,
     28 
     29 	// Treat the directory fd as the root directory. This affects
     30 	// open/create for absolute paths, as well as absolute path resolution
     31 	// of symlinks. The effects are similar to chroot.
     32 	IN_ROOT = rt::RESOLVE_IN_ROOT | rt::RESOLVE_NO_MAGICLINKS,
     33 
     34 	// Disables symlink resolution entirely.
     35 	NO_SYMLINKS = rt::RESOLVE_NO_SYMLINKS,
     36 
     37 	// Disallows traversal of mountpoints during path resolution. This is
     38 	// not recommended for general use, as bind mounts are extensively used
     39 	// on many systems.
     40 	NO_XDEV = rt::RESOLVE_NO_XDEV,
     41 };
     42 
     43 type os_filesystem = struct {
     44 	fs: fs::fs,
     45 	dirfd: int,
     46 	resolve: resolve_flag,
     47 	getdents_bufsz: size,
     48 };
     49 
     50 // Opens a file descriptor as an [[fs::fs]]. This file descriptor must be a
     51 // directory file. The file will be closed when the fs is closed.
     52 export fn dirfdopen(
     53 	fd: io::file,
     54 	resolve_flags: resolve_flag = resolve_flag::NORMAL,
     55 ) *fs::fs = {
     56 	let ofs = alloc(os_filesystem { resolve = resolve_flags, ... });
     57 	let fs = static_dirfdopen(fd, ofs);
     58 	fs.close = &fs_close;
     59 	return fs;
     60 };
     61 
     62 fn static_dirfdopen(fd: io::file, filesystem: *os_filesystem) *fs::fs = {
     63 	*filesystem = os_filesystem {
     64 		fs = fs::fs {
     65 			open = &fs_open,
     66 			openfile = &fs_open_file,
     67 			create = &fs_create,
     68 			createfile = &fs_create_file,
     69 			remove = &fs_remove,
     70 			rename = &fs_rename,
     71 			iter = &fs_iter,
     72 			stat = &fs_stat,
     73 			fstat = &fs_fstat,
     74 			readlink = &fs_readlink,
     75 			mkdir = &fs_mkdir,
     76 			rmdir = &fs_rmdir,
     77 			chmod = &fs_chmod,
     78 			fchmod = &fs_fchmod,
     79 			chown = &fs_chown,
     80 			fchown = &fs_fchown,
     81 			chtimes = &fs_chtimes,
     82 			fchtimes = &fs_fchtimes,
     83 			resolve = &fs_resolve,
     84 			link = &fs_link,
     85 			symlink = &fs_symlink,
     86 			...
     87 		},
     88 		dirfd = fd,
     89 		getdents_bufsz = 32768, // 32 KiB
     90 		...
     91 	};
     92 	return &filesystem.fs;
     93 };
     94 
     95 // Clones a dirfd filesystem, optionally adding additional [[resolve_flag]]
     96 // constraints.
     97 export fn dirfs_clone(
     98 	fs: *fs::fs,
     99 	resolve_flags: resolve_flag = resolve_flag::NORMAL,
    100 ) *fs::fs = {
    101 	assert(fs.open == &fs_open);
    102 	let fs = fs: *os_filesystem;
    103 	let new = alloc(*fs);
    104 	fs.resolve |= resolve_flags;
    105 	new.dirfd = rt::fcntl(new.dirfd, rt::F_DUPFD_CLOEXEC, 0) as int;
    106 	return &new.fs;
    107 };
    108 
    109 // Sets the buffer size to use with the getdents(2) system call, for use with
    110 // [[fs::iter]]. A larger buffer requires a larger runtime allocation, but can
    111 // scan large directories faster. The default buffer size is 32 KiB.
    112 //
    113 // This function is not portable.
    114 export fn dirfdfs_set_getdents_bufsz(fs: *fs::fs, sz: size) void = {
    115 	assert(fs.open == &fs_open);
    116 	let fs = fs: *os_filesystem;
    117 	fs.getdents_bufsz = sz;
    118 };
    119 
    120 // Returns an [[io::file]] for this filesystem. This function is not portable.
    121 export fn dirfile(fs: *fs::fs) io::file = {
    122 	assert(fs.open == &fs_open);
    123 	let fs = fs: *os_filesystem;
    124 	return fs.dirfd;
    125 };
    126 
    127 fn errno_to_fs(err: rt::errno) fs::error = {
    128 	switch (err) {
    129 	case rt::ENOENT =>
    130 		return errors::noentry;
    131 	case rt::EEXIST =>
    132 		return errors::exists;
    133 	case rt::EACCES =>
    134 		return errors::noaccess;
    135 	case rt::EBUSY =>
    136 		return errors::busy;
    137 	case rt::ENOTDIR =>
    138 		return fs::wrongtype;
    139 	case rt::EOPNOTSUPP, rt::ENOSYS =>
    140 		return errors::unsupported;
    141 	case rt::EXDEV =>
    142 		return fs::cannotrename;
    143 	case =>
    144 		return errors::errno(err);
    145 	};
    146 };
    147 
    148 fn _fs_open(
    149 	fs: *fs::fs,
    150 	path: str,
    151 	oh: *rt::open_how,
    152 ) (io::file | fs::error) = {
    153 	let fs = fs: *os_filesystem;
    154 
    155 	oh.resolve = fs.resolve;
    156 
    157 	let fd = match (rt::openat2(fs.dirfd, path, oh, size(rt::open_how))) {
    158 	case let err: rt::errno =>
    159 		return errno_to_fs(err);
    160 	case let fd: int =>
    161 		yield fd;
    162 	};
    163 
    164 	return io::fdopen(fd);
    165 };
    166 
    167 fn fs_open_file(
    168 	fs: *fs::fs,
    169 	path: str,
    170 	flags: fs::flag,
    171 ) (io::file | fs::error) = {
    172 	flags ^= fs::flag::CTTY | fs::flag::NOCLOEXEC; // invert NOCTTY/CLOEXEC
    173 
    174 	if ((flags & fs::flag::DIRECTORY) == fs::flag::DIRECTORY) {
    175 		// This is arch-specific
    176 		flags &= ~fs::flag::DIRECTORY;
    177 		flags |= rt::O_DIRECTORY: fs::flag;
    178 	};
    179 
    180 	let oh = rt::open_how {
    181 		flags = flags: u64,
    182 		...
    183 	};
    184 	return _fs_open(fs, path, &oh);
    185 };
    186 
    187 fn fs_open(
    188 	fs: *fs::fs,
    189 	path: str,
    190 	flags: fs::flag,
    191 ) (io::handle | fs::error) = fs_open_file(fs, path, flags)?;
    192 
    193 fn fs_create_file(
    194 	fs: *fs::fs,
    195 	path: str,
    196 	mode: fs::mode,
    197 	flags: fs::flag,
    198 ) (io::file | fs::error) = {
    199 	flags ^= fs::flag::CTTY | fs::flag::NOCLOEXEC; // invert NOCTTY/CLOEXEC
    200 	flags |= fs::flag::CREATE;
    201 
    202 	let oh = rt::open_how {
    203 		flags = flags: u64,
    204 		mode = mode: u64,
    205 		...
    206 	};
    207 	return _fs_open(fs, path, &oh)?;
    208 };
    209 
    210 fn fs_create(
    211 	fs: *fs::fs,
    212 	path: str,
    213 	mode: fs::mode,
    214 	flags: fs::flag,
    215 ) (io::handle | fs::error) = {
    216 	return fs_create_file(fs, path, mode, flags)?;
    217 };
    218 
    219 fn fs_remove(fs: *fs::fs, path: str) (void | fs::error) = {
    220 	let fs = fs: *os_filesystem;
    221 	match (rt::unlinkat(fs.dirfd, path, 0)) {
    222 	case let err: rt::errno =>
    223 		return errno_to_fs(err);
    224 	case void => void;
    225 	};
    226 };
    227 
    228 fn fs_rename(fs: *fs::fs, oldpath: str, newpath: str) (void | fs::error) = {
    229 	let fs = fs: *os_filesystem;
    230 	match (rt::renameat(fs.dirfd, oldpath, fs.dirfd, newpath, 0)) {
    231 	case let err: rt::errno =>
    232 		return errno_to_fs(err);
    233 	case void => void;
    234 	};
    235 };
    236 
    237 fn fs_stat(fs: *fs::fs, path: str) (fs::filestat | fs::error) = {
    238 	let fs = fs: *os_filesystem;
    239 	let st = rt::st { ... };
    240 	match (rt::fstatat(fs.dirfd, path, &st, rt::AT_SYMLINK_NOFOLLOW)) {
    241 	case let err: rt::errno =>
    242 		return errno_to_fs(err);
    243 	case void => void;
    244 	};
    245 	return st_to_filestat(&st);
    246 };
    247 
    248 fn fs_fstat(fs: *fs::fs, fd: io::file) (fs::filestat | fs::error) = {
    249 	let fs = fs: *os_filesystem;
    250 	let st = rt::st { ... };
    251 	match (rt::fstatat(fd, "", &st, rt::AT_EMPTY_PATH)) {
    252 	case let err: rt::errno =>
    253 		return errno_to_fs(err);
    254 	case void => void;
    255 	};
    256 	return st_to_filestat(&st);
    257 };
    258 
    259 fn st_to_filestat(st: *rt::st) fs::filestat = {
    260 	return fs::filestat {
    261 		mask = fs::stat_mask::UID
    262 			| fs::stat_mask::GID
    263 			| fs::stat_mask::SIZE
    264 			| fs::stat_mask::INODE
    265 			| fs::stat_mask::ATIME
    266 			| fs::stat_mask::MTIME
    267 			| fs::stat_mask::CTIME,
    268 		mode = st.mode: fs::mode,
    269 		uid = st.uid,
    270 		gid = st.gid,
    271 		sz = st.sz,
    272 		inode = st.ino,
    273 		atime = time::instant {
    274 			sec = st.atime.tv_sec,
    275 			nsec = st.atime.tv_nsec,
    276 		},
    277 		mtime = time::instant {
    278 			sec = st.mtime.tv_sec,
    279 			nsec = st.mtime.tv_nsec,
    280 		},
    281 		ctime = time::instant {
    282 			sec = st.ctime.tv_sec,
    283 			nsec = st.ctime.tv_nsec,
    284 		},
    285 	};
    286 };
    287 
    288 fn fs_readlink(fs: *fs::fs, path: str) (str | fs::error) = {
    289 	let fs = fs: *os_filesystem;
    290 	static let buf: [rt::PATH_MAX]u8 = [0...];
    291 	let z = match (rt::readlinkat(fs.dirfd, path, buf[..])) {
    292 	case let err: rt::errno =>
    293 		switch (err) {
    294 		case rt::EINVAL =>
    295 			return fs::wrongtype;
    296 		case =>
    297 			return errno_to_fs(err);
    298 		};
    299 	case let z: size =>
    300 		yield z;
    301 	};
    302 	return strings::fromutf8(buf[..z])!;
    303 };
    304 
    305 fn fs_rmdir(fs: *fs::fs, path: str) (void | fs::error) = {
    306 	let fs = fs: *os_filesystem;
    307 	match (rt::unlinkat(fs.dirfd, path, rt::AT_REMOVEDIR)) {
    308 	case let err: rt::errno =>
    309 		return errno_to_fs(err);
    310 	case void => void;
    311 	};
    312 };
    313 
    314 fn fs_mkdir(fs: *fs::fs, path: str, mode: fs::mode) (void | fs::error) = {
    315 	let fs = fs: *os_filesystem;
    316 	match (rt::mkdirat(fs.dirfd, path, mode: uint)) {
    317 	case let err: rt::errno =>
    318 		return errno_to_fs(err);
    319 	case void => void;
    320 	};
    321 };
    322 
    323 fn fs_chmod(fs: *fs::fs, path: str, mode: fs::mode) (void | fs::error) = {
    324 	let fs = fs: *os_filesystem;
    325 	match (rt::fchmodat(fs.dirfd, path, mode: uint, 0)) {
    326 	case let err: rt::errno =>
    327 		return errno_to_fs(err);
    328 	case void => void;
    329 	};
    330 };
    331 
    332 fn fs_fchmod(fd: io::file, mode: fs::mode) (void | fs::error) = {
    333 	match (rt::fchmod(fd, mode: uint)) {
    334 	case let err: rt::errno =>
    335 		return errno_to_fs(err);
    336 	case void => void;
    337 	};
    338 };
    339 
    340 fn fs_chown(fs: *fs::fs, path: str, uid: uint, gid: uint) (void | fs::error) = {
    341 	let fs = fs: *os_filesystem;
    342 	match (rt::fchownat(fs.dirfd, path, uid, gid, 0)) {
    343 	case let err: rt::errno =>
    344 		return errno_to_fs(err);
    345 	case void => void;
    346 	};
    347 };
    348 
    349 fn fs_fchown(fd: io::file, uid: uint, gid: uint) (void | fs::error) = {
    350 	match (rt::fchown(fd, uid, gid)) {
    351 	case let err: rt::errno =>
    352 		return errno_to_fs(err);
    353 	case void => void;
    354 	};
    355 };
    356 
    357 fn instant_to_timespec(time: (time::instant | void)) rt::timespec = {
    358 	match (time) {
    359 	case let t: time::instant =>
    360 		return time::instant_to_timespec(t);
    361 	case void =>
    362 		return rt::timespec{
    363 			tv_sec = rt::UTIME_OMIT,
    364 			tv_nsec = rt::UTIME_OMIT
    365 		};
    366 	};
    367 };
    368 
    369 fn fs_chtimes(fs: *fs::fs, path: str, atime: (time::instant | void),
    370 		mtime: (time::instant | void)) (void | fs::error) = {
    371 	let utimes: [2]rt::timespec = [
    372 		instant_to_timespec(atime),
    373 		instant_to_timespec(mtime),
    374 	];
    375         let fs = fs: *os_filesystem;
    376         match (rt::utimensat(fs.dirfd, path, &utimes, 0)) {
    377         case let err: rt::errno =>
    378                 return errno_to_fs(err);
    379         case void => void;
    380         };
    381 };
    382 
    383 fn fs_fchtimes(fd: io::file, atime: (time::instant | void),
    384 		mtime: (time::instant | void)) (void | fs::error) = {
    385 	let utimes: [2]rt::timespec = [
    386 		instant_to_timespec(atime),
    387 		instant_to_timespec(mtime),
    388 	];
    389         match (rt::futimens(fd, &utimes)) {
    390         case let err: rt::errno =>
    391                 return errno_to_fs(err);
    392         case void => void;
    393         };
    394 };
    395 
    396 fn fs_resolve(fs: *fs::fs, path: str) str = {
    397 	if (path::abs(path)) {
    398 		return path;
    399 	};
    400 	// XXX: This approach might not be right if this fs is based on a subdir
    401 	static let buf = path::buffer { ... };
    402 	path::set(&buf, getcwd(), path)!;
    403 	return path::string(&buf);
    404 };
    405 
    406 fn fs_link(fs: *fs::fs, old: str, new: str) (void | fs::error) = {
    407 	let fs = fs: *os_filesystem;
    408 	match (rt::linkat(fs.dirfd, old, fs.dirfd, new, 0)) {
    409 	case let err: rt::errno =>
    410 		return errno_to_fs(err);
    411 	case void => void;
    412 	};
    413 };
    414 
    415 fn fs_symlink(fs: *fs::fs, target: str, path: str) (void | fs::error) = {
    416 	let fs = fs: *os_filesystem;
    417 	match (rt::symlinkat(target, fs.dirfd, path)) {
    418 	case let err: rt::errno =>
    419 		return errno_to_fs(err);
    420 	case void => void;
    421 	};
    422 };
    423 
    424 fn fs_close(fs: *fs::fs) void = {
    425 	let fs = fs: *os_filesystem;
    426 	rt::close(fs.dirfd)!;
    427 	free(fs);
    428 };
    429 
    430 // Based on musl's readdir
    431 type os_iterator = struct {
    432 	iter: fs::iterator,
    433 	fd: int,
    434 	buf_pos: size,
    435 	buf_end: size,
    436 	buf: []u8,
    437 };
    438 
    439 fn fs_iter(fs: *fs::fs, path: str) (*fs::iterator | fs::error) = {
    440 	let fs = fs: *os_filesystem;
    441 	let oh = rt::open_how {
    442 		flags = (rt::O_RDONLY | rt::O_CLOEXEC | rt::O_DIRECTORY): u64,
    443 		...
    444 	};
    445 	let fd: int = match (rt::openat2(fs.dirfd, path,
    446 			&oh, size(rt::open_how))) {
    447 	case let err: rt::errno =>
    448 		return errno_to_fs(err);
    449 	case let fd: int =>
    450 		yield fd;
    451 	};
    452 
    453 	let buf = match (rt::malloc(fs.getdents_bufsz)) {
    454 	case let v: *opaque =>
    455 		yield v: *[*]u8;
    456 	case null =>
    457 		return errors::nomem;
    458 	};
    459 	let iter = alloc(os_iterator {
    460 		iter = fs::iterator {
    461 			next = &iter_next,
    462 			finish = &iter_finish,
    463 		},
    464 		fd = fd,
    465 		buf = buf[..fs.getdents_bufsz],
    466 		...
    467 	});
    468 	return &iter.iter;
    469 };
    470 
    471 fn iter_next(iter: *fs::iterator) (fs::dirent | done | fs::error) = {
    472 	let iter = iter: *os_iterator;
    473 	if (iter.buf_pos >= iter.buf_end) {
    474 		let n = match (rt::getdents64(iter.fd,
    475 			iter.buf: *[*]u8, len(iter.buf))) {
    476 		case let err: rt::errno =>
    477 			return errno_to_fs(err);
    478 		case let n: size =>
    479 			yield n;
    480 		};
    481 		if (n == 0) {
    482 			return done;
    483 		};
    484 		iter.buf_end = n;
    485 		iter.buf_pos = 0;
    486 	};
    487 	let de = &iter.buf[iter.buf_pos]: *rt::dirent64;
    488 	iter.buf_pos += de.d_reclen;
    489 	let name = c::tostr(&de.d_name: *const c::char)?;
    490 	if (name == "." || name == "..") {
    491 		return iter_next(iter);
    492 	};
    493 
    494 	let ftype: fs::mode = switch (de.d_type) {
    495 	case rt::DT_UNKNOWN =>
    496 		yield fs::mode::UNKNOWN;
    497 	case rt::DT_FIFO =>
    498 		yield fs::mode::FIFO;
    499 	case rt::DT_CHR =>
    500 		yield fs::mode::CHR;
    501 	case rt::DT_DIR =>
    502 		yield fs::mode::DIR;
    503 	case rt::DT_BLK =>
    504 		yield fs::mode::BLK;
    505 	case rt::DT_REG =>
    506 		yield fs::mode::REG;
    507 	case rt::DT_LNK =>
    508 		yield fs::mode::LINK;
    509 	case rt::DT_SOCK =>
    510 		yield fs::mode::SOCK;
    511 	case =>
    512 		yield fs::mode::UNKNOWN;
    513 	};
    514 	return fs::dirent {
    515 		name = name,
    516 		ftype = ftype,
    517 	};
    518 };
    519 
    520 fn iter_finish(iter: *fs::iterator) void = {
    521 	let iter = iter: *os_iterator;
    522 	rt::close(iter.fd)!;
    523 	free(iter.buf);
    524 	free(iter);
    525 };