Black Lives Matter. Support the Equal Justice Initiative.

Source file src/os/user/cgo_lookup_unix.go

Documentation: os/user

     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 (aix || darwin || dragonfly || freebsd || (!android && linux) || netbsd || openbsd || solaris) && cgo && !osusergo
     6  // +build aix darwin dragonfly freebsd !android,linux netbsd openbsd solaris
     7  // +build cgo
     8  // +build !osusergo
     9  
    10  package user
    11  
    12  import (
    13  	"fmt"
    14  	"strconv"
    15  	"strings"
    16  	"syscall"
    17  	"unsafe"
    18  )
    19  
    20  /*
    21  #cgo solaris CFLAGS: -D_POSIX_PTHREAD_SEMANTICS
    22  #include <unistd.h>
    23  #include <sys/types.h>
    24  #include <pwd.h>
    25  #include <grp.h>
    26  #include <stdlib.h>
    27  
    28  static int mygetpwuid_r(int uid, struct passwd *pwd,
    29  	char *buf, size_t buflen, struct passwd **result) {
    30  	return getpwuid_r(uid, pwd, buf, buflen, result);
    31  }
    32  
    33  static int mygetpwnam_r(const char *name, struct passwd *pwd,
    34  	char *buf, size_t buflen, struct passwd **result) {
    35  	return getpwnam_r(name, pwd, buf, buflen, result);
    36  }
    37  
    38  static int mygetgrgid_r(int gid, struct group *grp,
    39  	char *buf, size_t buflen, struct group **result) {
    40   return getgrgid_r(gid, grp, buf, buflen, result);
    41  }
    42  
    43  static int mygetgrnam_r(const char *name, struct group *grp,
    44  	char *buf, size_t buflen, struct group **result) {
    45   return getgrnam_r(name, grp, buf, buflen, result);
    46  }
    47  */
    48  import "C"
    49  
    50  func current() (*User, error) {
    51  	return lookupUnixUid(syscall.Getuid())
    52  }
    53  
    54  func lookupUser(username string) (*User, error) {
    55  	var pwd C.struct_passwd
    56  	var result *C.struct_passwd
    57  	nameC := make([]byte, len(username)+1)
    58  	copy(nameC, username)
    59  
    60  	buf := alloc(userBuffer)
    61  	defer buf.free()
    62  
    63  	err := retryWithBuffer(buf, func() syscall.Errno {
    64  		// mygetpwnam_r is a wrapper around getpwnam_r to avoid
    65  		// passing a size_t to getpwnam_r, because for unknown
    66  		// reasons passing a size_t to getpwnam_r doesn't work on
    67  		// Solaris.
    68  		return syscall.Errno(C.mygetpwnam_r((*C.char)(unsafe.Pointer(&nameC[0])),
    69  			&pwd,
    70  			(*C.char)(buf.ptr),
    71  			C.size_t(buf.size),
    72  			&result))
    73  	})
    74  	if err != nil {
    75  		return nil, fmt.Errorf("user: lookup username %s: %v", username, err)
    76  	}
    77  	if result == nil {
    78  		return nil, UnknownUserError(username)
    79  	}
    80  	return buildUser(&pwd), err
    81  }
    82  
    83  func lookupUserId(uid string) (*User, error) {
    84  	i, e := strconv.Atoi(uid)
    85  	if e != nil {
    86  		return nil, e
    87  	}
    88  	return lookupUnixUid(i)
    89  }
    90  
    91  func lookupUnixUid(uid int) (*User, error) {
    92  	var pwd C.struct_passwd
    93  	var result *C.struct_passwd
    94  
    95  	buf := alloc(userBuffer)
    96  	defer buf.free()
    97  
    98  	err := retryWithBuffer(buf, func() syscall.Errno {
    99  		// mygetpwuid_r is a wrapper around getpwuid_r to avoid using uid_t
   100  		// because C.uid_t(uid) for unknown reasons doesn't work on linux.
   101  		return syscall.Errno(C.mygetpwuid_r(C.int(uid),
   102  			&pwd,
   103  			(*C.char)(buf.ptr),
   104  			C.size_t(buf.size),
   105  			&result))
   106  	})
   107  	if err != nil {
   108  		return nil, fmt.Errorf("user: lookup userid %d: %v", uid, err)
   109  	}
   110  	if result == nil {
   111  		return nil, UnknownUserIdError(uid)
   112  	}
   113  	return buildUser(&pwd), nil
   114  }
   115  
   116  func buildUser(pwd *C.struct_passwd) *User {
   117  	u := &User{
   118  		Uid:      strconv.FormatUint(uint64(pwd.pw_uid), 10),
   119  		Gid:      strconv.FormatUint(uint64(pwd.pw_gid), 10),
   120  		Username: C.GoString(pwd.pw_name),
   121  		Name:     C.GoString(pwd.pw_gecos),
   122  		HomeDir:  C.GoString(pwd.pw_dir),
   123  	}
   124  	// The pw_gecos field isn't quite standardized. Some docs
   125  	// say: "It is expected to be a comma separated list of
   126  	// personal data where the first item is the full name of the
   127  	// user."
   128  	if i := strings.Index(u.Name, ","); i >= 0 {
   129  		u.Name = u.Name[:i]
   130  	}
   131  	return u
   132  }
   133  
   134  func lookupGroup(groupname string) (*Group, error) {
   135  	var grp C.struct_group
   136  	var result *C.struct_group
   137  
   138  	buf := alloc(groupBuffer)
   139  	defer buf.free()
   140  	cname := make([]byte, len(groupname)+1)
   141  	copy(cname, groupname)
   142  
   143  	err := retryWithBuffer(buf, func() syscall.Errno {
   144  		return syscall.Errno(C.mygetgrnam_r((*C.char)(unsafe.Pointer(&cname[0])),
   145  			&grp,
   146  			(*C.char)(buf.ptr),
   147  			C.size_t(buf.size),
   148  			&result))
   149  	})
   150  	if err != nil {
   151  		return nil, fmt.Errorf("user: lookup groupname %s: %v", groupname, err)
   152  	}
   153  	if result == nil {
   154  		return nil, UnknownGroupError(groupname)
   155  	}
   156  	return buildGroup(&grp), nil
   157  }
   158  
   159  func lookupGroupId(gid string) (*Group, error) {
   160  	i, e := strconv.Atoi(gid)
   161  	if e != nil {
   162  		return nil, e
   163  	}
   164  	return lookupUnixGid(i)
   165  }
   166  
   167  func lookupUnixGid(gid int) (*Group, error) {
   168  	var grp C.struct_group
   169  	var result *C.struct_group
   170  
   171  	buf := alloc(groupBuffer)
   172  	defer buf.free()
   173  
   174  	err := retryWithBuffer(buf, func() syscall.Errno {
   175  		// mygetgrgid_r is a wrapper around getgrgid_r to avoid using gid_t
   176  		// because C.gid_t(gid) for unknown reasons doesn't work on linux.
   177  		return syscall.Errno(C.mygetgrgid_r(C.int(gid),
   178  			&grp,
   179  			(*C.char)(buf.ptr),
   180  			C.size_t(buf.size),
   181  			&result))
   182  	})
   183  	if err != nil {
   184  		return nil, fmt.Errorf("user: lookup groupid %d: %v", gid, err)
   185  	}
   186  	if result == nil {
   187  		return nil, UnknownGroupIdError(strconv.Itoa(gid))
   188  	}
   189  	return buildGroup(&grp), nil
   190  }
   191  
   192  func buildGroup(grp *C.struct_group) *Group {
   193  	g := &Group{
   194  		Gid:  strconv.Itoa(int(grp.gr_gid)),
   195  		Name: C.GoString(grp.gr_name),
   196  	}
   197  	return g
   198  }
   199  
   200  type bufferKind C.int
   201  
   202  const (
   203  	userBuffer  = bufferKind(C._SC_GETPW_R_SIZE_MAX)
   204  	groupBuffer = bufferKind(C._SC_GETGR_R_SIZE_MAX)
   205  )
   206  
   207  func (k bufferKind) initialSize() C.size_t {
   208  	sz := C.sysconf(C.int(k))
   209  	if sz == -1 {
   210  		// DragonFly and FreeBSD do not have _SC_GETPW_R_SIZE_MAX.
   211  		// Additionally, not all Linux systems have it, either. For
   212  		// example, the musl libc returns -1.
   213  		return 1024
   214  	}
   215  	if !isSizeReasonable(int64(sz)) {
   216  		// Truncate.  If this truly isn't enough, retryWithBuffer will error on the first run.
   217  		return maxBufferSize
   218  	}
   219  	return C.size_t(sz)
   220  }
   221  
   222  type memBuffer struct {
   223  	ptr  unsafe.Pointer
   224  	size C.size_t
   225  }
   226  
   227  func alloc(kind bufferKind) *memBuffer {
   228  	sz := kind.initialSize()
   229  	return &memBuffer{
   230  		ptr:  C.malloc(sz),
   231  		size: sz,
   232  	}
   233  }
   234  
   235  func (mb *memBuffer) resize(newSize C.size_t) {
   236  	mb.ptr = C.realloc(mb.ptr, newSize)
   237  	mb.size = newSize
   238  }
   239  
   240  func (mb *memBuffer) free() {
   241  	C.free(mb.ptr)
   242  }
   243  
   244  // retryWithBuffer repeatedly calls f(), increasing the size of the
   245  // buffer each time, until f succeeds, fails with a non-ERANGE error,
   246  // or the buffer exceeds a reasonable limit.
   247  func retryWithBuffer(buf *memBuffer, f func() syscall.Errno) error {
   248  	for {
   249  		errno := f()
   250  		if errno == 0 {
   251  			return nil
   252  		} else if errno != syscall.ERANGE {
   253  			return errno
   254  		}
   255  		newSize := buf.size * 2
   256  		if !isSizeReasonable(int64(newSize)) {
   257  			return fmt.Errorf("internal buffer exceeds %d bytes", maxBufferSize)
   258  		}
   259  		buf.resize(newSize)
   260  	}
   261  }
   262  
   263  const maxBufferSize = 1 << 20
   264  
   265  func isSizeReasonable(sz int64) bool {
   266  	return sz > 0 && sz <= maxBufferSize
   267  }
   268  
   269  // Because we can't use cgo in tests:
   270  func structPasswdForNegativeTest() C.struct_passwd {
   271  	sp := C.struct_passwd{}
   272  	sp.pw_uid = 1<<32 - 2
   273  	sp.pw_gid = 1<<32 - 3
   274  	return sp
   275  }
   276  

View as plain text