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 };