hare

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

dirfdfs.ha (12673B)


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