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 7 8package user 9 10import ( 11 "fmt" 12 "strconv" 13 "strings" 14 "syscall" 15 "unsafe" 16) 17 18// bytePtrToString takes a NUL-terminated array of bytes and convert 19// it to a Go string. 20func bytePtrToString(p *byte) string { 21 if p == nil { 22 return "" 23 } 24 a := (*[10000]byte)(unsafe.Pointer(p)) 25 i := 0 26 for a[i] != 0 { 27 i++ 28 } 29 return string(a[:i]) 30} 31 32func current() (*User, error) { 33 return lookupUnixUid(syscall.Getuid()) 34} 35 36func lookupUser(username string) (*User, error) { 37 var pwd syscall.Passwd 38 var result *syscall.Passwd 39 p := syscall.StringBytePtr(username) 40 41 buf := alloc(userBuffer) 42 defer buf.free() 43 44 err := retryWithBuffer(buf, func() syscall.Errno { 45 syscall.Entersyscall() 46 rv := libc_getpwnam_r(p, 47 &pwd, 48 buf.ptr, 49 buf.size, 50 &result) 51 syscall.Exitsyscall() 52 if rv != 0 { 53 return syscall.GetErrno() 54 } 55 return 0 56 }) 57 if err != nil { 58 return nil, fmt.Errorf("user: lookup username %s: %v", username, err) 59 } 60 if result == nil { 61 return nil, UnknownUserError(username) 62 } 63 return buildUser(&pwd), err 64} 65 66func lookupUserId(uid string) (*User, error) { 67 i, e := strconv.Atoi(uid) 68 if e != nil { 69 return nil, e 70 } 71 return lookupUnixUid(i) 72} 73 74func lookupUnixUid(uid int) (*User, error) { 75 var pwd syscall.Passwd 76 var result *syscall.Passwd 77 78 buf := alloc(userBuffer) 79 defer buf.free() 80 81 err := retryWithBuffer(buf, func() syscall.Errno { 82 syscall.Entersyscall() 83 rv := libc_getpwuid_r(syscall.Uid_t(uid), 84 &pwd, 85 buf.ptr, 86 buf.size, 87 &result) 88 syscall.Exitsyscall() 89 if rv != 0 { 90 return syscall.GetErrno() 91 } 92 return 0 93 }) 94 if err != nil { 95 return nil, fmt.Errorf("user: lookup userid %d: %v", uid, err) 96 } 97 if result == nil { 98 return nil, UnknownUserIdError(uid) 99 } 100 return buildUser(&pwd), nil 101} 102 103func buildUser(pwd *syscall.Passwd) *User { 104 u := &User{ 105 Uid: strconv.FormatUint(uint64(pwd.Pw_uid), 10), 106 Gid: strconv.FormatUint(uint64(pwd.Pw_gid), 10), 107 Username: bytePtrToString((*byte)(unsafe.Pointer(pwd.Pw_name))), 108 Name: bytePtrToString((*byte)(unsafe.Pointer(pwd.Pw_gecos))), 109 HomeDir: bytePtrToString((*byte)(unsafe.Pointer(pwd.Pw_dir))), 110 } 111 // The pw_gecos field isn't quite standardized. Some docs 112 // say: "It is expected to be a comma separated list of 113 // personal data where the first item is the full name of the 114 // user." 115 if i := strings.Index(u.Name, ","); i >= 0 { 116 u.Name = u.Name[:i] 117 } 118 return u 119} 120 121func currentGroup() (*Group, error) { 122 return lookupUnixGid(syscall.Getgid()) 123} 124 125func lookupGroup(groupname string) (*Group, error) { 126 var grp syscall.Group 127 var result *syscall.Group 128 129 buf := alloc(groupBuffer) 130 defer buf.free() 131 p := syscall.StringBytePtr(groupname) 132 133 err := retryWithBuffer(buf, func() syscall.Errno { 134 syscall.Entersyscall() 135 rv := libc_getgrnam_r(p, 136 &grp, 137 buf.ptr, 138 buf.size, 139 &result) 140 syscall.Exitsyscall() 141 if rv != 0 { 142 return syscall.GetErrno() 143 } 144 return 0 145 }) 146 if err != nil { 147 return nil, fmt.Errorf("user: lookup groupname %s: %v", groupname, err) 148 } 149 if result == nil { 150 return nil, UnknownGroupError(groupname) 151 } 152 return buildGroup(&grp), nil 153} 154 155func lookupGroupId(gid string) (*Group, error) { 156 i, e := strconv.Atoi(gid) 157 if e != nil { 158 return nil, e 159 } 160 return lookupUnixGid(i) 161} 162 163func lookupUnixGid(gid int) (*Group, error) { 164 var grp syscall.Group 165 var result *syscall.Group 166 167 buf := alloc(groupBuffer) 168 defer buf.free() 169 170 err := retryWithBuffer(buf, func() syscall.Errno { 171 syscall.Entersyscall() 172 rv := libc_getgrgid_r(syscall.Gid_t(gid), 173 &grp, 174 buf.ptr, 175 buf.size, 176 &result) 177 syscall.Exitsyscall() 178 if rv != 0 { 179 return syscall.GetErrno() 180 } 181 return 0 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 192func buildGroup(grp *syscall.Group) *Group { 193 g := &Group{ 194 Gid: strconv.Itoa(int(grp.Gr_gid)), 195 Name: bytePtrToString((*byte)(unsafe.Pointer(grp.Gr_name))), 196 } 197 return g 198} 199 200type bufferKind int 201 202const ( 203 userBuffer = bufferKind(syscall.SC_GETPW_R_SIZE_MAX) 204 groupBuffer = bufferKind(syscall.SC_GETGR_R_SIZE_MAX) 205) 206 207func (k bufferKind) initialSize() syscall.Size_t { 208 sz, _ := syscall.Sysconf(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 syscall.Size_t(sz) 220} 221 222type memBuffer struct { 223 ptr *byte 224 size syscall.Size_t 225} 226 227func alloc(kind bufferKind) *memBuffer { 228 sz := kind.initialSize() 229 b := make([]byte, sz) 230 return &memBuffer{ 231 ptr: &b[0], 232 size: sz, 233 } 234} 235 236func (mb *memBuffer) resize(newSize syscall.Size_t) { 237 b := make([]byte, newSize) 238 mb.ptr = &b[0] 239 mb.size = newSize 240} 241 242func (mb *memBuffer) free() { 243 mb.ptr = nil 244} 245 246// retryWithBuffer repeatedly calls f(), increasing the size of the 247// buffer each time, until f succeeds, fails with a non-ERANGE error, 248// or the buffer exceeds a reasonable limit. 249func retryWithBuffer(buf *memBuffer, f func() syscall.Errno) error { 250 for { 251 errno := f() 252 if errno == 0 { 253 return nil 254 } else if errno != syscall.ERANGE { 255 return errno 256 } 257 newSize := buf.size * 2 258 if !isSizeReasonable(int64(newSize)) { 259 return fmt.Errorf("internal buffer exceeds %d bytes", maxBufferSize) 260 } 261 buf.resize(newSize) 262 } 263} 264 265const maxBufferSize = 1 << 20 266 267func isSizeReasonable(sz int64) bool { 268 return sz > 0 && sz <= maxBufferSize 269} 270 271// Because we can't use cgo in tests: 272func structPasswdForNegativeTest() syscall.Passwd { 273 sp := syscall.Passwd{} 274 sp.Pw_uid = 1<<32 - 2 275 sp.Pw_gid = 1<<32 - 3 276 return sp 277} 278