blob: 3735971eb4b7b4b87a456ea1d6b0117c6f087889 [file] [log] [blame] [edit]
// 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(startSize bufferKind, f func([]byte) syscall.Errno) error {
buf := make([]byte, startSize)
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
}