Skip to content

Commit 4826227

Browse files
committed
add os/user.Lookup
Signed-off-by: leongross <[email protected]>
1 parent 2733e37 commit 4826227

File tree

3 files changed

+129
-0
lines changed

3 files changed

+129
-0
lines changed

src/os/user/lookup_unix.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build linux && unix && !baremetal && !js && !wasi
6+
// +build linux,unix,!baremetal,!js,!wasi
7+
8+
package user
9+
10+
import (
11+
"bufio"
12+
"os"
13+
"strings"
14+
)
15+
16+
func lookupUser(username string) (*User, error) {
17+
f, err := os.Open("/etc/passwd")
18+
if err != nil {
19+
return nil, err
20+
}
21+
defer f.Close()
22+
23+
// parse file format <username>:<password>:<uid>:<gid>:<gecos>:<home>:<shell>
24+
lines := bufio.NewScanner(f)
25+
for lines.Scan() {
26+
line := lines.Text()
27+
fragments := strings.Split(line, ":")
28+
29+
if len(fragments) < 7 {
30+
continue
31+
}
32+
33+
if fragments[0] == username {
34+
return &User{
35+
Uid: fragments[2],
36+
Gid: fragments[3],
37+
Username: fragments[0],
38+
Name: fragments[4],
39+
HomeDir: fragments[5],
40+
}, nil
41+
}
42+
}
43+
44+
return nil, UnknownUserError(username)
45+
}

src/os/user/user.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,27 @@ type User struct {
3131
HomeDir string
3232
}
3333

34+
// UnknownUserError is returned by Lookup when a user cannot be found.
35+
type UnknownUserError string
36+
37+
func (e UnknownUserError) Error() string {
38+
return "user: unknown user " + string(e)
39+
}
40+
3441
// Current returns the current user.
3542
//
3643
// The first call will cache the current user information.
3744
// Subsequent calls will return the cached value and will not reflect
3845
// changes to the current user.
46+
// TODO: implement syscall.Getuid() and syscall.Getgid() to get the current user.
3947
func Current() (*User, error) {
4048
return nil, errors.New("user: Current not implemented")
4149
}
50+
51+
// Lookup looks up a user by username.
52+
//
53+
// If the user cannot be found, the returned error is of type UnknownUserError.
54+
// NOTE: This implementation does not support caching as the golang implementation does.
55+
func Lookup(username string) (*User, error) {
56+
return lookupUser(username)
57+
}

src/os/user_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build !baremetal && !js && !wasip1
6+
7+
package os_test
8+
9+
import (
10+
. "os/user"
11+
"runtime"
12+
"testing"
13+
)
14+
15+
// NOTE: This test requires some users to be present in the CI environment.
16+
// If the test fails, it may be because the users are not present.
17+
// We can guarantee that the root user is present on all systems.
18+
// Currently we can also guarantee that the user with UID 1001 and GID 127 is present on all systems.
19+
func TestUserLookup(t *testing.T) {
20+
if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
21+
t.Skip()
22+
}
23+
24+
testCases := map[string]struct {
25+
user User
26+
wantErr bool
27+
}{
28+
"root": {
29+
wantErr: false,
30+
user: User{
31+
Uid: "0",
32+
Gid: "0",
33+
Username: "root",
34+
Name: "root",
35+
HomeDir: "/root",
36+
},
37+
},
38+
"user-" + runtime.GOOS: {
39+
wantErr: false,
40+
user: User{
41+
Uid: "1001",
42+
Gid: "127",
43+
Username: "user",
44+
Name: "user",
45+
HomeDir: "/home/user",
46+
},
47+
},
48+
"error": {
49+
wantErr: true,
50+
user: User{
51+
Uid: "1000000",
52+
Gid: "1000000",
53+
Username: "nonexistentuser",
54+
Name: "nonexistentuser",
55+
HomeDir: "/home/nonexistentuser",
56+
},
57+
},
58+
}
59+
60+
for name, tc := range testCases {
61+
t.Run(name, func(t *testing.T) {
62+
user, err := Lookup(tc.user.Username)
63+
if (err != nil && !tc.wantErr) || (err == nil && tc.wantErr) {
64+
t.Fatalf("Lookup(%q) = %v; want %v, got error %v", tc.user.Username, user, tc.user, err)
65+
}
66+
})
67+
}
68+
}

0 commit comments

Comments
 (0)