mgetgroups: avoid argument promotion issues with -1
[gnulib.git] / lib / mgetgroups.c
1 /* mgetgroups.c -- return a list of the groups a user or current process is in
2
3    Copyright (C) 2007-2009 Free Software Foundation, Inc.
4
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.
9
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.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
17
18 /* Extracted from coreutils' src/id.c. */
19
20 #include <config.h>
21
22 #include "mgetgroups.h"
23
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
32
33 #include "getugroups.h"
34 #include "xalloc.h"
35
36 static gid_t *
37 realloc_groupbuf (gid_t *g, size_t num)
38 {
39   if (xalloc_oversized (num, sizeof *g))
40     {
41       errno = ENOMEM;
42       return NULL;
43     }
44
45   return realloc (g, num * sizeof *g);
46 }
47
48 /* Like getugroups, but store the result in malloc'd storage.
49    Set *GROUPS to the malloc'd list of all group IDs of which USERNAME
50    is a member.  If GID is not -1, store it first.  GID should be the
51    group ID (pw_gid) obtained from getpwuid, in case USERNAME is not
52    listed in the groups database (e.g., /etc/groups).  If USERNAME is
53    NULL, store the supplementary groups of the current process, and GID
54    should be -1 or the effective group ID (getegid).  Upon failure,
55    don't modify *GROUPS, set errno, and return -1.  Otherwise, return
56    the number of groups.  */
57
58 int
59 mgetgroups (char const *username, gid_t gid, gid_t **groups)
60 {
61   int max_n_groups;
62   int ng;
63   gid_t *g;
64
65 #if HAVE_GETGROUPLIST
66   /* We prefer to use getgrouplist if available, because it has better
67      performance characteristics.
68
69      In glibc 2.3.2, getgrouplist is buggy.  If you pass a zero as the
70      length of the output buffer, getgrouplist will still write to the
71      buffer.  Contrary to what some versions of the getgrouplist
72      manpage say, this doesn't happen with nonzero buffer sizes.
73      Therefore our usage here just avoids a zero sized buffer.  */
74   if (username)
75     {
76       enum { N_GROUPS_INIT = 10 };
77       max_n_groups = N_GROUPS_INIT;
78
79       g = realloc_groupbuf (NULL, max_n_groups);
80       if (g == NULL)
81         return -1;
82
83       while (1)
84         {
85           gid_t *h;
86           int last_n_groups = max_n_groups;
87
88           /* getgrouplist updates max_n_groups to num required.  */
89           ng = getgrouplist (username, gid, g, &max_n_groups);
90
91           /* Some systems (like Darwin) have a bug where they
92              never increase max_n_groups.  */
93           if (ng < 0 && last_n_groups == max_n_groups)
94             max_n_groups *= 2;
95
96           if ((h = realloc_groupbuf (g, max_n_groups)) == NULL)
97             {
98               int saved_errno = errno;
99               free (g);
100               errno = saved_errno;
101               return -1;
102             }
103           g = h;
104
105           if (0 <= ng)
106             {
107               *groups = g;
108               /* On success some systems just return 0 from getgrouplist,
109                  so return max_n_groups rather than ng.  */
110               return max_n_groups;
111             }
112         }
113     }
114   /* else no username, so fall through and use getgroups. */
115 #endif
116
117   max_n_groups = (username
118                   ? getugroups (0, NULL, username, gid)
119                   : getgroups (0, NULL) + (gid != (gid_t) -1));
120
121   /* If we failed to count groups with NULL for a buffer,
122      try again with a non-NULL one, just in case.  */
123   if (max_n_groups < 0)
124       max_n_groups = 5;
125
126   g = realloc_groupbuf (NULL, max_n_groups);
127   if (g == NULL)
128     return -1;
129
130   ng = (username
131         ? getugroups (max_n_groups, g, username, gid)
132         : getgroups (max_n_groups, g + (gid != (gid_t) -1)));
133
134   if (ng < 0)
135     {
136       int saved_errno = errno;
137       free (g);
138       errno = saved_errno;
139       return -1;
140     }
141
142   if (!username && gid != (gid_t) -1)
143     {
144       *g = gid;
145       ng++;
146     }
147   *groups = g;
148   return ng;
149 }