libc/grp: add getgrouplist()

Add getgrouplist() to the C library.  It scans the group database for
a user's supplementary groups and always reports the primary group
first.  Without CONFIG_LIBC_GROUP_FILE only the primary group is
returned, since no membership information is available.

The group file is read through lib_get_tempbuffer()/lib_put_tempbuffer()
to avoid a heap allocation on every lookup.

Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
This commit is contained in:
Xiang Xiao 2026-06-21 01:55:08 +08:00 committed by Xiang Xiao
parent 1685e8ff7b
commit a9b72ed796
6 changed files with 223 additions and 2 deletions

View file

@ -72,6 +72,8 @@ int getgrgid_r(gid_t gid, FAR struct group *grp,
FAR char *buf, size_t buflen,
FAR struct group **result);
int initgroups(FAR const char *user, gid_t group);
int getgrouplist(FAR const char *user, gid_t group, FAR gid_t *groups,
FAR int *ngroups);
#undef EXTERN
#if defined(__cplusplus)

View file

@ -20,7 +20,7 @@
#
# ##############################################################################
set(SRCS lib_getgrgid.c lib_getgrgidr.c lib_getgrnam.c lib_getgrnamr.c
lib_initgroups.c)
lib_initgroups.c lib_getgrouplist.c)
if(CONFIG_LIBC_GROUP_FILE)
list(APPEND SRCS lib_find_grpfile.c lib_grp_globals.c)

View file

@ -23,7 +23,7 @@
# Add the grp C files to the build
CSRCS += lib_getgrgid.c lib_getgrgidr.c lib_getgrnam.c lib_getgrnamr.c
CSRCS += lib_initgroups.c
CSRCS += lib_initgroups.c lib_getgrouplist.c
ifeq ($(CONFIG_LIBC_GROUP_FILE),y)
CSRCS += lib_find_grpfile.c lib_grp_globals.c

View file

@ -31,6 +31,9 @@
#include <string.h>
#include <grp.h>
#include <assert.h>
#include <errno.h>
#include <nuttx/lib/lib.h>
#include "grp/lib_grp.h"
@ -41,6 +44,19 @@
typedef CODE int (grp_foreach_match_t)(FAR const struct group *entry,
uintptr_t arg);
/* Context used by grp_match_user() to accumulate the group IDs of all the
* groups that a given user is a member of.
*/
struct grp_user_s
{
FAR const char *user; /* User name to search for */
gid_t group; /* Primary group to skip (avoid duplicate) */
FAR gid_t *grouplist; /* Output array of group IDs */
int maxgroups; /* Number of slots in grouplist */
int count; /* Number of group IDs found so far */
};
/****************************************************************************
* Private Functions
****************************************************************************/
@ -93,6 +109,61 @@ static int grp_match_gid(FAR const struct group *entry, uintptr_t arg)
return match_gid == entry->gr_gid ? 1 : 0;
}
/****************************************************************************
* Name: grp_match_user
*
* Description:
* Called for each record in the group file. If the user (passed via the
* context in arg) is a member of the group, its group ID is appended to
* the caller's output array. Always returns 0 so that every record in
* the file is visited and the total number of matching groups is counted.
*
* Input Parameters:
* entry - The parsed group file record
* arg - A pointer to the grp_user_s search context
*
* Returned Value:
* = 0 : Always, so that iteration continues to the end of the file.
*
****************************************************************************/
static int grp_match_user(FAR const struct group *entry, uintptr_t arg)
{
FAR struct grp_user_s *ctx = (FAR struct grp_user_s *)arg;
FAR char **member;
/* The primary group has already been accounted for by the caller. Skip
* it here to avoid reporting it twice.
*/
if (entry->gr_gid == ctx->group)
{
return 0;
}
/* Check whether the user is listed as a member of this group. */
for (member = entry->gr_mem; *member != NULL; member++)
{
if (strcmp(*member, ctx->user) == 0)
{
/* Append the group ID if there is room in the output array. Keep
* counting regardless so the caller learns the required size.
*/
if (ctx->count < ctx->maxgroups)
{
ctx->grouplist[ctx->count] = entry->gr_gid;
}
ctx->count++;
break;
}
}
return 0;
}
/****************************************************************************
* Name: grp_foreach
*
@ -337,3 +408,54 @@ int grp_findby_gid(gid_t gid, FAR struct group *entry, FAR char *buffer,
return grp_foreach(grp_match_gid, (uintptr_t)gid, entry, buffer, buflen);
}
/****************************************************************************
* Name: grp_findby_user
*
* Description:
* Scan the group file and collect the group IDs of all groups that have
* 'user' as a member, excluding the primary group 'group'.
*
* Input Parameters:
* user - The user name to search for
* group - The primary group ID to exclude from the search
* grouplist - Location to return the matching group IDs
* maxgroups - The number of slots available in grouplist
* count - On input, the number of group IDs already stored in
* grouplist. On output, the total number of group IDs found
* (which may exceed maxgroups).
*
* Returned Value:
* Zero (OK) is returned on success. A negated errno value is returned on
* any failure.
*
****************************************************************************/
int grp_findby_user(FAR const char *user, gid_t group,
FAR gid_t *grouplist, int maxgroups, FAR int *count)
{
struct grp_user_s ctx;
struct group entry;
FAR char *buffer;
int ret;
buffer = lib_get_tempbuffer(GRPBUF_RESERVE_SIZE);
if (buffer == NULL)
{
return -ENOMEM;
}
ctx.user = user;
ctx.group = group;
ctx.grouplist = grouplist;
ctx.maxgroups = maxgroups;
ctx.count = *count;
ret = grp_foreach(grp_match_user, (uintptr_t)&ctx, &entry, buffer,
GRPBUF_RESERVE_SIZE);
*count = ctx.count;
lib_put_tempbuffer(buffer);
return ret < 0 ? ret : 0;
}

View file

@ -0,0 +1,95 @@
/****************************************************************************
* libs/libc/grp/lib_getgrouplist.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <grp.h>
#include "grp/lib_grp.h"
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: getgrouplist
*
* Description:
* The getgrouplist() function scans the group database to obtain the list
* of groups that 'user' belongs to. The group 'group' (typically the
* primary group ID from the password database) is always included in the
* returned list and is stored as the first element.
*
* Input Parameters:
* user - The user name whose group membership is queried.
* group - An additional group ID (the primary group) to include.
* groups - Location to return the list of group IDs.
* ngroups - On input, the number of slots available in groups. On output,
* the actual number of group IDs found.
*
* Returned Value:
* On success, the number of group IDs found is returned. If the number of
* groups exceeds the available space, -1 is returned and ngroups is set to
* the number of group IDs that would have been returned.
*
****************************************************************************/
int getgrouplist(FAR const char *user, gid_t group, FAR gid_t *groups,
FAR int *ngroups)
{
int maxgroups = *ngroups;
int count;
/* The primary group is always reported first. */
if (maxgroups > 0)
{
groups[0] = group;
}
count = 1;
#ifdef CONFIG_LIBC_GROUP_FILE
/* Scan the group file for any supplementary groups that 'user' belongs to
* and append their group IDs to the list.
*/
grp_findby_user(user, group, groups, maxgroups, &count);
#else
/* Without a group file there is no membership information, so only the
* primary group is reported.
*/
UNUSED(user);
#endif
/* If the caller's buffer was too small, report the required size and
* return failure. Otherwise, return the number of group IDs stored.
*/
*ngroups = count;
return count > maxgroups ? -1 : count;
}

View file

@ -83,6 +83,8 @@ int grp_findby_name(FAR const char *gname, FAR struct group *entry,
FAR char *buffer, size_t buflen);
int grp_findby_gid(gid_t gid, FAR struct group *entry, FAR char *buffer,
size_t buflen);
int grp_findby_user(FAR const char *user, gid_t group,
FAR gid_t *grouplist, int maxgroups, FAR int *count);
#endif
#undef EXTERN