Skip to content
Snippets Groups Projects
cgo_lookup_unix.go 4.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • // 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"
    
    	"strconv"
    
    Russ Cox's avatar
    Russ Cox committed
    	"syscall"
    
    func current() (*User, error) {
    
    	return lookupUnixUid(syscall.Getuid())
    
    func lookupUser(username string) (*User, error) {
    
    	var pwd _C_struct_passwd
    
    	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)
    	}
    
    		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
    	}
    
    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)
    	}
    
    		return nil, UnknownUserIdError(uid)
    	}
    	return buildUser(&pwd), nil
    }
    
    
    func buildUser(pwd *_C_struct_passwd) *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
    
    	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)
    	}
    
    		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)
    	}
    
    		return nil, UnknownGroupIdError(strconv.Itoa(gid))
    
    func buildGroup(grp *_C_struct_group) *Group {
    
    		Gid:  strconv.Itoa(int(_C_gr_gid(grp))),
    		Name: _C_GoString(_C_gr_name(grp)),
    
    type bufferKind _C_int
    
    	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)
    
    		if errno == 0 {
    			return nil
    		} else if errno != syscall.ERANGE {
    			return errno
    		}
    
    		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
    
    func structPasswdForNegativeTest() _C_struct_passwd {
    	sp := _C_struct_passwd{}
    	*_C_pw_uidp(&sp) = 1<<32 - 2
    	*_C_pw_gidp(&sp) = 1<<32 - 3