hare

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

dirfdfs.ha (12695B)


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