(root)/
coreutils-9.4/
lib/
mgetgroups.c
       1  /* mgetgroups.c -- return a list of the groups a user or current process is in
       2  
       3     Copyright (C) 2007-2023 Free Software Foundation, Inc.
       4  
       5     This file is free software: you can redistribute it and/or modify
       6     it under the terms of the GNU Lesser General Public License as
       7     published by the Free Software Foundation; either version 2.1 of the
       8     License, or (at your option) any later version.
       9  
      10     This file 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 Lesser General Public License for more details.
      14  
      15     You should have received a copy of the GNU Lesser General Public License
      16     along with this program.  If not, see <https://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-oversized.h"
      35  
      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__) || defined __clang__
      39  # pragma GCC diagnostic ignored "-Wpointer-sign"
      40  #endif
      41  
      42  static gid_t *
      43  realloc_groupbuf (gid_t *g, size_t num)
      44  {
      45    if (xalloc_oversized (num, sizeof *g))
      46      {
      47        errno = ENOMEM;
      48        return NULL;
      49      }
      50  
      51    return realloc (g, num * sizeof *g);
      52  }
      53  
      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.  */
      64  
      65  int
      66  mgetgroups (char const *username, gid_t gid, gid_t **groups)
      67  {
      68    int max_n_groups;
      69    int ng;
      70    gid_t *g;
      71  
      72  #if HAVE_GETGROUPLIST
      73    /* We prefer to use getgrouplist if available, because it has better
      74       performance characteristics.
      75  
      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)
      82      {
      83        enum { N_GROUPS_INIT = 10 };
      84        max_n_groups = N_GROUPS_INIT;
      85  
      86        g = realloc_groupbuf (NULL, max_n_groups);
      87        if (g == NULL)
      88          return -1;
      89  
      90        while (1)
      91          {
      92            gid_t *h;
      93            int last_n_groups = max_n_groups;
      94  
      95            /* getgrouplist updates max_n_groups to num required.  */
      96            ng = getgrouplist (username, gid, g, &max_n_groups);
      97  
      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;
     102  
     103            if ((h = realloc_groupbuf (g, max_n_groups)) == NULL)
     104              {
     105                free (g);
     106                return -1;
     107              }
     108            g = h;
     109  
     110            if (0 <= ng)
     111              {
     112                *groups = g;
     113                /* On success some systems just return 0 from getgrouplist,
     114                   so return max_n_groups rather than ng.  */
     115                return max_n_groups;
     116              }
     117          }
     118      }
     119    /* else no username, so fall through and use getgroups. */
     120  #endif
     121  
     122    max_n_groups = (username
     123                    ? getugroups (0, NULL, username, gid)
     124                    : getgroups (0, NULL));
     125  
     126    /* If we failed to count groups because there is no supplemental
     127       group support, then return an array containing just GID.
     128       Otherwise, we fail for the same reason.  */
     129    if (max_n_groups < 0)
     130      {
     131        if (errno == ENOSYS && (g = realloc_groupbuf (NULL, 1)))
     132          {
     133            *groups = g;
     134            *g = gid;
     135            return gid != (gid_t) -1;
     136          }
     137        return -1;
     138      }
     139  
     140    if (max_n_groups == 0 || (!username && gid != (gid_t) -1))
     141      max_n_groups++;
     142    g = realloc_groupbuf (NULL, max_n_groups);
     143    if (g == NULL)
     144      return -1;
     145  
     146    ng = (username
     147          ? getugroups (max_n_groups, g, username, gid)
     148          : getgroups (max_n_groups - (gid != (gid_t) -1),
     149                                  g + (gid != (gid_t) -1)));
     150  
     151    if (ng < 0)
     152      {
     153        /* Failure is unexpected, but handle it anyway.  */
     154        free (g);
     155        return -1;
     156      }
     157  
     158    if (!username && gid != (gid_t) -1)
     159      {
     160        *g = gid;
     161        ng++;
     162      }
     163    *groups = g;
     164  
     165    /* Reduce the number of duplicates.  On some systems, getgroups
     166       returns the effective gid twice: once as the first element, and
     167       once in its position within the supplementary groups.  On other
     168       systems, getgroups does not return the effective gid at all,
     169       which is why we provide a GID argument.  Meanwhile, the GID
     170       argument, if provided, is typically any member of the
     171       supplementary groups, and not necessarily the effective gid.  So,
     172       the most likely duplicates are the first element with an
     173       arbitrary other element, or pair-wise duplication between the
     174       first and second elements returned by getgroups.  It is possible
     175       that this O(n) pass will not remove all duplicates, but it is not
     176       worth the effort to slow down to an O(n log n) algorithm that
     177       sorts the array in place, nor the extra memory needed for
     178       duplicate removal via an O(n) hash-table.  Hence, this function
     179       is only documented as guaranteeing no pair-wise duplicates,
     180       rather than returning the minimal set.  */
     181    if (1 < ng)
     182      {
     183        gid_t first = *g;
     184        gid_t *next;
     185        gid_t *groups_end = g + ng;
     186  
     187        for (next = g + 1; next < groups_end; next++)
     188          {
     189            if (*next == first || *next == *g)
     190              ng--;
     191            else
     192              *++g = *next;
     193          }
     194      }
     195  
     196    return ng;
     197  }