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 7package user 8 9import ( 10 "fmt" 11 "runtime" 12 "strconv" 13 "strings" 14 "syscall" 15 "unsafe" 16) 17 18func current() (*User, error) { 19 return lookupUnixUid(syscall.Getuid()) 20} 21 22func 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 43func 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 51func 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 70func 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 86func 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 108func 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 116func 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 135func 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 143type bufferKind _C_int 144 145var ( 146 userBuffer = bufferKind(_C__SC_GETPW_R_SIZE_MAX) 147 groupBuffer = bufferKind(_C__SC_GETGR_R_SIZE_MAX) 148) 149 150func (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. 168func 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 188const maxBufferSize = 1 << 20 189 190func isSizeReasonable(sz int64) bool { 191 return sz > 0 && sz <= maxBufferSize 192} 193 194// Because we can't use cgo in tests: 195func 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