File: util.ha

package info (click to toggle)
hare 0.26.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 7,352 kB
  • sloc: asm: 1,374; makefile: 123; sh: 117; lisp: 101
file content (173 lines) | stat: -rw-r--r-- 5,086 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use errors;
use path;
use strings;

// Converts a mode into a Unix-like mode string (e.g. "-rw-r--r--"). The string
// is statically allocated, use [[strings::dup]] to duplicate it or it will be
// overwritten on subsequent calls.
export fn mode_str(m: mode) const str = {
	static let buf: [10]u8 = [0...];
	buf = [
		(if (m & mode::DIR == mode::DIR) 'd'
			else if (m & mode::FIFO == mode::FIFO) 'p'
			else if (m & mode::SOCK == mode::SOCK) 's'
			else if (m & mode::BLK == mode::BLK) 'b'
			else if (m & mode::LINK == mode::LINK) 'l'
			else if (m & mode::CHR == mode::CHR) 'c'
			else '-'): u8,
		(if (m & mode::USER_R == mode::USER_R) 'r' else '-'): u8,
		(if (m & mode::USER_W == mode::USER_W) 'w' else '-'): u8,
		(if (m & mode::SETUID == mode::SETUID) 's'
			else if (m & mode::USER_X == mode::USER_X) 'x'
			else '-'): u8,
		(if (m & mode::GROUP_R == mode::GROUP_R) 'r' else '-'): u8,
		(if (m & mode::GROUP_W == mode::GROUP_W) 'w' else '-'): u8,
		(if (m & mode::SETGID == mode::SETGID) 's'
			else if (m & mode::GROUP_X == mode::GROUP_X) 'x'
			else '-'): u8,
		(if (m & mode::OTHER_R == mode::OTHER_R) 'r' else '-'): u8,
		(if (m & mode::OTHER_W == mode::OTHER_W) 'w' else '-'): u8,
		(if (m & mode::STICKY == mode::STICKY) 't'
			else if (m & mode::OTHER_X == mode::OTHER_X) 'x'
			else '-'): u8,
	];
	return strings::fromutf8(buf)!;
};

@test fn mode_str() void = {
	assert(mode_str(0o777: mode) == "-rwxrwxrwx");
	assert(mode_str(mode::DIR | 0o755: mode) == "drwxr-xr-x");
	assert(mode_str(0o755: mode | mode::SETUID) == "-rwsr-xr-x");
	assert(mode_str(0o644: mode) == "-rw-r--r--");
	assert(mode_str(0: mode) == "----------");
};

// Returns the permission bits of a file mode.
export fn mode_perm(m: mode) mode = (m: uint & 0o777u): mode;

// Returns the type bits of a file mode.
export fn mode_type(m: mode) mode = (m: uint & ~0o777u): mode;

// bit mask for the file type bit field
def IFMT: mode = 0o0170000u: mode;

// Returns true if this item is a regular file.
export fn isfile(mode: mode) bool = mode & IFMT == mode::REG;

// Returns true if this item is a FIFO (named pipe).
export fn isfifo(mode: mode) bool = mode & IFMT == mode::FIFO;

// Returns true if this item is a directory.
export fn isdir(mode: mode) bool = mode & IFMT == mode::DIR;

// Returns true if this item is a character device.
export fn ischdev(mode: mode) bool = mode & IFMT == mode::CHR;

// Returns true if this item is a block device.
export fn isblockdev(mode: mode) bool = mode & IFMT == mode::BLK;

// Returns true if this item is a symbolic link.
export fn islink(mode: mode) bool = mode & IFMT == mode::LINK;

// Returns true if this item is a Unix socket.
export fn issocket(mode: mode) bool = mode & IFMT == mode::SOCK;

@test fn modes() void = {
	const foo = mode::LINK | 0o755: mode;
	assert(islink(foo));
	assert(!isfile(foo));
};

// Reads all entries from a directory. The caller must free the return value
// with [[dirents_free]].
export fn readdir(fs: *fs, path: str) ([]dirent | error | nomem) = {
	let i = iter(fs, path)?;
	defer finish(i);
	let ents: []dirent = [];
	let ok = false;
	defer if (!ok) dirents_free(ents);

	for (let d => next(i)?) {
		let dup = dirent_dup(&d)?;
		match (append(ents, dup)) {
		case void => void;
		case nomem =>
			dirent_finish(&dup);
			return nomem;
		};
	};

	ok = true;
	return ents;
};

// Frees a slice of [[dirent]]s.
export fn dirents_free(dirents: []dirent) void = {
	for (let d &.. dirents) {
		dirent_finish(d);
	};
	free(dirents);
};

// Removes a directory, and anything in it.
export fn rmdirall(fs: *fs, path: str) (void | error) = {
	match (path::init(path)) {
	case let buf: path::buffer =>
		return rmdirall_path(fs, &buf);
	case let err: path::error =>
		assert(err is path::too_long);
		return errors::noentry;
	};
};

fn rmdirall_path(fs: *fs, buf: *path::buffer) (void | error) = {
	let it = iter(fs, path::string(buf))?;
	defer finish(it);
	for (let ent => next(it)?) {
		path::push(buf, ent.name)!;

		switch (ent.ftype & mode::DIR) {
		case mode::DIR =>
			rmdirall_path(fs, buf)?;
		case =>
			remove(fs, path::string(buf))?;
		};
		path::pop(buf);
	};
	return rmdir(fs, path::string(buf));
};

// Canonicalizes a path in this filesystem by resolving all symlinks and
// collapsing any "." or ".." path components. The return value is statically
// allocated and will be overwritten on subsequent calls.
export fn realpath(fs: *fs, path: str) (str | error) = {
	static let res = path::buffer { ... };
	path::set(&res)!;
	static let pathbuf = path::buffer { ... };
	path::set(&pathbuf, resolve(fs, path))!;
	const iter = path::iter(&pathbuf);

	for (let item => path::nextiter(&iter)) {
		const item = path::push(&res, item)!;

		const link = match (readlink(fs, item)) {
		case let link: str =>
			yield link;
		case wrongtype =>
			continue;
		case let err: error =>
			return err;
		};

		if (!path::abs(link)) {
			path::push(&res, "..", link)!;
		} else {
			path::set(&res, link)!;
		};
	};

	return path::string(&res);
};