...

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

View as plain text