hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

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