1// Copyright 2016 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 && (darwin || dragonfly || freebsd || (linux && !android) || netbsd || openbsd || (solaris && !illumos))
6
7package user
8
9import (
10	"fmt"
11	"strconv"
12	"unsafe"
13)
14
15const maxGroups = 2048
16
17func listGroups(u *User) ([]string, error) {
18	ug, err := strconv.Atoi(u.Gid)
19	if err != nil {
20		return nil, fmt.Errorf("user: list groups for %s: invalid gid %q", u.Username, u.Gid)
21	}
22	userGID := _C_gid_t(ug)
23	nameC := make([]byte, len(u.Username)+1)
24	copy(nameC, u.Username)
25
26	n := _C_int(256)
27	gidsC := make([]_C_gid_t, n)
28	rv := getGroupList((*_C_char)(unsafe.Pointer(&nameC[0])), userGID, &gidsC[0], &n)
29	if rv == -1 {
30		// Mac is the only Unix that does not set n properly when rv == -1, so
31		// we need to use different logic for Mac vs. the other OS's.
32		if err := groupRetry(u.Username, nameC, userGID, &gidsC, &n); err != nil {
33			return nil, err
34		}
35	}
36	gidsC = gidsC[:n]
37	gids := make([]string, 0, n)
38	for _, g := range gidsC[:n] {
39		gids = append(gids, strconv.Itoa(int(g)))
40	}
41	return gids, nil
42}
43
44// groupRetry retries getGroupList with much larger size for n. The result is
45// stored in gids.
46func groupRetry(username string, name []byte, userGID _C_gid_t, gids *[]_C_gid_t, n *_C_int) error {
47	// More than initial buffer, but now n contains the correct size.
48	if *n > maxGroups {
49		return fmt.Errorf("user: %q is a member of more than %d groups", username, maxGroups)
50	}
51	*gids = make([]_C_gid_t, *n)
52	rv := getGroupList((*_C_char)(unsafe.Pointer(&name[0])), userGID, &(*gids)[0], n)
53	if rv == -1 {
54		return fmt.Errorf("user: list groups for %s failed", username)
55	}
56	return nil
57}
58