| // Copyright 2011 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // +build aix darwin dragonfly freebsd hurd !android,linux netbsd openbsd solaris |
| // +build cgo,!osusergo |
| |
| package user |
| |
| import ( |
| "fmt" |
| "strconv" |
| "strings" |
| "syscall" |
| "unsafe" |
| ) |
| |
| // bytePtrToString takes a NUL-terminated array of bytes and convert |
| // it to a Go string. |
| func bytePtrToString(p *byte) string { |
| if p == nil { |
| return "" |
| } |
| a := (*[10000]byte)(unsafe.Pointer(p)) |
| i := 0 |
| for a[i] != 0 { |
| i++ |
| } |
| return string(a[:i]) |
| } |
| |
| func current() (*User, error) { |
| return lookupUnixUid(syscall.Getuid()) |
| } |
| |
| func lookupUser(username string) (*User, error) { |
| var pwd syscall.Passwd |
| var result *syscall.Passwd |
| p := syscall.StringBytePtr(username) |
| |
| buf := alloc(userBuffer) |
| defer buf.free() |
| |
| err := retryWithBuffer(buf, func() syscall.Errno { |
| syscall.Entersyscall() |
| rv := libc_getpwnam_r(p, |
| &pwd, |
| buf.ptr, |
| buf.size, |
| &result) |
| syscall.Exitsyscall() |
| if rv != 0 { |
| return syscall.GetErrno() |
| } |
| return 0 |
| }) |
| if err != nil { |
| return nil, fmt.Errorf("user: lookup username %s: %v", username, err) |
| } |
| if result == nil { |
| return nil, UnknownUserError(username) |
| } |
| return buildUser(&pwd), err |
| } |
| |
| func lookupUserId(uid string) (*User, error) { |
| i, e := strconv.Atoi(uid) |
| if e != nil { |
| return nil, e |
| } |
| return lookupUnixUid(i) |
| } |
| |
| func lookupUnixUid(uid int) (*User, error) { |
| var pwd syscall.Passwd |
| var result *syscall.Passwd |
| |
| buf := alloc(userBuffer) |
| defer buf.free() |
| |
| err := retryWithBuffer(buf, func() syscall.Errno { |
| syscall.Entersyscall() |
| rv := libc_getpwuid_r(syscall.Uid_t(uid), |
| &pwd, |
| buf.ptr, |
| buf.size, |
| &result) |
| syscall.Exitsyscall() |
| if rv != 0 { |
| return syscall.GetErrno() |
| } |
| return 0 |
| }) |
| if err != nil { |
| return nil, fmt.Errorf("user: lookup userid %d: %v", uid, err) |
| } |
| if result == nil { |
| return nil, UnknownUserIdError(uid) |
| } |
| return buildUser(&pwd), nil |
| } |
| |
| func buildUser(pwd *syscall.Passwd) *User { |
| u := &User{ |
| Uid: strconv.FormatUint(uint64(pwd.Pw_uid), 10), |
| Gid: strconv.FormatUint(uint64(pwd.Pw_gid), 10), |
| Username: bytePtrToString((*byte)(unsafe.Pointer(pwd.Pw_name))), |
| Name: bytePtrToString((*byte)(unsafe.Pointer(pwd.Pw_gecos))), |
| HomeDir: bytePtrToString((*byte)(unsafe.Pointer(pwd.Pw_dir))), |
| } |
| // The pw_gecos field isn't quite standardized. Some docs |
| // say: "It is expected to be a comma separated list of |
| // personal data where the first item is the full name of the |
| // user." |
| if i := strings.Index(u.Name, ","); i >= 0 { |
| u.Name = u.Name[:i] |
| } |
| return u |
| } |
| |
| func lookupGroup(groupname string) (*Group, error) { |
| var grp syscall.Group |
| var result *syscall.Group |
| |
| buf := alloc(groupBuffer) |
| defer buf.free() |
| p := syscall.StringBytePtr(groupname) |
| |
| err := retryWithBuffer(buf, func() syscall.Errno { |
| syscall.Entersyscall() |
| rv := libc_getgrnam_r(p, |
| &grp, |
| buf.ptr, |
| buf.size, |
| &result) |
| syscall.Exitsyscall() |
| if rv != 0 { |
| return syscall.GetErrno() |
| } |
| return 0 |
| }) |
| if err != nil { |
| return nil, fmt.Errorf("user: lookup groupname %s: %v", groupname, err) |
| } |
| if result == nil { |
| return nil, UnknownGroupError(groupname) |
| } |
| return buildGroup(&grp), nil |
| } |
| |
| func lookupGroupId(gid string) (*Group, error) { |
| i, e := strconv.Atoi(gid) |
| if e != nil { |
| return nil, e |
| } |
| return lookupUnixGid(i) |
| } |
| |
| func lookupUnixGid(gid int) (*Group, error) { |
| var grp syscall.Group |
| var result *syscall.Group |
| |
| buf := alloc(groupBuffer) |
| defer buf.free() |
| |
| err := retryWithBuffer(buf, func() syscall.Errno { |
| syscall.Entersyscall() |
| rv := libc_getgrgid_r(syscall.Gid_t(gid), |
| &grp, |
| buf.ptr, |
| buf.size, |
| &result) |
| syscall.Exitsyscall() |
| if rv != 0 { |
| return syscall.GetErrno() |
| } |
| return 0 |
| }) |
| if err != nil { |
| return nil, fmt.Errorf("user: lookup groupid %d: %v", gid, err) |
| } |
| if result == nil { |
| return nil, UnknownGroupIdError(strconv.Itoa(gid)) |
| } |
| return buildGroup(&grp), nil |
| } |
| |
| func buildGroup(grp *syscall.Group) *Group { |
| g := &Group{ |
| Gid: strconv.Itoa(int(grp.Gr_gid)), |
| Name: bytePtrToString((*byte)(unsafe.Pointer(grp.Gr_name))), |
| } |
| return g |
| } |
| |
| type bufferKind int |
| |
| const ( |
| userBuffer = bufferKind(syscall.SC_GETPW_R_SIZE_MAX) |
| groupBuffer = bufferKind(syscall.SC_GETGR_R_SIZE_MAX) |
| ) |
| |
| func (k bufferKind) initialSize() syscall.Size_t { |
| sz, _ := syscall.Sysconf(int(k)) |
| if sz == -1 { |
| // DragonFly and FreeBSD do not have _SC_GETPW_R_SIZE_MAX. |
| // Additionally, not all Linux systems have it, either. For |
| // example, the musl libc returns -1. |
| return 1024 |
| } |
| if !isSizeReasonable(int64(sz)) { |
| // Truncate. If this truly isn't enough, retryWithBuffer will error on the first run. |
| return maxBufferSize |
| } |
| return syscall.Size_t(sz) |
| } |
| |
| type memBuffer struct { |
| ptr *byte |
| size syscall.Size_t |
| } |
| |
| func alloc(kind bufferKind) *memBuffer { |
| sz := kind.initialSize() |
| b := make([]byte, sz) |
| return &memBuffer{ |
| ptr: &b[0], |
| size: sz, |
| } |
| } |
| |
| func (mb *memBuffer) resize(newSize syscall.Size_t) { |
| b := make([]byte, newSize) |
| mb.ptr = &b[0] |
| mb.size = newSize |
| } |
| |
| func (mb *memBuffer) free() { |
| mb.ptr = nil |
| } |
| |
| // retryWithBuffer repeatedly calls f(), increasing the size of the |
| // buffer each time, until f succeeds, fails with a non-ERANGE error, |
| // or the buffer exceeds a reasonable limit. |
| func retryWithBuffer(buf *memBuffer, f func() syscall.Errno) error { |
| for { |
| errno := f() |
| if errno == 0 { |
| return nil |
| } else if errno != syscall.ERANGE { |
| return errno |
| } |
| newSize := buf.size * 2 |
| if !isSizeReasonable(int64(newSize)) { |
| return fmt.Errorf("internal buffer exceeds %d bytes", maxBufferSize) |
| } |
| buf.resize(newSize) |
| } |
| } |
| |
| const maxBufferSize = 1 << 20 |
| |
| func isSizeReasonable(sz int64) bool { |
| return sz > 0 && sz <= maxBufferSize |
| } |
| |
| // Because we can't use cgo in tests: |
| func structPasswdForNegativeTest() syscall.Passwd { |
| sp := syscall.Passwd{} |
| sp.Pw_uid = 1<<32 - 2 |
| sp.Pw_gid = 1<<32 - 3 |
| return sp |
| } |