diff options
Diffstat (limited to 'src/os/user/cgo_lookup_unix.go')
-rw-r--r-- | src/os/user/cgo_lookup_unix.go | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/src/os/user/cgo_lookup_unix.go b/src/os/user/cgo_lookup_unix.go new file mode 100644 index 0000000..402429b --- /dev/null +++ b/src/os/user/cgo_lookup_unix.go @@ -0,0 +1,200 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (cgo || darwin) && !osusergo && unix && !android + +package user + +import ( + "fmt" + "runtime" + "strconv" + "strings" + "syscall" + "unsafe" +) + +func current() (*User, error) { + return lookupUnixUid(syscall.Getuid()) +} + +func lookupUser(username string) (*User, error) { + var pwd _C_struct_passwd + var found bool + nameC := make([]byte, len(username)+1) + copy(nameC, username) + + err := retryWithBuffer(userBuffer, func(buf []byte) syscall.Errno { + var errno syscall.Errno + pwd, found, errno = _C_getpwnam_r((*_C_char)(unsafe.Pointer(&nameC[0])), + (*_C_char)(unsafe.Pointer(&buf[0])), _C_size_t(len(buf))) + return errno + }) + if err != nil { + return nil, fmt.Errorf("user: lookup username %s: %v", username, err) + } + if !found { + return nil, UnknownUserError(username) + } + return buildUser(&pwd), err +} + +func lookupUserId(uid string) (*User, error) { + i, e := strconv.Atoi(uid) + if e != nil { + return nil, e + } + return lookupUnixUid(i) +} + +func lookupUnixUid(uid int) (*User, error) { + var pwd _C_struct_passwd + var found bool + + err := retryWithBuffer(userBuffer, func(buf []byte) syscall.Errno { + var errno syscall.Errno + pwd, found, errno = _C_getpwuid_r(_C_uid_t(uid), + (*_C_char)(unsafe.Pointer(&buf[0])), _C_size_t(len(buf))) + return errno + }) + if err != nil { + return nil, fmt.Errorf("user: lookup userid %d: %v", uid, err) + } + if !found { + return nil, UnknownUserIdError(uid) + } + return buildUser(&pwd), nil +} + +func buildUser(pwd *_C_struct_passwd) *User { + u := &User{ + Uid: strconv.FormatUint(uint64(_C_pw_uid(pwd)), 10), + Gid: strconv.FormatUint(uint64(_C_pw_gid(pwd)), 10), + Username: _C_GoString(_C_pw_name(pwd)), + Name: _C_GoString(_C_pw_gecos(pwd)), + HomeDir: _C_GoString(_C_pw_dir(pwd)), + } + // The pw_gecos field isn't quite standardized. Some docs + // say: "It is expected to be a comma separated list of + // personal data where the first item is the full name of the + // user." + u.Name, _, _ = strings.Cut(u.Name, ",") + return u +} + +func lookupGroup(groupname string) (*Group, error) { + var grp _C_struct_group + var found bool + + cname := make([]byte, len(groupname)+1) + copy(cname, groupname) + + err := retryWithBuffer(groupBuffer, func(buf []byte) syscall.Errno { + var errno syscall.Errno + grp, found, errno = _C_getgrnam_r((*_C_char)(unsafe.Pointer(&cname[0])), + (*_C_char)(unsafe.Pointer(&buf[0])), _C_size_t(len(buf))) + return errno + }) + if err != nil { + return nil, fmt.Errorf("user: lookup groupname %s: %v", groupname, err) + } + if !found { + return nil, UnknownGroupError(groupname) + } + return buildGroup(&grp), nil +} + +func lookupGroupId(gid string) (*Group, error) { + i, e := strconv.Atoi(gid) + if e != nil { + return nil, e + } + return lookupUnixGid(i) +} + +func lookupUnixGid(gid int) (*Group, error) { + var grp _C_struct_group + var found bool + + err := retryWithBuffer(groupBuffer, func(buf []byte) syscall.Errno { + var errno syscall.Errno + grp, found, errno = _C_getgrgid_r(_C_gid_t(gid), + (*_C_char)(unsafe.Pointer(&buf[0])), _C_size_t(len(buf))) + return syscall.Errno(errno) + }) + if err != nil { + return nil, fmt.Errorf("user: lookup groupid %d: %v", gid, err) + } + if !found { + return nil, UnknownGroupIdError(strconv.Itoa(gid)) + } + return buildGroup(&grp), nil +} + +func buildGroup(grp *_C_struct_group) *Group { + g := &Group{ + Gid: strconv.Itoa(int(_C_gr_gid(grp))), + Name: _C_GoString(_C_gr_name(grp)), + } + return g +} + +type bufferKind _C_int + +var ( + userBuffer = bufferKind(_C__SC_GETPW_R_SIZE_MAX) + groupBuffer = bufferKind(_C__SC_GETGR_R_SIZE_MAX) +) + +func (k bufferKind) initialSize() _C_size_t { + sz := _C_sysconf(_C_int(k)) + if sz == -1 { + // DragonFly and FreeBSD do not have _SC_GETPW_R_SIZE_MAX. + // Additionally, not all Linux systems have it, either. For + // example, the musl libc returns -1. + return 1024 + } + if !isSizeReasonable(int64(sz)) { + // Truncate. If this truly isn't enough, retryWithBuffer will error on the first run. + return maxBufferSize + } + return _C_size_t(sz) +} + +// retryWithBuffer repeatedly calls f(), increasing the size of the +// buffer each time, until f succeeds, fails with a non-ERANGE error, +// or the buffer exceeds a reasonable limit. +func retryWithBuffer(kind bufferKind, f func([]byte) syscall.Errno) error { + buf := make([]byte, kind.initialSize()) + for { + errno := f(buf) + if errno == 0 { + return nil + } else if runtime.GOOS == "aix" && errno+1 == 0 { + // On AIX getpwuid_r appears to return -1, + // not ERANGE, on buffer overflow. + } else if errno != syscall.ERANGE { + return errno + } + newSize := len(buf) * 2 + if !isSizeReasonable(int64(newSize)) { + return fmt.Errorf("internal buffer exceeds %d bytes", maxBufferSize) + } + buf = make([]byte, newSize) + } +} + +const maxBufferSize = 1 << 20 + +func isSizeReasonable(sz int64) bool { + return sz > 0 && sz <= maxBufferSize +} + +// Because we can't use cgo in tests: +func structPasswdForNegativeTest() _C_struct_passwd { + sp := _C_struct_passwd{} + *_C_pw_uidp(&sp) = 1<<32 - 2 + *_C_pw_gidp(&sp) = 1<<32 - 3 + return sp +} |