util.ha (4863B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use errors; 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 match (path::init(path)) { 108 case let buf: path::path => 109 return rmdirall_path(fs, &buf); 110 case let err: path::error => 111 assert(err is path::too_long); 112 return errors::noentry; 113 }; 114 }; 115 116 fn rmdirall_path(fs: *fs, p: *path::path) (void | error) = { 117 let it = iter(fs, path::string(p))?; 118 defer finish(it); 119 for (let ent => next(it)?) { 120 path::push(p, ent.name)!; 121 122 switch (ent.ftype & mode::DIR) { 123 case mode::DIR => 124 rmdirall_path(fs, p)?; 125 case => 126 remove(fs, path::string(p))?; 127 }; 128 path::pop(p); 129 }; 130 return rmdir(fs, path::string(p)); 131 }; 132 133 // Canonicalizes a path in this filesystem by resolving all symlinks and 134 // collapsing any "." or ".." path components. The return value is statically 135 // allocated and will be overwritten on subsequent calls. 136 export fn realpath(fs: *fs, path: str) (str | error) = { 137 static let res = path::path { ... }; 138 path::set(&res)!; 139 static let p = path::path { ... }; 140 path::set(&p, resolve(fs, path))!; 141 const iter = path::iter(&p); 142 143 for (let item => path::nextiter(&iter)) { 144 const item = path::push(&res, item)!; 145 146 const link = match (readlink(fs, item)) { 147 case let link: str => 148 yield link; 149 case wrongtype => 150 continue; 151 case let err: error => 152 return err; 153 }; 154 155 if (!path::abs(link)) { 156 path::push(&res, "..", link)!; 157 } else { 158 path::set(&res, link)!; 159 }; 160 }; 161 162 return path::string(&res); 163 };