group.ha (3733B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use bufio; 5 use io; 6 use memio; 7 use os; 8 use strconv; 9 use strings; 10 use unix; 11 12 // A Unix-like group file entry. 13 export type grent = struct { 14 // Name of the group 15 name: str, 16 // Optional encrypted password 17 password: str, 18 // Numerical group ID 19 gid: unix::gid, 20 // List of usernames that are members of this group 21 userlist: []str, 22 }; 23 24 // Reads a Unix-like group entry from an [[io::handle]]. The caller must free 25 // the return value using [[grent_finish]]. 26 export fn nextgr(in: io::handle) (grent | io::EOF | io::error | invalid) = { 27 let line = match (bufio::read_line(in)?) { 28 case let ln: []u8 => 29 yield ln; 30 case io::EOF => 31 return io::EOF; 32 }; 33 let line = match (strings::fromutf8(line)) { 34 case let s: str => 35 yield s; 36 case => 37 return invalid; 38 }; 39 40 let fields = strings::split(line, ":"); 41 defer free(fields); 42 43 if (len(fields) != 4) { 44 return invalid; 45 }; 46 47 let gid = match (strconv::stou64(fields[2])) { 48 case let u: u64 => 49 yield u: unix::gid; 50 case => 51 return invalid; 52 }; 53 54 return grent { 55 // Borrows the return value of bufio::read_line 56 name = fields[0], 57 password = fields[1], 58 gid = gid, 59 userlist = strings::split(fields[3], ","), 60 }; 61 }; 62 63 // Frees resources associated with [[grent]]. 64 export fn grent_finish(ent: *grent) void = { 65 free(ent.name); 66 free(ent.userlist); 67 }; 68 69 // Looks up a group by name in a Unix-like group file. It expects a such file at 70 // /etc/group. Aborts if that file doesn't exist or is not properly formatted. 71 // 72 // See [[nextgr]] for low-level parsing API. 73 export fn getgroup(name: str) (grent | void) = { 74 let file = match (os::open("/etc/group")) { 75 case let f: io::file => 76 yield f; 77 case => 78 abort("Unable to open /etc/group"); 79 }; 80 defer io::close(file)!; 81 82 let rbuf: [os::BUFSZ]u8 = [0...]; 83 let strm = bufio::init(file, rbuf, []); 84 for (true) { 85 let ent = match (nextgr(&strm)) { 86 case let e: grent => 87 yield e; 88 case io::EOF => 89 break; 90 case => 91 abort("Invalid entry in /etc/group"); 92 }; 93 94 if (ent.name == name) { 95 return ent; 96 } else { 97 grent_finish(&ent); 98 }; 99 }; 100 }; 101 102 // Looks up a group by ID in a Unix-like group file. It expects a such file at 103 // /etc/group. Aborts if that file doesn't exist or is not properly formatted. 104 // 105 // See [[nextgr]] for low-level parsing API. 106 export fn getgid(gid: unix::gid) (grent | void) = { 107 let file = match (os::open("/etc/group")) { 108 case let f: io::file => 109 yield f; 110 case => 111 abort("Unable to open /etc/group"); 112 }; 113 defer io::close(file)!; 114 115 let rbuf: [os::BUFSZ]u8 = [0...]; 116 let strm = bufio::init(file, rbuf, []); 117 for (true) { 118 let ent = match (nextgr(&strm)) { 119 case let e: grent => 120 yield e; 121 case io::EOF => 122 break; 123 case => 124 abort("Invalid entry in /etc/group"); 125 }; 126 127 if (ent.gid == gid) { 128 return ent; 129 } else { 130 grent_finish(&ent); 131 }; 132 }; 133 }; 134 135 @test fn nextgr() void = { 136 let buf = memio::fixed(strings::toutf8( 137 "root:x:0:root\n" 138 "mail:x:12:\n" 139 "video:x:986:alex,wmuser")); 140 141 let ent = nextgr(&buf) as grent; 142 defer grent_finish(&ent); 143 144 assert(ent.name == "root"); 145 assert(ent.password == "x"); 146 assert(ent.gid == 0); 147 assert(len(ent.userlist) == 1); 148 assert(ent.userlist[0] == "root"); 149 150 let ent = nextgr(&buf) as grent; 151 defer grent_finish(&ent); 152 153 assert(ent.name == "mail"); 154 assert(ent.password == "x"); 155 assert(ent.gid == 12); 156 assert(len(ent.userlist) == 0); 157 158 let ent = nextgr(&buf) as grent; 159 defer grent_finish(&ent); 160 161 assert(ent.name == "video"); 162 assert(ent.password == "x"); 163 assert(ent.gid == 986); 164 assert(len(ent.userlist) == 2); 165 assert(ent.userlist[0] == "alex"); 166 assert(ent.userlist[1] == "wmuser"); 167 168 // No more entries 169 assert(nextgr(&buf) is io::EOF); 170 };