hare

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

dirfdfs.ha (11464B)


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