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