cgo_lookup_unix.mx raw

   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  	"bytes"
  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), nil
  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, _, _ = bytes.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