commit 88bda176d9875340a95c1c5acb156d8ff35e298d
parent 316a40ee12d8b90ae1f676bbf195de32cb6f2fc1
Author: Alexey Yerin <yyp@disroot.org>
Date: Thu, 25 Mar 2021 20:21:21 +0300
Add unix::passwd module for /etc/passwd parsing
Diffstat:
3 files changed, 170 insertions(+), 1 deletion(-)
diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib
@@ -532,6 +532,13 @@ unicode() {
gen_ssa unicode
}
+unix_passwd() {
+ printf '# unix::passwd\n'
+ gen_srcs unix::passwd \
+ passwd.ha
+ gen_ssa unix::passwd bufio io os strconv strings
+}
+
printf '# This file is generated by the gen-stdlib script, do not edit it by hand\n\n'
modules="ascii
@@ -571,7 +578,8 @@ strio
temp
time
types
-unicode"
+unicode
+unix_passwd"
stdlib() {
rt
for module in $modules; do
diff --git a/stdlib.mk b/stdlib.mk
@@ -182,6 +182,9 @@ hare_stdlib_deps+=$(stdlib_types)
stdlib_unicode=$(HARECACHE)/unicode/unicode.o
hare_stdlib_deps+=$(stdlib_unicode)
+stdlib_unix_passwd=$(HARECACHE)/unix/passwd/unix_passwd.o
+hare_stdlib_deps+=$(stdlib_unix_passwd)
+
# ascii
stdlib_ascii_srcs= \
$(STDLIB)/ascii/ctype.ha \
@@ -647,6 +650,16 @@ $(HARECACHE)/unicode/unicode.ssa: $(stdlib_unicode_srcs) $(stdlib_rt)
@HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nunicode \
-t$(HARECACHE)/unicode/unicode.td $(stdlib_unicode_srcs)
+# unix::passwd
+stdlib_unix_passwd_srcs= \
+ $(STDLIB)/unix/passwd/passwd.ha
+
+$(HARECACHE)/unix/passwd/unix_passwd.ssa: $(stdlib_unix_passwd_srcs) $(stdlib_rt) $(stdlib_bufio) $(stdlib_io) $(stdlib_os) $(stdlib_strconv) $(stdlib_strings)
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(HARECACHE)/unix/passwd
+ @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nunix::passwd \
+ -t$(HARECACHE)/unix/passwd/unix_passwd.td $(stdlib_unix_passwd_srcs)
+
# rt
testlib_rt_srcs= \
$(STDLIB)/rt/$(PLATFORM)/env.ha \
@@ -831,6 +844,9 @@ hare_testlib_deps+=$(testlib_types)
testlib_unicode=$(TESTCACHE)/unicode/unicode.o
hare_testlib_deps+=$(testlib_unicode)
+testlib_unix_passwd=$(TESTCACHE)/unix/passwd/unix_passwd.o
+hare_testlib_deps+=$(testlib_unix_passwd)
+
# ascii
testlib_ascii_srcs= \
$(STDLIB)/ascii/ctype.ha \
@@ -1306,3 +1322,13 @@ $(TESTCACHE)/unicode/unicode.ssa: $(testlib_unicode_srcs) $(testlib_rt)
@HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nunicode \
-t$(TESTCACHE)/unicode/unicode.td $(testlib_unicode_srcs)
+# unix::passwd
+testlib_unix_passwd_srcs= \
+ $(STDLIB)/unix/passwd/passwd.ha
+
+$(TESTCACHE)/unix/passwd/unix_passwd.ssa: $(testlib_unix_passwd_srcs) $(testlib_rt) $(testlib_bufio) $(testlib_io) $(testlib_os) $(testlib_strconv) $(testlib_strings)
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(TESTCACHE)/unix/passwd
+ @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nunix::passwd \
+ -t$(TESTCACHE)/unix/passwd/unix_passwd.td $(testlib_unix_passwd_srcs)
+
diff --git a/unix/passwd/passwd.ha b/unix/passwd/passwd.ha
@@ -0,0 +1,135 @@
+use bufio;
+use io;
+use os;
+use strconv;
+use strings;
+
+// An invalid entry was encountered during parsing.
+export type invalid = void!;
+
+// A Unix-like password database entry.
+export type pwent = struct {
+ // Login name
+ username: str,
+ // Optional encrypted password
+ password: str,
+ // Numerical user ID
+ uid: uint,
+ // Numerical group ID
+ gid: uint,
+ // User name or comment field
+ comment: str,
+ // User home directory
+ homedir: str,
+ // Optional user command interpreter
+ shell: str,
+};
+
+// Reads a Unix-like password entry from a stream. The caller must free the
+// result using [unix::passwd::pwent_finish].
+export fn nextpw(stream: *io::stream) (pwent | io::EOF | io::error | invalid) = {
+ let line = match (bufio::scanline(stream)?) {
+ io::EOF => return io::EOF,
+ ln: []u8 => ln,
+ };
+ let line = match (strings::try_fromutf8(line)) {
+ s: str => s,
+ * => return invalid,
+ };
+
+ let fields = strings::split(line, ":");
+ defer free(fields);
+
+ if (len(fields) != 7) {
+ return invalid;
+ };
+
+ let uid = match (strconv::stou(fields[2])) {
+ u: uint => u,
+ * => return invalid,
+ };
+
+ let gid = match (strconv::stou(fields[3])) {
+ u: uint => u,
+ * => return invalid,
+ };
+
+ return pwent {
+ // Borrows the return value of bufio::scanline
+ username = fields[0],
+ password = fields[1],
+ uid = uid,
+ gid = gid,
+ comment = fields[4],
+ homedir = fields[5],
+ shell = fields[6],
+ };
+};
+
+// Frees resources associated with [pwent].
+export fn pwent_finish(ent: pwent) void = {
+ // pwent fields are sliced from one allocated string returned by
+ // bufio::scanline. Freeing the first field frees the entire string in
+ // one go.
+ free(ent.username);
+};
+
+// Looks up a user by name in a Unix-like password file. It expects a password
+// database file at /etc/passwd. Aborts if that file doesn't exist or is not
+// properly formatted.
+//
+// See [unix::passwd::nextpw] for low-level parsing API.
+export fn getuser(username: str) (pwent | void) = {
+ let file = match (os::open("/etc/passwd")) {
+ s: *io::stream => s,
+ * => abort("Can't open /etc/passwd"),
+ };
+ defer io::close(file);
+
+ for (true) {
+ let ent = match (nextpw(file)) {
+ e: pwent => e,
+ io::EOF => break,
+ * => abort("/etc/passwd entry is invalid"),
+ };
+ defer pwent_finish(ent);
+
+ if (ent.username == username) {
+ return ent;
+ };
+ };
+
+ return;
+};
+
+@test fn nextpw() void = {
+ let buf = bufio::fixed(strings::toutf8(
+ "sircmpwn:x:1000:1000:sircmpwn's comment:/home/sircmpwn:/bin/mrsh\n"
+ "alex:x:1001:1001::/home/alex:/bin/zsh"), io::mode::READ);
+ defer free(buf);
+
+ let ent = nextpw(buf) as pwent;
+ defer pwent_finish(ent);
+
+ assert(ent.username == "sircmpwn");
+ assert(ent.password == "x");
+ assert(ent.uid == 1000);
+ assert(ent.gid == 1000);
+ assert(ent.comment == "sircmpwn's comment");
+ assert(ent.homedir == "/home/sircmpwn");
+ assert(ent.shell == "/bin/mrsh");
+
+ let ent = nextpw(buf) as pwent;
+ defer pwent_finish(ent);
+
+ assert(ent.username == "alex");
+ assert(ent.password == "x");
+ assert(ent.uid == 1001);
+ assert(ent.gid == 1001);
+ assert(ent.comment == "");
+ assert(ent.homedir == "/home/alex");
+ assert(ent.shell == "/bin/zsh");
+
+ // No more entries
+ assert(nextpw(buf) is io::EOF);
+};