From d9ef0e17e1b6d6e236adabce8f69e422504fef95 Mon Sep 17 00:00:00 2001 From: leongross Date: Fri, 10 May 2024 12:33:54 +0200 Subject: [PATCH] add LookupId, LookupGroupId Signed-off-by: leongross --- src/os/user/lookup_unix.go | 77 +++++++++++++++++++++++++++++++++++++- src/os/user/user.go | 43 ++++++++++++++++++++- src/os/user_test.go | 10 +++++ 3 files changed, 128 insertions(+), 2 deletions(-) diff --git a/src/os/user/lookup_unix.go b/src/os/user/lookup_unix.go index 5db6b83982..8e4c894eb8 100644 --- a/src/os/user/lookup_unix.go +++ b/src/os/user/lookup_unix.go @@ -10,11 +10,17 @@ package user import ( "bufio" "os" + "strconv" "strings" ) +const ( + userFile = "/etc/passwd" + groupFile = "/etc/group" +) + func lookupUser(username string) (*User, error) { - f, err := os.Open("/etc/passwd") + f, err := os.Open(userFile) if err != nil { return nil, err } @@ -43,3 +49,72 @@ func lookupUser(username string) (*User, error) { return nil, UnknownUserError(username) } + +func lookupUserId(uid string) (*User, error) { + f, err := os.Open(userFile) + if err != nil { + return nil, err + } + defer f.Close() + + // parse file format :::::: + lines := bufio.NewScanner(f) + for lines.Scan() { + line := lines.Text() + fragments := strings.Split(line, ":") + + if len(fragments) < 7 { + continue + } + + if fragments[2] == uid { + return &User{ + Uid: fragments[2], + Gid: fragments[3], + Username: fragments[0], + Name: fragments[4], + HomeDir: fragments[5], + }, nil + } + } + + id, err := strconv.Atoi(uid) + if err != nil { + return nil, err + } + + return nil, UnknownUserIdError(id) +} + +func lookupGroupId(gid string) (*Group, error) { + f, err := os.Open(groupFile) + if err != nil { + return nil, err + } + defer f.Close() + + // parse file format group_name:password:GID:user_list + // group_name: the name of the group. + // password: the (encrypted) group password. If this field is empty, no password is needed. + // GID: the numeric group ID. + // user_list: a list of the usernames that are members of this group, separated by commas. + + lines := bufio.NewScanner(f) + for lines.Scan() { + line := lines.Text() + fragments := strings.Split(line, ":") + + if len(fragments) < 4 { + continue + } + + if fragments[2] == gid { + return &Group{ + Gid: fragments[2], + Name: fragments[0], + }, nil + } + } + + return nil, UnknownGroupIdError(gid) +} diff --git a/src/os/user/user.go b/src/os/user/user.go index dfeaad1842..cba54c6c75 100644 --- a/src/os/user/user.go +++ b/src/os/user/user.go @@ -4,7 +4,10 @@ package user -import "errors" +import ( + "errors" + "strconv" +) // User represents a user account. type User struct { @@ -38,6 +41,29 @@ func (e UnknownUserError) Error() string { return "user: unknown user " + string(e) } +// Group represents a grouping of users. +// +// On POSIX systems Gid contains a decimal number representing the group ID. +type Group struct { + Gid string // group ID + Name string // group name +} + +// UnknownGroupIdError is returned by [LookupGroupId] when +// a group cannot be found. +type UnknownGroupIdError string + +func (e UnknownGroupIdError) Error() string { + return "group: unknown groupid " + string(e) +} + +// UnknownUserIdError is returned by [LookupId] when a user cannot be found. +type UnknownUserIdError int + +func (e UnknownUserIdError) Error() string { + return "user: unknown userid " + strconv.Itoa(int(e)) +} + // Current returns the current user. // // The first call will cache the current user information. @@ -55,3 +81,18 @@ func Current() (*User, error) { func Lookup(username string) (*User, error) { return lookupUser(username) } + +// LookupId looks up a user by userid. If the user cannot be found, the +// returned error is of type UnknownUserIdError. +func LookupId(uid string) (*User, error) { + if u, err := Current(); err == nil && u.Uid == uid { + return u, err + } + return lookupUserId(uid) +} + +// LookupGroupId looks up a group by groupid. If the group cannot be found, the +// returned error is of type [UnknownGroupIdError]. +func LookupGroupId(gid string) (*Group, error) { + return lookupGroupId(gid) +} diff --git a/src/os/user_test.go b/src/os/user_test.go index a68ccf870b..c3fd15c25a 100644 --- a/src/os/user_test.go +++ b/src/os/user_test.go @@ -54,6 +54,16 @@ func TestUserLookup(t *testing.T) { if (err != nil && !tc.wantErr) || (err == nil && tc.wantErr) { t.Fatalf("Lookup(%q) = %v; want %v, got error %v", tc.user.Username, user, tc.user, err) } + + userId, err := LookupId(tc.user.Uid) + if (err != nil && !tc.wantErr) || (err == nil && tc.wantErr) { + t.Fatalf("LookupId(%q) = %v; want %v, got error %v", tc.user.Username, userId, tc.user, err) + } + + group, err := LookupGroupId(tc.user.Gid) + if (err != nil && !tc.wantErr) || (err == nil && tc.wantErr) { + t.Fatalf("LookupGroupId(%q) = %v; want %v, got error %v", tc.user.Gid, group, tc.user.Gid, err) + } }) } }