commit 4298b6824c2b42026fbf7147eb267cb3c312756f
parent ae4bcefee4ba0ca0b03b603daf310059da8f726c
Author: Alexey Yerin <yyp@disroot.org>
Date: Sun, 11 Apr 2021 19:36:27 +0300
unix/passwd: add /etc/group parser
Diffstat:
3 files changed, 120 insertions(+), 0 deletions(-)
diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib
@@ -622,6 +622,7 @@ unix() {
unix_passwd() {
gen_srcs unix::passwd \
+ group.ha \
passwd.ha \
types.ha
gen_ssa unix::passwd bufio io os strconv strings
diff --git a/stdlib.mk b/stdlib.mk
@@ -818,6 +818,7 @@ $(HARECACHE)/unix/unix.ssa: $(stdlib_unix_srcs) $(stdlib_rt) $(stdlib_errors)
# unix::passwd
stdlib_unix_passwd_srcs= \
+ $(STDLIB)/unix/passwd/group.ha \
$(STDLIB)/unix/passwd/passwd.ha \
$(STDLIB)/unix/passwd/types.ha
@@ -1673,6 +1674,7 @@ $(TESTCACHE)/unix/unix.ssa: $(testlib_unix_srcs) $(testlib_rt) $(testlib_errors)
# unix::passwd
testlib_unix_passwd_srcs= \
+ $(STDLIB)/unix/passwd/group.ha \
$(STDLIB)/unix/passwd/passwd.ha \
$(STDLIB)/unix/passwd/types.ha
diff --git a/unix/passwd/group.ha b/unix/passwd/group.ha
@@ -0,0 +1,117 @@
+use bufio;
+use io;
+use os;
+use strconv;
+use strings;
+
+// A Unix-like group file entry.
+export type grent = struct {
+ // Name of the group
+ name: str,
+ // Optional encrypted password
+ password: str,
+ // Numerical group ID
+ gid: uint,
+ // List of usernames that are members of this group, comma separated
+ userlist: str,
+};
+
+// Reads a Unix-like group entry from a stream. The caller must free the result
+// using [grent_finish].
+export fn nextgr(stream: *io::stream) (grent | io::EOF | io::error | invalid) = {
+ let line = match (bufio::scanline(stream)?) {
+ ln: []u8 => ln,
+ io::EOF => return io::EOF,
+ };
+ let line = match (strings::try_fromutf8(line)) {
+ s: str => s,
+ * => return invalid,
+ };
+
+ let fields = strings::split(line, ":");
+ defer free(fields);
+
+ if (len(fields) != 4) {
+ return invalid;
+ };
+
+ let gid = match (strconv::stou(fields[2])) {
+ u: uint => u,
+ * => return invalid,
+ };
+
+ return grent {
+ // Borrows the return value of bufio::scanline
+ name = fields[0],
+ password = fields[1],
+ gid = gid,
+ userlist = fields[3],
+ };
+};
+
+// Frees resources associated with [grent].
+export fn grent_finish(ent: grent) void = {
+ free(ent.name);
+};
+
+// Looks up a group by name in a Unix-like group file. It expects a such file at
+// /etc/group. Aborts if that file doesn't exist or is not properly formatted.
+//
+// See [nextgr] for low-level parsing API.
+export fn getgroup(name: str) (grent | void) = {
+ let file = match (os::open("/etc/group")) {
+ s: *io::stream => s,
+ * => abort("Unable to open /etc/group"),
+ };
+ defer io::close(file);
+
+ for (true) {
+ let ent = match (nextgr(file)) {
+ e: grent => e,
+ io::EOF => break,
+ * => abort("Invalid entry in /etc/group"),
+ };
+ defer grent_finish(ent);
+
+ if (ent.name == name) {
+ return ent;
+ };
+ };
+
+ return;
+};
+
+@test fn nextgr() void = {
+ let buf = bufio::fixed(strings::toutf8(
+ "root:x:0:root\n"
+ "mail:x:12:\n"
+ "video:x:986:alex,wmuser"), io::mode::READ);
+ defer free(buf);
+
+ let ent = nextgr(buf) as grent;
+ defer grent_finish(ent);
+
+ assert(ent.name == "root");
+ assert(ent.password == "x");
+ assert(ent.gid == 0);
+ assert(ent.userlist == "root");
+
+ let ent = nextgr(buf) as grent;
+ defer grent_finish(ent);
+
+ assert(ent.name == "mail");
+ assert(ent.password == "x");
+ assert(ent.gid == 12);
+ assert(ent.userlist == "");
+
+ let ent = nextgr(buf) as grent;
+ defer grent_finish(ent);
+
+ assert(ent.name == "video");
+ assert(ent.password == "x");
+ assert(ent.gid == 986);
+ assert(ent.userlist == "alex,wmuser");
+
+ // No more entries
+ assert(nextgr(buf) is io::EOF);
+};