github.com/geraldss/go/src@v0.0.0-20210511222824-ac7d0ebfc235/os/user/cgo_lookup_unix.go (about)

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