vasnprintf: Don't use %n on modern, ISO C 99 compliant platforms.
[gnulib.git] / lib / mgetgroups.c
blob3377d7bb2281f8f742538d91ca71bd34227e9452
1 /* mgetgroups.c -- return a list of the groups a user or current process is in
3 Copyright (C) 2007-2020 Free Software Foundation, Inc.
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
18 /* Extracted from coreutils' src/id.c. */
20 #include <config.h>
22 #include "mgetgroups.h"
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <stdint.h>
27 #include <string.h>
28 #include <errno.h>
29 #if HAVE_GETGROUPLIST
30 # include <grp.h>
31 #endif
33 #include "getugroups.h"
34 #include "xalloc-oversized.h"
36 /* Work around an incompatibility of OS X 10.11: getgrouplist
37 accepts int *, not gid_t *, and int and gid_t differ in sign. */
38 #if 4 < __GNUC__ + (3 <= __GNUC_MINOR__)
39 # pragma GCC diagnostic ignored "-Wpointer-sign"
40 #endif
42 static gid_t *
43 realloc_groupbuf (gid_t *g, size_t num)
45 if (xalloc_oversized (num, sizeof *g))
47 errno = ENOMEM;
48 return NULL;
51 return realloc (g, num * sizeof *g);
54 /* Like getugroups, but store the result in malloc'd storage.
55 Set *GROUPS to the malloc'd list of all group IDs of which USERNAME
56 is a member. If GID is not -1, store it first. GID should be the
57 group ID (pw_gid) obtained from getpwuid, in case USERNAME is not
58 listed in the groups database (e.g., /etc/groups). If USERNAME is
59 NULL, store the supplementary groups of the current process, and GID
60 should be -1 or the effective group ID (getegid). Upon failure,
61 don't modify *GROUPS, set errno, and return -1. Otherwise, return
62 the number of groups. The resulting list may contain duplicates,
63 but adjacent members will be distinct. */
65 int
66 mgetgroups (char const *username, gid_t gid, gid_t **groups)
68 int max_n_groups;
69 int ng;
70 gid_t *g;
72 #if HAVE_GETGROUPLIST
73 /* We prefer to use getgrouplist if available, because it has better
74 performance characteristics.
76 In glibc 2.3.2, getgrouplist is buggy. If you pass a zero as the
77 length of the output buffer, getgrouplist will still write to the
78 buffer. Contrary to what some versions of the getgrouplist
79 manpage say, this doesn't happen with nonzero buffer sizes.
80 Therefore our usage here just avoids a zero sized buffer. */
81 if (username)
83 enum { N_GROUPS_INIT = 10 };
84 max_n_groups = N_GROUPS_INIT;
86 g = realloc_groupbuf (NULL, max_n_groups);
87 if (g == NULL)
88 return -1;
90 while (1)
92 gid_t *h;
93 int last_n_groups = max_n_groups;
95 /* getgrouplist updates max_n_groups to num required. */
96 ng = getgrouplist (username, gid, g, &max_n_groups);
98 /* Some systems (like Darwin) have a bug where they
99 never increase max_n_groups. */
100 if (ng < 0 && last_n_groups == max_n_groups)
101 max_n_groups *= 2;
103 if ((h = realloc_groupbuf (g, max_n_groups)) == NULL)
105 int saved_errno = errno;
106 free (g);
107 errno = saved_errno;
108 return -1;
110 g = h;
112 if (0 <= ng)
114 *groups = g;
115 /* On success some systems just return 0 from getgrouplist,
116 so return max_n_groups rather than ng. */
117 return max_n_groups;
121 /* else no username, so fall through and use getgroups. */
122 #endif
124 max_n_groups = (username
125 ? getugroups (0, NULL, username, gid)
126 : getgroups (0, NULL));
128 /* If we failed to count groups because there is no supplemental
129 group support, then return an array containing just GID.
130 Otherwise, we fail for the same reason. */
131 if (max_n_groups < 0)
133 if (errno == ENOSYS && (g = realloc_groupbuf (NULL, 1)))
135 *groups = g;
136 *g = gid;
137 return gid != (gid_t) -1;
139 return -1;
142 if (max_n_groups == 0 || (!username && gid != (gid_t) -1))
143 max_n_groups++;
144 g = realloc_groupbuf (NULL, max_n_groups);
145 if (g == NULL)
146 return -1;
148 ng = (username
149 ? getugroups (max_n_groups, g, username, gid)
150 : getgroups (max_n_groups - (gid != (gid_t) -1),
151 g + (gid != (gid_t) -1)));
153 if (ng < 0)
155 /* Failure is unexpected, but handle it anyway. */
156 int saved_errno = errno;
157 free (g);
158 errno = saved_errno;
159 return -1;
162 if (!username && gid != (gid_t) -1)
164 *g = gid;
165 ng++;
167 *groups = g;
169 /* Reduce the number of duplicates. On some systems, getgroups
170 returns the effective gid twice: once as the first element, and
171 once in its position within the supplementary groups. On other
172 systems, getgroups does not return the effective gid at all,
173 which is why we provide a GID argument. Meanwhile, the GID
174 argument, if provided, is typically any member of the
175 supplementary groups, and not necessarily the effective gid. So,
176 the most likely duplicates are the first element with an
177 arbitrary other element, or pair-wise duplication between the
178 first and second elements returned by getgroups. It is possible
179 that this O(n) pass will not remove all duplicates, but it is not
180 worth the effort to slow down to an O(n log n) algorithm that
181 sorts the array in place, nor the extra memory needed for
182 duplicate removal via an O(n) hash-table. Hence, this function
183 is only documented as guaranteeing no pair-wise duplicates,
184 rather than returning the minimal set. */
185 if (1 < ng)
187 gid_t first = *g;
188 gid_t *next;
189 gid_t *groups_end = g + ng;
191 for (next = g + 1; next < groups_end; next++)
193 if (*next == first || *next == *g)
194 ng--;
195 else
196 *++g = *next;
200 return ng;