hare

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

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:
Mscripts/gen-stdlib | 10+++++++++-
Mstdlib.mk | 26++++++++++++++++++++++++++
Aunix/passwd/passwd.ha | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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); +};