Source file src/os/user/cgo_lookup_unix.go

     1  // Copyright 2011 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 (cgo || darwin) && !osusergo && unix && !android
     6  
     7  package user
     8  
     9  import (
    10  	"fmt"
    11  	"runtime"
    12  	"strconv"
    13  	"strings"
    14  	"syscall"
    15  	"unsafe"
    16  )
    17  
    18  func current() (*User, error) {
    19  	return lookupUnixUid(syscall.Getuid())
    20  }
    21  
    22  func lookupUser(username string) (*User, error) {
    23  	var pwd _C_struct_passwd
    24  	var found bool
    25  	nameC := make([]byte, len(username)+1)
    26  	copy(nameC, username)
    27  
    28  	err := retryWithBuffer(userBuffer, func(buf []byte) syscall.Errno {
    29  		var errno syscall.Errno
    30  		pwd, found, errno = _C_getpwnam_r((*_C_char)(unsafe.Pointer(&nameC[0])),
    31  			(*_C_char)(unsafe.Pointer(&buf[0])), _C_size_t(len(buf)))
    32  		return errno
    33  	})
    34  	if err == syscall.ENOENT || (err == nil && !found) {
    35  		return nil, UnknownUserError(username)
    36  	}
    37  	if err != nil {
    38  		return nil, fmt.Errorf("user: lookup username %s: %v", username, err)
    39  	}
    40  	return buildUser(&pwd), err
    41  }
    42  
    43  func lookupUserId(uid string) (*User, error) {
    44  	i, e := strconv.Atoi(uid)
    45  	if e != nil {
    46  		return nil, e
    47  	}
    48  	return lookupUnixUid(i)
    49  }
    50  
    51  func lookupUnixUid(uid int) (*User, error) {
    52  	var pwd _C_struct_passwd
    53  	var found bool
    54  
    55  	err := retryWithBuffer(userBuffer, func(buf []byte) syscall.Errno {
    56  		var errno syscall.Errno
    57  		pwd, found, errno = _C_getpwuid_r(_C_uid_t(uid),
    58  			(*_C_char)(unsafe.Pointer(&buf[0])), _C_size_t(len(buf)))
    59  		return errno
    60  	})
    61  	if err == syscall.ENOENT || (err == nil && !found) {
    62  		return nil, UnknownUserIdError(uid)
    63  	}
    64  	if err != nil {
    65  		return nil, fmt.Errorf("user: lookup userid %d: %v", uid, err)
    66  	}
    67  	return buildUser(&pwd), nil
    68  }
    69  
    70  func buildUser(pwd *_C_struct_passwd) *User {
    71  	u := &User{
    72  		Uid:      strconv.FormatUint(uint64(_C_pw_uid(pwd)), 10),
    73  		Gid:      strconv.FormatUint(uint64(_C_pw_gid(pwd)), 10),
    74  		Username: _C_GoString(_C_pw_name(pwd)),
    75  		Name:     _C_GoString(_C_pw_gecos(pwd)),
    76  		HomeDir:  _C_GoString(_C_pw_dir(pwd)),
    77  	}
    78  	// The pw_gecos field isn't quite standardized. Some docs
    79  	// say: "It is expected to be a comma separated list of
    80  	// personal data where the first item is the full name of the
    81  	// user."
    82  	u.Name, _, _ = strings.Cut(u.Name, ",")
    83  	return u
    84  }
    85  
    86  func lookupGroup(groupname string) (*Group, error) {
    87  	var grp _C_struct_group
    88  	var found bool
    89  
    90  	cname := make([]byte, len(groupname)+1)
    91  	copy(cname, groupname)
    92  
    93  	err := retryWithBuffer(groupBuffer, func(buf []byte) syscall.Errno {
    94  		var errno syscall.Errno
    95  		grp, found, errno = _C_getgrnam_r((*_C_char)(unsafe.Pointer(&cname[0])),
    96  			(*_C_char)(unsafe.Pointer(&buf[0])), _C_size_t(len(buf)))
    97  		return errno
    98  	})
    99  	if err == syscall.ENOENT || (err == nil && !found) {
   100  		return nil, UnknownGroupError(groupname)
   101  	}
   102  	if err != nil {
   103  		return nil, fmt.Errorf("user: lookup groupname %s: %v", groupname, err)
   104  	}
   105  	return buildGroup(&grp), nil
   106  }
   107  
   108  func lookupGroupId(gid string) (*Group, error) {
   109  	i, e := strconv.Atoi(gid)
   110  	if e != nil {
   111  		return nil, e
   112  	}
   113  	return lookupUnixGid(i)
   114  }
   115  
   116  func lookupUnixGid(gid int) (*Group, error) {
   117  	var grp _C_struct_group
   118  	var found bool
   119  
   120  	err := retryWithBuffer(groupBuffer, func(buf []byte) syscall.Errno {
   121  		var errno syscall.Errno
   122  		grp, found, errno = _C_getgrgid_r(_C_gid_t(gid),
   123  			(*_C_char)(unsafe.Pointer(&buf[0])), _C_size_t(len(buf)))
   124  		return syscall.Errno(errno)
   125  	})
   126  	if err == syscall.ENOENT || (err == nil && !found) {
   127  		return nil, UnknownGroupIdError(strconv.Itoa(gid))
   128  	}
   129  	if err != nil {
   130  		return nil, fmt.Errorf("user: lookup groupid %d: %v", gid, err)
   131  	}
   132  	return buildGroup(&grp), nil
   133  }
   134  
   135  func buildGroup(grp *_C_struct_group) *Group {
   136  	g := &Group{
   137  		Gid:  strconv.Itoa(int(_C_gr_gid(grp))),
   138  		Name: _C_GoString(_C_gr_name(grp)),
   139  	}
   140  	return g
   141  }
   142  
   143  type bufferKind _C_int
   144  
   145  var (
   146  	userBuffer  = bufferKind(_C__SC_GETPW_R_SIZE_MAX)
   147  	groupBuffer = bufferKind(_C__SC_GETGR_R_SIZE_MAX)
   148  )
   149  
   150  func (k bufferKind) initialSize() _C_size_t {
   151  	sz := _C_sysconf(_C_int(k))
   152  	if sz == -1 {
   153  		// DragonFly and FreeBSD do not have _SC_GETPW_R_SIZE_MAX.
   154  		// Additionally, not all Linux systems have it, either. For
   155  		// example, the musl libc returns -1.
   156  		return 1024
   157  	}
   158  	if !isSizeReasonable(int64(sz)) {
   159  		// Truncate.  If this truly isn't enough, retryWithBuffer will error on the first run.
   160  		return maxBufferSize
   161  	}
   162  	return _C_size_t(sz)
   163  }
   164  
   165  // retryWithBuffer repeatedly calls f(), increasing the size of the
   166  // buffer each time, until f succeeds, fails with a non-ERANGE error,
   167  // or the buffer exceeds a reasonable limit.
   168  func retryWithBuffer(kind bufferKind, f func([]byte) syscall.Errno) error {
   169  	buf := make([]byte, kind.initialSize())
   170  	for {
   171  		errno := f(buf)
   172  		if errno == 0 {
   173  			return nil
   174  		} else if runtime.GOOS == "aix" && errno+1 == 0 {
   175  			// On AIX getpwuid_r appears to return -1,
   176  			// not ERANGE, on buffer overflow.
   177  		} else if errno != syscall.ERANGE {
   178  			return errno
   179  		}
   180  		newSize := len(buf) * 2
   181  		if !isSizeReasonable(int64(newSize)) {
   182  			return fmt.Errorf("internal buffer exceeds %d bytes", maxBufferSize)
   183  		}
   184  		buf = make([]byte, newSize)
   185  	}
   186  }
   187  
   188  const maxBufferSize = 1 << 20
   189  
   190  func isSizeReasonable(sz int64) bool {
   191  	return sz > 0 && sz <= maxBufferSize
   192  }
   193  
   194  // Because we can't use cgo in tests:
   195  func structPasswdForNegativeTest() _C_struct_passwd {
   196  	sp := _C_struct_passwd{}
   197  	*_C_pw_uidp(&sp) = 1<<32 - 2
   198  	*_C_pw_gidp(&sp) = 1<<32 - 3
   199  	return sp
   200  }
   201  

View as plain text