hare

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

util.ha (5275B)


      1 // License: MPL-2.0
      2 // (c) 2022 Bor Grošelj Simić <bor.groseljsimic@telemach.net>
      3 // (c) 2021-2022 Drew DeVault <sir@cmpwn.com>
      4 // (c) 2021 Ember Sawady <ecs@d2evs.net>
      5 // (c) 2021 Thomas Bracht Laumann Jespersen <t@laumann.xyz>
      6 use errors;
      7 use io;
      8 use path;
      9 use strings;
     10 
     11 // Converts a mode into a Unix-like mode string (e.g. "-rw-r--r--"). The string
     12 // is statically allocated, use [[strings::dup]] to duplicate it or it will be
     13 // overwritten on subsequent calls.
     14 export fn mode_str(m: mode) const str = {
     15 	static let buf: [10]u8 = [0...];
     16 	buf = [
     17 		(if (m & mode::DIR == mode::DIR) 'd'
     18 			else if (m & mode::FIFO == mode::FIFO) 'p'
     19 			else if (m & mode::SOCK == mode::SOCK) 's'
     20 			else if (m & mode::BLK == mode::BLK) 'b'
     21 			else if (m & mode::LINK == mode::LINK) 'l'
     22 			else if (m & mode::CHR == mode::CHR) 'c'
     23 			else '-'): u32: u8,
     24 		(if (m & mode::USER_R == mode::USER_R) 'r' else '-'): u32: u8,
     25 		(if (m & mode::USER_W == mode::USER_W) 'w' else '-'): u32: u8,
     26 		(if (m & mode::SETUID == mode::SETUID) 's'
     27 			else if (m & mode::USER_X == mode::USER_X) 'x'
     28 			else '-'): u32: u8,
     29 		(if (m & mode::GROUP_R == mode::GROUP_R) 'r' else '-'): u32: u8,
     30 		(if (m & mode::GROUP_W == mode::GROUP_W) 'w' else '-'): u32: u8,
     31 		(if (m & mode::SETGID == mode::SETGID) 's'
     32 			else if (m & mode::GROUP_X == mode::GROUP_X) 'x'
     33 			else '-'): u32: u8,
     34 		(if (m & mode::OTHER_R == mode::OTHER_R) 'r' else '-'): u32: u8,
     35 		(if (m & mode::OTHER_W == mode::OTHER_W) 'w' else '-'): u32: u8,
     36 		(if (m & mode::STICKY == mode::STICKY) 't'
     37 			else if (m & mode::OTHER_X == mode::OTHER_X) 'x'
     38 			else '-'): u32: u8,
     39 	];
     40 	return strings::fromutf8(buf)!;
     41 };
     42 
     43 @test fn mode_str() void = {
     44 	assert(mode_str(0o777: mode) == "-rwxrwxrwx");
     45 	assert(mode_str(mode::DIR | 0o755: mode) == "drwxr-xr-x");
     46 	assert(mode_str(0o755: mode | mode::SETUID) == "-rwsr-xr-x");
     47 	assert(mode_str(0o644: mode) == "-rw-r--r--");
     48 	assert(mode_str(0: mode) == "----------");
     49 };
     50 
     51 // Returns the permission bits of a file mode.
     52 export fn mode_perm(m: mode) mode = (m: uint & 0o777u): mode;
     53 
     54 // Returns the type bits of a file mode.
     55 export fn mode_type(m: mode) mode = (m: uint & ~0o777u): mode;
     56 
     57 // bit mask for the file type bit field
     58 def IFMT: mode = 0o0170000u: mode;
     59 
     60 // Returns true if this item is a regular file.
     61 export fn isfile(mode: mode) bool = mode & IFMT == mode::REG;
     62 
     63 // Returns true if this item is a FIFO (named pipe).
     64 export fn isfifo(mode: mode) bool = mode & IFMT == mode::FIFO;
     65 
     66 // Returns true if this item is a directory.
     67 export fn isdir(mode: mode) bool = mode & IFMT == mode::DIR;
     68 
     69 // Returns true if this item is a character device.
     70 export fn ischdev(mode: mode) bool = mode & IFMT == mode::CHR;
     71 
     72 // Returns true if this item is a block device.
     73 export fn isblockdev(mode: mode) bool = mode & IFMT == mode::BLK;
     74 
     75 // Returns true if this item is a symbolic link.
     76 export fn islink(mode: mode) bool = mode & IFMT == mode::LINK;
     77 
     78 // Returns true if this item is a Unix socket.
     79 export fn issocket(mode: mode) bool = mode & IFMT == mode::SOCK;
     80 
     81 @test fn modes() void = {
     82 	const foo = mode::LINK | 0o755: mode;
     83 	assert(islink(foo));
     84 	assert(!isfile(foo));
     85 };
     86 
     87 // Reads all entries from a directory. The caller must free the return value
     88 // with [[dirents_free]].
     89 export fn readdir(fs: *fs, path: str) ([]dirent | error) = {
     90 	let i = iter(fs, path)?;
     91 	defer finish(i);
     92 	let ents: []dirent = [];
     93 	for (true) {
     94 		match (next(i)) {
     95 		case let d: dirent =>
     96 			append(ents, dirent_dup(&d));
     97 		case void =>
     98 			break;
     99 		};
    100 	};
    101 	return ents;
    102 };
    103 
    104 // Frees a slice of [[dirent]]s.
    105 export fn dirents_free(d: []dirent) void = {
    106 	for (let i = 0z; i < len(d); i += 1) {
    107 		dirent_finish(&d[i]);
    108 	};
    109 	free(d);
    110 };
    111 
    112 // Removes a directory, and anything in it.
    113 export fn rmdirall(fs: *fs, path: str) (void | error) = {
    114 	let buf = path::init(path)!;
    115 	return rmdirall_path(fs, &buf);
    116 };
    117 
    118 fn rmdirall_path(fs: *fs, buf: *path::buffer) (void | error) = {
    119 	let it = iter(fs, path::string(buf))?;
    120 	defer finish(it);
    121 	for (true) {
    122 		match (next(it)) {
    123 		case let ent: dirent =>
    124 			if (ent.name == "." || ent.name == "..") {
    125 				continue;
    126 			};
    127 			path::push(buf, ent.name)!;
    128 
    129 			switch (ent.ftype & mode::DIR) {
    130 			case mode::DIR =>
    131 				rmdirall_path(fs, buf)?;
    132 			case =>
    133 				remove(fs, path::string(buf))?;
    134 			};
    135 			path::pop(buf);
    136 		case void =>
    137 			break;
    138 		};
    139 	};
    140 	return rmdir(fs, path::string(buf));
    141 };
    142 
    143 // Canonicalizes a path in this filesystem by resolving all symlinks and
    144 // collapsing any "." or ".." path components. The return value is statically
    145 // allocated and will be overwritten on subsequent calls.
    146 export fn realpath(fs: *fs, path: str) (str | error) = {
    147 	static let res = path::buffer { ... };
    148 	path::set(&res)!;
    149 	static let pathbuf = path::buffer { ... };
    150 	path::set(&pathbuf, resolve(fs, path))!;
    151 	const iter = path::iter(&pathbuf);
    152 	for (true) {
    153 		const item = match (path::nextiter(&iter)) {
    154 		case let item: str =>
    155 			yield item;
    156 		case void =>
    157 			break;
    158 		};
    159 
    160 		const item = path::push(&res, item)!;
    161 		const link = match (readlink(fs, item)) {
    162 		case let link: str =>
    163 			yield link;
    164 		case wrongtype =>
    165 			continue;
    166 		case let err: error =>
    167 			return err;
    168 		};
    169 
    170 		if (!path::abs(link)) {
    171 			path::push(&res, "..", link)!;
    172 		} else {
    173 			path::set(&res, link)!;
    174 		};
    175 	};
    176 
    177 	return path::string(&res);
    178 };