feat(passwd): getpw*/getgr*/setpwent/endpwent family
This commit is contained in:
+103
@@ -0,0 +1,103 @@
|
|||||||
|
#ifndef VLIBC_GRP_H
|
||||||
|
#define VLIBC_GRP_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — <grp.h>.
|
||||||
|
*
|
||||||
|
* The group database (todo 37). struct group mirrors /etc/group, whose
|
||||||
|
* records are name:passwd:gid:member,member,... — the member list is a
|
||||||
|
* comma-separated tail, empty when the group has no members. Everything
|
||||||
|
* here is POSIX.1-2008 base (Level 1).
|
||||||
|
*
|
||||||
|
* The non-reentrant forms getgrnam/getgrgid/getgrent return a pointer to a
|
||||||
|
* static structure whose contents — including the strings and the member
|
||||||
|
* array it points at — are overwritten by the next call to any of the
|
||||||
|
* three. getgrnam/getgrgid open and scan the whole file per call; getgrent
|
||||||
|
* walks a cursor that setgrent rewinds and endgrent closes.
|
||||||
|
*
|
||||||
|
* The _r forms behave like their <pwd.h> counterparts: the entry is written
|
||||||
|
* into caller storage (the gr_mem pointer array and the member name strings
|
||||||
|
* both live inside the supplied buffer) and the return value is 0 on
|
||||||
|
* success, ERANGE when the buffer is too small, or 0 with *result NULL when
|
||||||
|
* no entry matches. errno is never touched: the error number IS the return
|
||||||
|
* value.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <vlibc/features.h>
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* One group database entry (/etc/group record). */
|
||||||
|
struct group
|
||||||
|
{
|
||||||
|
char *gr_name; /* group name */
|
||||||
|
char *gr_passwd; /* encrypted password; usually empty */
|
||||||
|
gid_t gr_gid; /* numeric group id */
|
||||||
|
char **gr_mem; /* NULL-terminated member-name array */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Look up the first entry whose name matches; NULL when absent. The result
|
||||||
|
* points into shared static storage valid only until the next grp call.
|
||||||
|
*/
|
||||||
|
struct group *
|
||||||
|
getgrnam(const char *name);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Look up the first entry whose gid matches; NULL when absent. Result
|
||||||
|
* storage is shared with getgrnam/getgrent as described above.
|
||||||
|
*/
|
||||||
|
struct group *
|
||||||
|
getgrgid(gid_t gid);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the next entry from the group stream, opening it on the first
|
||||||
|
* call; NULL at end of file. The stream cursor advances per call.
|
||||||
|
*/
|
||||||
|
struct group *
|
||||||
|
getgrent(void);
|
||||||
|
|
||||||
|
/* Rewind the group stream to its first entry. */
|
||||||
|
void
|
||||||
|
setgrent(void);
|
||||||
|
|
||||||
|
/* Close the group stream; a later getgrent reopens from the start. */
|
||||||
|
void
|
||||||
|
endgrent(void);
|
||||||
|
|
||||||
|
int
|
||||||
|
getgrnam_r(const char *name, struct group *gr, char *buf, size_t bufsize, struct group **result);
|
||||||
|
|
||||||
|
int
|
||||||
|
getgrgid_r(gid_t gid, struct group *gr, char *buf, size_t bufsize, struct group **result);
|
||||||
|
|
||||||
|
int
|
||||||
|
getgrent_r(struct group *gr, char *buf, size_t bufsize, struct group **result);
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
/* Level 2 (muslmimic): XSI. */
|
||||||
|
|
||||||
|
#include <stdio.h> /* FILE */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the next entry read from the given open stream, or NULL at end of
|
||||||
|
* file or on a malformed/unopenable record; the entry is parsed as an
|
||||||
|
* /etc/group record. Result storage is shared with getgrnam/getgrgid/
|
||||||
|
* getgrent as described above, so the stream must not be closed until the
|
||||||
|
* caller has copied the result. XSI.
|
||||||
|
*/
|
||||||
|
struct group *
|
||||||
|
fgetgrent(FILE *stream);
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* VLIBC_GRP_H */
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
#ifndef VLIBC_PWD_H
|
||||||
|
#define VLIBC_PWD_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — <pwd.h>.
|
||||||
|
*
|
||||||
|
* The password database (todo 37). struct passwd mirrors /etc/passwd, whose
|
||||||
|
* records are name:passwd:uid:gid:gecos:dir:shell — seven ':'-separated
|
||||||
|
* fields. Everything here is POSIX.1-2008 base (Level 1).
|
||||||
|
*
|
||||||
|
* The non-reentrant forms getpwnam/getpwuid/getpwent return a pointer to a
|
||||||
|
* static structure whose contents — including the strings it points at —
|
||||||
|
* are overwritten by the next call to any of the three, so the result must
|
||||||
|
* be copied out before the next database call. getpwnam/getpwuid open and
|
||||||
|
* scan the whole file per call; getpwent walks a cursor that setpwent
|
||||||
|
* rewinds and endpwent closes.
|
||||||
|
*
|
||||||
|
* The getpwnam_r/getpwuid_r/getpwent_r forms write the entry into
|
||||||
|
* caller-supplied storage (struct, char buffer of bufsize bytes) and
|
||||||
|
* return 0 on success with *result pointing at the filled struct, or an
|
||||||
|
* error number as the return value with *result left NULL: ERANGE when the
|
||||||
|
* buffer is too small for the entry, 0 with *result NULL when no entry
|
||||||
|
* matches (or the stream is exhausted). errno is never touched by any _r
|
||||||
|
* function: the error number IS the return value.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <vlibc/features.h>
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* One password database entry (/etc/passwd record). */
|
||||||
|
struct passwd
|
||||||
|
{
|
||||||
|
char *pw_name; /* user's login name */
|
||||||
|
char *pw_passwd; /* encrypted password; often "x" with shadow files */
|
||||||
|
uid_t pw_uid; /* numeric user id */
|
||||||
|
gid_t pw_gid; /* numeric group id */
|
||||||
|
char *pw_gecos; /* user information (comment) field */
|
||||||
|
char *pw_dir; /* home directory */
|
||||||
|
char *pw_shell; /* login shell */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Look up the first entry whose name matches; NULL when absent. The result
|
||||||
|
* points into shared static storage valid only until the next pwd call.
|
||||||
|
*/
|
||||||
|
struct passwd *
|
||||||
|
getpwnam(const char *name);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Look up the first entry whose uid matches; NULL when absent. Result
|
||||||
|
* storage is shared with getpwnam/getpwent as described above.
|
||||||
|
*/
|
||||||
|
struct passwd *
|
||||||
|
getpwuid(uid_t uid);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the next entry from the password stream, opening it on the first
|
||||||
|
* call; NULL at end of file. The stream cursor advances per call.
|
||||||
|
*/
|
||||||
|
struct passwd *
|
||||||
|
getpwent(void);
|
||||||
|
|
||||||
|
/* Rewind the password stream to its first entry. */
|
||||||
|
void
|
||||||
|
setpwent(void);
|
||||||
|
|
||||||
|
/* Close the password stream; a later getpwent reopens from the start. */
|
||||||
|
void
|
||||||
|
endpwent(void);
|
||||||
|
|
||||||
|
int
|
||||||
|
getpwnam_r(const char *name, struct passwd *pw, char *buf, size_t bufsize, struct passwd **result);
|
||||||
|
|
||||||
|
int
|
||||||
|
getpwuid_r(uid_t uid, struct passwd *pw, char *buf, size_t bufsize, struct passwd **result);
|
||||||
|
|
||||||
|
int
|
||||||
|
getpwent_r(struct passwd *pw, char *buf, size_t bufsize, struct passwd **result);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* VLIBC_PWD_H */
|
||||||
@@ -544,6 +544,21 @@ getsubopt(char **optionp, char *const *tokens, char **valuep);
|
|||||||
|
|
||||||
#endif /* VLIBC_LEVEL_GE(2) */
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Resolve path to an absolute, NUL-terminated canonical pathname with no
|
||||||
|
* symbolic links, "." or ".." components (todo 38). resolved_path may be
|
||||||
|
* NULL, in which case the canonical path is returned in a malloc'd
|
||||||
|
* buffer the caller must free; otherwise it must hold at least PATH_MAX
|
||||||
|
* bytes. Returns resolved_path (or the malloc'd buffer), or NULL with
|
||||||
|
* errno set. XSI.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
realpath(const char *restrict path, char *restrict resolved_path);
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,565 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <grp.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — group database (todo 37).
|
||||||
|
*
|
||||||
|
* getgrnam/getgrgid scan /etc/group with a fresh fopen per call (no
|
||||||
|
* persistent cursor); getgrent walks one FILE kept open between calls,
|
||||||
|
* which setgrent rewinds and endgrent closes. Every line is read with
|
||||||
|
* getline, one at a time — the whole file is never cached. fgetgrent is
|
||||||
|
* the XSI variant that parses the next record from an arbitrary caller-
|
||||||
|
* supplied stream; it is gated at level 2 (whole-level-gate rule: XSI is
|
||||||
|
* not POSIX.1-2008 base), everything else here is level 1.
|
||||||
|
*
|
||||||
|
* Storage model: two static buffers back every non-reentrant result. A
|
||||||
|
* successful call returns a struct whose string members and gr_mem
|
||||||
|
* entries point into a private result copy; the getline scan that found
|
||||||
|
* the match ran in a separate buffer, so a caller may feed the returned
|
||||||
|
* record's own name back into a later lookup (the
|
||||||
|
* getgrnam(getgrgid(x)->gr_name) round-trip) without the scan clobbering
|
||||||
|
* its key. The result copy is replaced by the next call to any of
|
||||||
|
* getgrnam/getgrgid/getgrent/fgetgrent (or a realloc of the copy),
|
||||||
|
* matching POSIX result-lifetime rules. The _r forms never use this
|
||||||
|
* static storage for their result: the caller's struct and buffer
|
||||||
|
* receive the record, and the caller's *result is how the outcome is
|
||||||
|
* reported.
|
||||||
|
*
|
||||||
|
* Record syntax: name:passwd:gid:member,member,... — the first three
|
||||||
|
* fields are ':'-separated and the member list is the comma-separated
|
||||||
|
* tail (empty when the group has no members). Blank or comment-style
|
||||||
|
* lines and records whose gid is not a decimal number are skipped like
|
||||||
|
* malformed input.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* The database path; not overridable (no NSS, no shadow, POSIX only). */
|
||||||
|
#define GRP_PATH "/etc/group"
|
||||||
|
|
||||||
|
/* Non-reentrant result: struct, the private copy it points into, and the
|
||||||
|
* member pointer array whose entries point into that same copy. */
|
||||||
|
static struct group gr_result;
|
||||||
|
static char *gr_out;
|
||||||
|
static size_t gr_out_cap;
|
||||||
|
static char **gr_mem;
|
||||||
|
static size_t gr_mem_cap;
|
||||||
|
|
||||||
|
/* The getline scan buffer shared by every lookup and stream walk. */
|
||||||
|
static char *gr_line;
|
||||||
|
static size_t gr_cap;
|
||||||
|
|
||||||
|
/* The persistent getgrent stream; NULL when closed. */
|
||||||
|
static FILE *gr_stream;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Parse the leading digit string of s as an unsigned id. Accepts only
|
||||||
|
* plain decimal digits and rejects empty fields and overflow; returns 0
|
||||||
|
* on success.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
gr_parse_id(const char *s, unsigned *out)
|
||||||
|
{
|
||||||
|
unsigned long v = 0;
|
||||||
|
const char *p;
|
||||||
|
|
||||||
|
if (*s == '\0')
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
for (p = s; *p != '\0'; p++)
|
||||||
|
{
|
||||||
|
if (*p < '0' || *p > '9')
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
v = v * 10 + (unsigned long)(*p - '0');
|
||||||
|
if (v > 0xffffffffUL)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*out = (unsigned)v;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grow the static member pointer array to hold cnt entries plus the
|
||||||
|
* terminating NULL; return 0 on success, -1 when realloc fails. */
|
||||||
|
static int
|
||||||
|
gr_reserve_mem(size_t cnt)
|
||||||
|
{
|
||||||
|
char **newmem;
|
||||||
|
size_t ncap;
|
||||||
|
|
||||||
|
if (gr_mem_cap >= cnt + 1)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
ncap = gr_mem_cap == 0 ? 8 : gr_mem_cap * 2;
|
||||||
|
while (ncap < cnt + 1)
|
||||||
|
{
|
||||||
|
ncap *= 2;
|
||||||
|
}
|
||||||
|
newmem = (char **)realloc((void *)gr_mem, ncap * sizeof(*newmem));
|
||||||
|
if (newmem == NULL)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
gr_mem = newmem;
|
||||||
|
gr_mem_cap = ncap;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Split the comma-separated member tail in place: every name becomes a
|
||||||
|
* NUL-terminated substring of line, member[i] points at the i-th one and
|
||||||
|
* member[cnt] is NULL. An empty tail yields a single NULL entry. The
|
||||||
|
* parsed group's gr_mem is set to the shared member array.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
gr_split_members(char *tail, struct group *gr)
|
||||||
|
{
|
||||||
|
size_t cnt = 0;
|
||||||
|
char *p;
|
||||||
|
|
||||||
|
if (*tail != '\0')
|
||||||
|
{
|
||||||
|
cnt = 1;
|
||||||
|
for (p = tail; *p != '\0'; p++)
|
||||||
|
{
|
||||||
|
if (*p == ',')
|
||||||
|
{
|
||||||
|
cnt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (gr_reserve_mem(cnt) != 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
p = tail;
|
||||||
|
for (size_t i = 0; i < cnt; i++)
|
||||||
|
{
|
||||||
|
char *comma = strchr(p, ',');
|
||||||
|
|
||||||
|
if (comma != NULL)
|
||||||
|
{
|
||||||
|
*comma = '\0';
|
||||||
|
}
|
||||||
|
gr_mem[i] = p;
|
||||||
|
p = comma == NULL ? p + strlen(p) : comma + 1;
|
||||||
|
}
|
||||||
|
gr_mem[cnt] = NULL;
|
||||||
|
gr->gr_mem = gr_mem;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Split the line (newline already stripped) into its fields in place,
|
||||||
|
* replacing the first three ':' separators with NULs. On success the
|
||||||
|
* struct fields point into line and the member list has been split in
|
||||||
|
* place, and 0 is returned; a record with fewer than three separators or
|
||||||
|
* a non-numeric gid yields -1.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
gr_split(struct group *gr, char *line)
|
||||||
|
{
|
||||||
|
char *fields[4];
|
||||||
|
char *p;
|
||||||
|
unsigned gid;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
p = line;
|
||||||
|
for (i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
fields[i] = p;
|
||||||
|
p = strchr(p, ':');
|
||||||
|
if (p == NULL)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*p = '\0';
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
fields[3] = p;
|
||||||
|
if (gr_parse_id(fields[2], &gid) != 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (gr_split_members(fields[3], gr) != 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
gr->gr_name = fields[0];
|
||||||
|
gr->gr_passwd = fields[1];
|
||||||
|
gr->gr_gid = (gid_t)gid;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Strip a trailing newline (and CR) from the line just read by getline. */
|
||||||
|
static void
|
||||||
|
gr_chomp(char *line, ssize_t len)
|
||||||
|
{
|
||||||
|
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
|
||||||
|
{
|
||||||
|
line[--len] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make line — a chomped, already-split record in the scan buffer — the
|
||||||
|
* non-reentrant result: copy it into the private result buffer and
|
||||||
|
* re-split it there, so the returned struct's strings and member
|
||||||
|
* pointers reference storage the scanner never touches again. Returns
|
||||||
|
* the result struct, or NULL if the result copy could not be grown.
|
||||||
|
*/
|
||||||
|
static struct group *
|
||||||
|
gr_publish(char *line)
|
||||||
|
{
|
||||||
|
size_t len = strlen(line) + 1;
|
||||||
|
|
||||||
|
if (len > gr_out_cap)
|
||||||
|
{
|
||||||
|
char *nb = (char *)realloc(gr_out, len);
|
||||||
|
|
||||||
|
if (nb == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
gr_out = nb;
|
||||||
|
gr_out_cap = len;
|
||||||
|
}
|
||||||
|
strcpy(gr_out, line);
|
||||||
|
if (gr_split(&gr_result, gr_out) != 0)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return &gr_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Match predicate over a parsed record, keyed by name or gid. */
|
||||||
|
typedef int (*gr_matcher)(const struct group *gr, const void *key);
|
||||||
|
|
||||||
|
static int
|
||||||
|
gr_match_name(const struct group *gr, const void *key)
|
||||||
|
{
|
||||||
|
return strcmp(gr->gr_name, (const char *)key) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
gr_match_gid(const struct group *gr, const void *key)
|
||||||
|
{
|
||||||
|
return gr->gr_gid == *(const gid_t *)key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Non-reentrant lookup core: open the database, scan with getline until
|
||||||
|
* the predicate matches, and hand back a pointer into the shared static
|
||||||
|
* line buffer. NULL means "no such entry" (or an unopenable database).
|
||||||
|
*/
|
||||||
|
static struct group *
|
||||||
|
gr_find(gr_matcher match, const void *key)
|
||||||
|
{
|
||||||
|
FILE *f = fopen(GRP_PATH, "r");
|
||||||
|
struct group cur;
|
||||||
|
ssize_t got;
|
||||||
|
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
got = getline(&gr_line, &gr_cap, f);
|
||||||
|
if (got < 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
gr_chomp(gr_line, got);
|
||||||
|
if (gr_split(&cur, gr_line) == 0 && match(&cur, key))
|
||||||
|
{
|
||||||
|
(void)fclose(f);
|
||||||
|
return gr_publish(gr_line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(void)fclose(f);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reentrant serializer: copy the parsed record into the caller's struct
|
||||||
|
* and buffer. The string bytes go first; the NULL-terminated member
|
||||||
|
* pointer array follows on a pointer-aligned boundary. Returns 0 on
|
||||||
|
* success, or ERANGE (as the POSIX _r functions return, never touching
|
||||||
|
* errno) when the record needs more than bufsize bytes.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
gr_store(const struct group *in, struct group *gr, char *buf, size_t bufsize)
|
||||||
|
{
|
||||||
|
size_t nmem = 0;
|
||||||
|
size_t strneed;
|
||||||
|
size_t ptoff;
|
||||||
|
char *s;
|
||||||
|
char **mem;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
while (in->gr_mem != NULL && in->gr_mem[nmem] != NULL)
|
||||||
|
{
|
||||||
|
nmem++;
|
||||||
|
}
|
||||||
|
strneed = strlen(in->gr_name) + 1;
|
||||||
|
strneed += strlen(in->gr_passwd) + 1;
|
||||||
|
for (i = 0; i < nmem; i++)
|
||||||
|
{
|
||||||
|
strneed += strlen(in->gr_mem[i]) + 1;
|
||||||
|
}
|
||||||
|
ptoff = (strneed + sizeof(char *) - 1) & ~(sizeof(char *) - 1);
|
||||||
|
if (bufsize < ptoff + (nmem + 1) * sizeof(char *))
|
||||||
|
{
|
||||||
|
return ERANGE;
|
||||||
|
}
|
||||||
|
gr->gr_gid = in->gr_gid;
|
||||||
|
mem = (char **)(void *)(buf + ptoff);
|
||||||
|
gr->gr_mem = mem;
|
||||||
|
s = buf;
|
||||||
|
gr->gr_name = s;
|
||||||
|
s += strlen(in->gr_name) + 1;
|
||||||
|
gr->gr_passwd = s;
|
||||||
|
s += strlen(in->gr_passwd) + 1;
|
||||||
|
for (i = 0; i < nmem; i++)
|
||||||
|
{
|
||||||
|
size_t len = strlen(in->gr_mem[i]) + 1;
|
||||||
|
|
||||||
|
gr->gr_mem[i] = s;
|
||||||
|
s += len;
|
||||||
|
}
|
||||||
|
gr->gr_mem[nmem] = NULL;
|
||||||
|
strcpy(gr->gr_name, in->gr_name);
|
||||||
|
strcpy(gr->gr_passwd, in->gr_passwd);
|
||||||
|
for (i = 0; i < nmem; i++)
|
||||||
|
{
|
||||||
|
size_t len = strlen(in->gr_mem[i]) + 1;
|
||||||
|
|
||||||
|
__builtin_memcpy(gr->gr_mem[i], in->gr_mem[i], len);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct group *
|
||||||
|
getgrnam(const char *name)
|
||||||
|
{
|
||||||
|
if (name == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return gr_find(gr_match_name, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct group *
|
||||||
|
getgrgid(gid_t gid)
|
||||||
|
{
|
||||||
|
return gr_find(gr_match_gid, &gid);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct group *
|
||||||
|
getgrent(void)
|
||||||
|
{
|
||||||
|
struct group cur;
|
||||||
|
ssize_t got;
|
||||||
|
|
||||||
|
if (gr_stream == NULL)
|
||||||
|
{
|
||||||
|
gr_stream = fopen(GRP_PATH, "r");
|
||||||
|
if (gr_stream == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
got = getline(&gr_line, &gr_cap, gr_stream);
|
||||||
|
if (got < 0)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
gr_chomp(gr_line, got);
|
||||||
|
if (gr_split(&cur, gr_line) == 0)
|
||||||
|
{
|
||||||
|
return gr_publish(gr_line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
setgrent(void)
|
||||||
|
{
|
||||||
|
if (gr_stream != NULL)
|
||||||
|
{
|
||||||
|
rewind(gr_stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
endgrent(void)
|
||||||
|
{
|
||||||
|
if (gr_stream != NULL)
|
||||||
|
{
|
||||||
|
(void)fclose(gr_stream);
|
||||||
|
gr_stream = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reentrant lookup core: scan for the first matching record and serialize
|
||||||
|
* it into the caller's storage. Returns 0 with *result pointing at the
|
||||||
|
* filled struct on success, 0 with *result NULL when no record matches,
|
||||||
|
* ERANGE when the record does not fit bufsize, and EINVAL for a NULL
|
||||||
|
* result pointer.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
gr_find_r(gr_matcher match, const void *key, struct group *gr, char *buf, size_t bufsize,
|
||||||
|
struct group **result)
|
||||||
|
{
|
||||||
|
FILE *f;
|
||||||
|
struct group cur;
|
||||||
|
ssize_t got;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (result == NULL || gr == NULL)
|
||||||
|
{
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
if (buf == NULL && bufsize != 0)
|
||||||
|
{
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
*result = NULL;
|
||||||
|
f = fopen(GRP_PATH, "r");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
got = getline(&gr_line, &gr_cap, f);
|
||||||
|
if (got < 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
gr_chomp(gr_line, got);
|
||||||
|
if (gr_split(&cur, gr_line) != 0 || !match(&cur, key))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
rc = gr_store(&cur, gr, buf, bufsize);
|
||||||
|
(void)fclose(f);
|
||||||
|
if (rc == 0)
|
||||||
|
{
|
||||||
|
*result = gr;
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
(void)fclose(f);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
getgrnam_r(const char *name, struct group *gr, char *buf, size_t bufsize, struct group **result)
|
||||||
|
{
|
||||||
|
if (name == NULL)
|
||||||
|
{
|
||||||
|
if (result != NULL)
|
||||||
|
{
|
||||||
|
*result = NULL;
|
||||||
|
}
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
return gr_find_r(gr_match_name, name, gr, buf, bufsize, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
getgrgid_r(gid_t gid, struct group *gr, char *buf, size_t bufsize, struct group **result)
|
||||||
|
{
|
||||||
|
return gr_find_r(gr_match_gid, &gid, gr, buf, bufsize, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
getgrent_r(struct group *gr, char *buf, size_t bufsize, struct group **result)
|
||||||
|
{
|
||||||
|
struct group cur;
|
||||||
|
ssize_t got;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (result == NULL || gr == NULL)
|
||||||
|
{
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
if (buf == NULL && bufsize != 0)
|
||||||
|
{
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
*result = NULL;
|
||||||
|
if (gr_stream == NULL)
|
||||||
|
{
|
||||||
|
gr_stream = fopen(GRP_PATH, "r");
|
||||||
|
if (gr_stream == NULL)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
got = getline(&gr_line, &gr_cap, gr_stream);
|
||||||
|
if (got < 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
gr_chomp(gr_line, got);
|
||||||
|
if (gr_split(&cur, gr_line) != 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
rc = gr_store(&cur, gr, buf, bufsize);
|
||||||
|
if (rc == 0)
|
||||||
|
{
|
||||||
|
*result = gr;
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
struct group *
|
||||||
|
fgetgrent(FILE *stream)
|
||||||
|
{
|
||||||
|
struct group cur;
|
||||||
|
ssize_t got;
|
||||||
|
|
||||||
|
if (stream == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
got = getline(&gr_line, &gr_cap, stream);
|
||||||
|
if (got < 0)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
gr_chomp(gr_line, got);
|
||||||
|
if (gr_split(&cur, gr_line) == 0)
|
||||||
|
{
|
||||||
|
return gr_publish(gr_line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,433 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <pwd.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — password database (todo 37).
|
||||||
|
*
|
||||||
|
* getpwnam/getpwuid scan /etc/passwd with a fresh fopen per call (no
|
||||||
|
* persistent cursor); getpwent walks one FILE kept open between calls,
|
||||||
|
* which setpwent rewinds and endpwent closes. Every line is read with
|
||||||
|
* getline, one at a time — the whole file is never cached.
|
||||||
|
*
|
||||||
|
* Storage model: two static buffers back every non-reentrant result. A
|
||||||
|
* successful call returns a struct whose string members point into a
|
||||||
|
* private result copy; the getline scan that produced the match ran in a
|
||||||
|
* separate buffer, so a caller may feed the returned record's own name
|
||||||
|
* back into a later lookup (the getpwnam(getpwuid(x)->pw_name)
|
||||||
|
* round-trip) without the scan clobbering its key. The result copy is
|
||||||
|
* replaced by the next call to any of getpwnam/getpwuid/getpwent (or a
|
||||||
|
* realloc of the copy), matching POSIX result-lifetime rules. The _r
|
||||||
|
* forms never use this static storage for their result: the caller's
|
||||||
|
* struct and buffer receive the record, and the caller's *result is how
|
||||||
|
* the outcome is reported.
|
||||||
|
*
|
||||||
|
* Record syntax: seven ':'-separated fields, name:passwd:uid:gid:gecos:
|
||||||
|
* dir:shell. The last field (shell) keeps any further ':' characters as
|
||||||
|
* data. Blank or comment-style lines and records whose uid/gid are not
|
||||||
|
* decimal numbers are skipped like malformed input.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* The database path; not overridable (no NSS, no shadow, POSIX only). */
|
||||||
|
#define PWD_PATH "/etc/passwd"
|
||||||
|
|
||||||
|
/* Non-reentrant result: struct plus the private copy it points into. */
|
||||||
|
static struct passwd pw_result;
|
||||||
|
static char *pw_out;
|
||||||
|
static size_t pw_out_cap;
|
||||||
|
|
||||||
|
/* The getline scan buffer shared by every lookup and stream walk. */
|
||||||
|
static char *pw_line;
|
||||||
|
static size_t pw_cap;
|
||||||
|
|
||||||
|
/* The persistent getpwent stream; NULL when closed. */
|
||||||
|
static FILE *pw_stream;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Parse the leading digit string of s as an unsigned id. Accepts only
|
||||||
|
* plain decimal digits and rejects empty fields and overflow; returns 0
|
||||||
|
* on success.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
pw_parse_id(const char *s, unsigned *out)
|
||||||
|
{
|
||||||
|
unsigned long v = 0;
|
||||||
|
const char *p;
|
||||||
|
|
||||||
|
if (*s == '\0')
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
for (p = s; *p != '\0'; p++)
|
||||||
|
{
|
||||||
|
if (*p < '0' || *p > '9')
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
v = v * 10 + (unsigned long)(*p - '0');
|
||||||
|
if (v > 0xffffffffUL)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*out = (unsigned)v;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Split the line (newline already stripped) into its seven fields in
|
||||||
|
* place, replacing the first six ':' separators with NULs. On success the
|
||||||
|
* struct fields point into line and 0 is returned; a record with fewer
|
||||||
|
* than six separators or a non-numeric uid/gid yields -1.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
pw_split(struct passwd *pw, char *line)
|
||||||
|
{
|
||||||
|
char *fields[7];
|
||||||
|
char *p;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
p = line;
|
||||||
|
for (i = 0; i < 6; i++)
|
||||||
|
{
|
||||||
|
fields[i] = p;
|
||||||
|
p = strchr(p, ':');
|
||||||
|
if (p == NULL)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*p = '\0';
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
fields[6] = p;
|
||||||
|
if (pw_parse_id(fields[2], &pw->pw_uid) != 0 || pw_parse_id(fields[3], &pw->pw_gid) != 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
pw->pw_name = fields[0];
|
||||||
|
pw->pw_passwd = fields[1];
|
||||||
|
pw->pw_gecos = fields[4];
|
||||||
|
pw->pw_dir = fields[5];
|
||||||
|
pw->pw_shell = fields[6];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Strip a trailing newline (and CR) from the line just read by getline. */
|
||||||
|
static void
|
||||||
|
pw_chomp(char *line, ssize_t len)
|
||||||
|
{
|
||||||
|
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
|
||||||
|
{
|
||||||
|
line[--len] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make line — a chomped, already-split record in the scan buffer — the
|
||||||
|
* non-reentrant result: copy it into the private result buffer and
|
||||||
|
* re-split it there, so the returned struct's strings point into storage
|
||||||
|
* the scanner never touches again. Returns the result struct, or NULL if
|
||||||
|
* the result copy could not be grown.
|
||||||
|
*/
|
||||||
|
static struct passwd *
|
||||||
|
pw_publish(char *line)
|
||||||
|
{
|
||||||
|
size_t len = strlen(line) + 1;
|
||||||
|
|
||||||
|
if (len > pw_out_cap)
|
||||||
|
{
|
||||||
|
char *nb = (char *)realloc(pw_out, len);
|
||||||
|
|
||||||
|
if (nb == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
pw_out = nb;
|
||||||
|
pw_out_cap = len;
|
||||||
|
}
|
||||||
|
strcpy(pw_out, line);
|
||||||
|
if (pw_split(&pw_result, pw_out) != 0)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return &pw_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Match predicate over a parsed record, keyed by name or uid. */
|
||||||
|
typedef int (*pw_matcher)(const struct passwd *pw, const void *key);
|
||||||
|
|
||||||
|
static int
|
||||||
|
pw_match_name(const struct passwd *pw, const void *key)
|
||||||
|
{
|
||||||
|
return strcmp(pw->pw_name, (const char *)key) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
pw_match_uid(const struct passwd *pw, const void *key)
|
||||||
|
{
|
||||||
|
return pw->pw_uid == *(const uid_t *)key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Non-reentrant lookup core: open the database, scan with getline until
|
||||||
|
* the predicate matches, and hand back a pointer into the shared static
|
||||||
|
* line buffer. NULL means "no such entry" (or an unopenable database).
|
||||||
|
*/
|
||||||
|
static struct passwd *
|
||||||
|
pw_find(pw_matcher match, const void *key)
|
||||||
|
{
|
||||||
|
FILE *f = fopen(PWD_PATH, "r");
|
||||||
|
struct passwd cur;
|
||||||
|
ssize_t got;
|
||||||
|
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
got = getline(&pw_line, &pw_cap, f);
|
||||||
|
if (got < 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pw_chomp(pw_line, got);
|
||||||
|
if (pw_split(&cur, pw_line) == 0 && match(&cur, key))
|
||||||
|
{
|
||||||
|
(void)fclose(f);
|
||||||
|
return pw_publish(pw_line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(void)fclose(f);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reentrant serializer: copy the parsed record into the caller's struct
|
||||||
|
* and buffer. Returns 0 on success, or ERANGE (as the POSIX _r functions
|
||||||
|
* return, never touching errno) when the record needs more than bufsize
|
||||||
|
* bytes.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
pw_store(const struct passwd *in, struct passwd *pw, char *buf, size_t bufsize)
|
||||||
|
{
|
||||||
|
const char *fields[5] = {
|
||||||
|
in->pw_name, in->pw_passwd, in->pw_gecos, in->pw_dir, in->pw_shell,
|
||||||
|
};
|
||||||
|
char *p = buf;
|
||||||
|
size_t need = 0;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < 5; i++)
|
||||||
|
{
|
||||||
|
need += strlen(fields[i]) + 1;
|
||||||
|
}
|
||||||
|
if (bufsize < need)
|
||||||
|
{
|
||||||
|
return ERANGE;
|
||||||
|
}
|
||||||
|
pw->pw_uid = in->pw_uid;
|
||||||
|
pw->pw_gid = in->pw_gid;
|
||||||
|
pw->pw_name = p;
|
||||||
|
p += strlen(pw->pw_name) + 1;
|
||||||
|
pw->pw_passwd = p;
|
||||||
|
p += strlen(pw->pw_passwd) + 1;
|
||||||
|
pw->pw_gecos = p;
|
||||||
|
p += strlen(pw->pw_gecos) + 1;
|
||||||
|
pw->pw_dir = p;
|
||||||
|
p += strlen(pw->pw_dir) + 1;
|
||||||
|
pw->pw_shell = p;
|
||||||
|
strcpy(pw->pw_shell, in->pw_shell);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct passwd *
|
||||||
|
getpwnam(const char *name)
|
||||||
|
{
|
||||||
|
if (name == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return pw_find(pw_match_name, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct passwd *
|
||||||
|
getpwuid(uid_t uid)
|
||||||
|
{
|
||||||
|
return pw_find(pw_match_uid, &uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct passwd *
|
||||||
|
getpwent(void)
|
||||||
|
{
|
||||||
|
struct passwd cur;
|
||||||
|
ssize_t got;
|
||||||
|
|
||||||
|
if (pw_stream == NULL)
|
||||||
|
{
|
||||||
|
pw_stream = fopen(PWD_PATH, "r");
|
||||||
|
if (pw_stream == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
got = getline(&pw_line, &pw_cap, pw_stream);
|
||||||
|
if (got < 0)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
pw_chomp(pw_line, got);
|
||||||
|
if (pw_split(&cur, pw_line) == 0)
|
||||||
|
{
|
||||||
|
return pw_publish(pw_line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
setpwent(void)
|
||||||
|
{
|
||||||
|
if (pw_stream != NULL)
|
||||||
|
{
|
||||||
|
rewind(pw_stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
endpwent(void)
|
||||||
|
{
|
||||||
|
if (pw_stream != NULL)
|
||||||
|
{
|
||||||
|
(void)fclose(pw_stream);
|
||||||
|
pw_stream = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reentrant lookup core: scan for the first matching record and serialize
|
||||||
|
* it into the caller's storage. Returns 0 with *result pointing at the
|
||||||
|
* filled struct on success, 0 with *result NULL when no record matches,
|
||||||
|
* ERANGE when the record does not fit bufsize, and EINVAL for a NULL
|
||||||
|
* result pointer.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
pw_find_r(pw_matcher match, const void *key, struct passwd *pw, char *buf, size_t bufsize,
|
||||||
|
struct passwd **result)
|
||||||
|
{
|
||||||
|
FILE *f;
|
||||||
|
struct passwd cur;
|
||||||
|
ssize_t got;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (result == NULL || pw == NULL)
|
||||||
|
{
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
if (buf == NULL && bufsize != 0)
|
||||||
|
{
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
*result = NULL;
|
||||||
|
f = fopen(PWD_PATH, "r");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
got = getline(&pw_line, &pw_cap, f);
|
||||||
|
if (got < 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pw_chomp(pw_line, got);
|
||||||
|
if (pw_split(&cur, pw_line) != 0 || !match(&cur, key))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
rc = pw_store(&cur, pw, buf, bufsize);
|
||||||
|
(void)fclose(f);
|
||||||
|
if (rc == 0)
|
||||||
|
{
|
||||||
|
*result = pw;
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
(void)fclose(f);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
getpwnam_r(const char *name, struct passwd *pw, char *buf, size_t bufsize, struct passwd **result)
|
||||||
|
{
|
||||||
|
if (name == NULL)
|
||||||
|
{
|
||||||
|
if (result != NULL)
|
||||||
|
{
|
||||||
|
*result = NULL;
|
||||||
|
}
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
return pw_find_r(pw_match_name, name, pw, buf, bufsize, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
getpwuid_r(uid_t uid, struct passwd *pw, char *buf, size_t bufsize, struct passwd **result)
|
||||||
|
{
|
||||||
|
return pw_find_r(pw_match_uid, &uid, pw, buf, bufsize, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
getpwent_r(struct passwd *pw, char *buf, size_t bufsize, struct passwd **result)
|
||||||
|
{
|
||||||
|
struct passwd cur;
|
||||||
|
ssize_t got;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (result == NULL || pw == NULL)
|
||||||
|
{
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
if (buf == NULL && bufsize != 0)
|
||||||
|
{
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
*result = NULL;
|
||||||
|
if (pw_stream == NULL)
|
||||||
|
{
|
||||||
|
pw_stream = fopen(PWD_PATH, "r");
|
||||||
|
if (pw_stream == NULL)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
got = getline(&pw_line, &pw_cap, pw_stream);
|
||||||
|
if (got < 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
pw_chomp(pw_line, got);
|
||||||
|
if (pw_split(&cur, pw_line) != 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
rc = pw_store(&cur, pw, buf, bufsize);
|
||||||
|
if (rc == 0)
|
||||||
|
{
|
||||||
|
*result = pw;
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,343 @@
|
|||||||
|
/*
|
||||||
|
* vlibc — pwd.h/grp.h test (todo 37).
|
||||||
|
*
|
||||||
|
* Exercises the password and group databases end to end without assuming
|
||||||
|
* anything about their CONTENTS: every lookup is asserted either to
|
||||||
|
* return a sane record (string fields non-NULL, ids echo the key) or
|
||||||
|
* NULL, and every stream walk is asserted only to terminate cleanly. The
|
||||||
|
* one environment precondition is that /etc/passwd and /etc/group EXIST
|
||||||
|
* and are readable; when either is absent the whole test skips (rc 0),
|
||||||
|
* detected through a raw SYS_faccessat so no vlibc errno slot is touched
|
||||||
|
* on the skip path.
|
||||||
|
*
|
||||||
|
* Scenarios (all content-independent):
|
||||||
|
*
|
||||||
|
* 1. current-uid: getpwuid(getuid()) is NULL or a record whose pw_uid
|
||||||
|
* echoes the key and whose pw_name is set; when a record comes back,
|
||||||
|
* getpwnam(pw_name) finds it again (name round-trip).
|
||||||
|
* 2. current-gid: the same pair against the group database.
|
||||||
|
* 3. stream walk: setpwent/getpwent/endpwent; two consecutive full
|
||||||
|
* walks return the same count (the stream really rewinds), every
|
||||||
|
* record is sane, and the walk terminates.
|
||||||
|
* 4. group members: every parsed record has a non-NULL gr_mem array
|
||||||
|
* (the empty list is a single NULL entry), and member arrays
|
||||||
|
* terminate.
|
||||||
|
* 5. _r forms: getpwuid_r/getgrgid_r into a 4096-byte buffer return 0
|
||||||
|
* and either *result == NULL (key absent) or a filled struct echoing
|
||||||
|
* the key; getpwent_r/getgrent_r likewise fill one record.
|
||||||
|
* 6. NULL keys: getpwnam(NULL)/getgrnam(NULL) return NULL (defensive
|
||||||
|
* contract, no crash).
|
||||||
|
* 7. -f mode: impossible keys (uid/gid 2147483647, a name no real
|
||||||
|
* database carries) come back NULL without crashing, then the test
|
||||||
|
* leaves via a raw SYS_exit_group (house pattern: after a vlibc
|
||||||
|
* errno-capable call the host TCB may be dirty).
|
||||||
|
* 8. fgetgrent (level 2 only): parse one record from an explicitly
|
||||||
|
* fopen'd stream.
|
||||||
|
*
|
||||||
|
* Host headers are never included; <pwd.h>/<grp.h>/<unistd.h> are
|
||||||
|
* vlibc's own (-Iinclude shadows the system copies). getuid/getgid come
|
||||||
|
* from src/process/uid.c.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <grp.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <vlibc/internal/test.h>
|
||||||
|
|
||||||
|
/* Local kernel-ABI constants for the skip probe (no public header pulled). */
|
||||||
|
#define PG_AT_FDCWD (-100)
|
||||||
|
#define PG_R_OK 4
|
||||||
|
|
||||||
|
/* Both databases readable (the only environment assumption). */
|
||||||
|
static int
|
||||||
|
pg_db_present(void)
|
||||||
|
{
|
||||||
|
return __syscall4(SYS_faccessat, PG_AT_FDCWD, (long)"/etc/passwd", PG_R_OK, 0) == 0 &&
|
||||||
|
__syscall4(SYS_faccessat, PG_AT_FDCWD, (long)"/etc/group", PG_R_OK, 0) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
test_current_uid(void)
|
||||||
|
{
|
||||||
|
uid_t uid = getuid();
|
||||||
|
struct passwd *p = getpwuid(uid);
|
||||||
|
|
||||||
|
if (p == NULL)
|
||||||
|
{
|
||||||
|
return 0; /* key absent from the database: acceptable */
|
||||||
|
}
|
||||||
|
TEST_ASSERT_TRUE(p->pw_name != NULL);
|
||||||
|
TEST_ASSERT_EQ((unsigned long)p->pw_uid, (unsigned long)uid);
|
||||||
|
if (p->pw_name != NULL)
|
||||||
|
{
|
||||||
|
struct passwd *q = getpwnam(p->pw_name);
|
||||||
|
|
||||||
|
TEST_ASSERT_TRUE(q != NULL);
|
||||||
|
if (q != NULL)
|
||||||
|
{
|
||||||
|
TEST_ASSERT_EQ((unsigned long)q->pw_uid, (unsigned long)uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
test_current_gid(void)
|
||||||
|
{
|
||||||
|
gid_t gid = getgid();
|
||||||
|
struct group *g = getgrgid(gid);
|
||||||
|
|
||||||
|
if (g == NULL)
|
||||||
|
{
|
||||||
|
return 0; /* key absent from the database: acceptable */
|
||||||
|
}
|
||||||
|
TEST_ASSERT_TRUE(g->gr_name != NULL);
|
||||||
|
TEST_ASSERT_EQ((unsigned long)g->gr_gid, (unsigned long)gid);
|
||||||
|
if (g->gr_name != NULL)
|
||||||
|
{
|
||||||
|
struct group *h = getgrnam(g->gr_name);
|
||||||
|
|
||||||
|
TEST_ASSERT_TRUE(h != NULL);
|
||||||
|
if (h != NULL)
|
||||||
|
{
|
||||||
|
TEST_ASSERT_EQ((unsigned long)h->gr_gid, (unsigned long)gid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Count the records of one full pwd walk, sanity-checking each. */
|
||||||
|
static unsigned long
|
||||||
|
pg_pwd_walk_count(void)
|
||||||
|
{
|
||||||
|
struct passwd *p;
|
||||||
|
unsigned long n = 0;
|
||||||
|
|
||||||
|
setpwent();
|
||||||
|
while ((p = getpwent()) != NULL)
|
||||||
|
{
|
||||||
|
TEST_ASSERT_TRUE(p->pw_name != NULL);
|
||||||
|
if (++n > 100000UL)
|
||||||
|
{
|
||||||
|
break; /* runaway guard: the walk must terminate */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endpwent();
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
test_pwd_stream(void)
|
||||||
|
{
|
||||||
|
unsigned long first = pg_pwd_walk_count();
|
||||||
|
unsigned long second = pg_pwd_walk_count();
|
||||||
|
|
||||||
|
/* The walk terminates and setpwent really rewinds (0 == 0 for an
|
||||||
|
* empty database is still a valid equality). */
|
||||||
|
TEST_ASSERT_TRUE(first < 100000UL);
|
||||||
|
TEST_ASSERT_EQ(first, second);
|
||||||
|
|
||||||
|
/* After endpwent a fresh getpwent reopens from the start: the result
|
||||||
|
* is the first record, or NULL only when the database is empty. */
|
||||||
|
if (first != 0)
|
||||||
|
{
|
||||||
|
struct passwd *p = getpwent();
|
||||||
|
|
||||||
|
TEST_ASSERT_TRUE(p != NULL);
|
||||||
|
endpwent();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Every parsed group has a terminating gr_mem array; empty lists appear
|
||||||
|
* as a single NULL entry, so gr_mem itself is never NULL. */
|
||||||
|
static int
|
||||||
|
test_group_members(void)
|
||||||
|
{
|
||||||
|
struct group *g;
|
||||||
|
unsigned long n = 0;
|
||||||
|
|
||||||
|
setgrent();
|
||||||
|
while ((g = getgrent()) != NULL)
|
||||||
|
{
|
||||||
|
char **m;
|
||||||
|
unsigned long members = 0;
|
||||||
|
|
||||||
|
TEST_ASSERT_TRUE(g->gr_name != NULL);
|
||||||
|
TEST_ASSERT_TRUE(g->gr_mem != NULL);
|
||||||
|
for (m = g->gr_mem; m != NULL && *m != NULL; m++)
|
||||||
|
{
|
||||||
|
TEST_ASSERT_TRUE(**m != '\0');
|
||||||
|
if (++members > 100000UL)
|
||||||
|
{
|
||||||
|
break; /* runaway guard: member arrays must terminate */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TEST_ASSERT_TRUE(members < 100000UL);
|
||||||
|
if (++n > 100000UL)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endgrent();
|
||||||
|
TEST_ASSERT_TRUE(n < 100000UL);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
test_pwd_r(void)
|
||||||
|
{
|
||||||
|
struct passwd pw;
|
||||||
|
struct passwd *res = NULL;
|
||||||
|
char buf[4096];
|
||||||
|
int rc = getpwuid_r(getuid(), &pw, buf, sizeof buf, &res);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQ(rc, 0);
|
||||||
|
TEST_ASSERT_TRUE(res == NULL || res == &pw);
|
||||||
|
if (res == &pw)
|
||||||
|
{
|
||||||
|
TEST_ASSERT_EQ((unsigned long)pw.pw_uid, (unsigned long)getuid());
|
||||||
|
TEST_ASSERT_TRUE(pw.pw_name != NULL);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
test_grp_r(void)
|
||||||
|
{
|
||||||
|
struct group gr;
|
||||||
|
struct group *res = NULL;
|
||||||
|
char buf[4096];
|
||||||
|
int rc = getgrgid_r(getgid(), &gr, buf, sizeof buf, &res);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQ(rc, 0);
|
||||||
|
TEST_ASSERT_TRUE(res == NULL || res == &gr);
|
||||||
|
if (res == &gr)
|
||||||
|
{
|
||||||
|
TEST_ASSERT_EQ((unsigned long)gr.gr_gid, (unsigned long)getgid());
|
||||||
|
TEST_ASSERT_TRUE(gr.gr_name != NULL);
|
||||||
|
TEST_ASSERT_TRUE(gr.gr_mem != NULL);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
test_null_keys(void)
|
||||||
|
{
|
||||||
|
TEST_ASSERT_TRUE(getpwnam(NULL) == NULL);
|
||||||
|
TEST_ASSERT_TRUE(getgrnam(NULL) == NULL);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
static int
|
||||||
|
test_fgetgrent(void)
|
||||||
|
{
|
||||||
|
FILE *f = fopen("/etc/group", "r");
|
||||||
|
struct group *g;
|
||||||
|
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return 0; /* opened by the caller, closed by the caller */
|
||||||
|
}
|
||||||
|
g = fgetgrent(f);
|
||||||
|
if (g != NULL)
|
||||||
|
{
|
||||||
|
TEST_ASSERT_TRUE(g->gr_name != NULL);
|
||||||
|
TEST_ASSERT_TRUE(g->gr_mem != NULL);
|
||||||
|
}
|
||||||
|
(void)fclose(f);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
static const struct vlibc_test tests[] = {
|
||||||
|
{"current-uid", test_current_uid},
|
||||||
|
{"current-gid", test_current_gid},
|
||||||
|
{"pwd-stream", test_pwd_stream},
|
||||||
|
{"group-members", test_group_members},
|
||||||
|
{"pwd-r", test_pwd_r},
|
||||||
|
{"grp-r", test_grp_r},
|
||||||
|
{"null-keys", test_null_keys},
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
{"fgetgrent", test_fgetgrent},
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Own main (not TEST_MAIN): supports the -f failure mode. Everything else
|
||||||
|
* follows the TEST_MAIN contract — same output shape, 0 on all-pass.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
const size_t count = sizeof tests / sizeof tests[0];
|
||||||
|
size_t passed = 0;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'f' && argv[1][2] == '\0')
|
||||||
|
{
|
||||||
|
/* Impossible keys must come back NULL without crashing. */
|
||||||
|
int before = vlibc_test_failures;
|
||||||
|
uid_t uid = (uid_t)2147483647UL;
|
||||||
|
gid_t gid = (gid_t)2147483647UL;
|
||||||
|
|
||||||
|
vlibc_test_say(1, "RUN missing-lookups: ");
|
||||||
|
TEST_ASSERT_TRUE(getpwuid(uid) == NULL);
|
||||||
|
TEST_ASSERT_TRUE(getgrgid(gid) == NULL);
|
||||||
|
TEST_ASSERT_TRUE(getpwnam("vlibc-no-such-user-xyz") == NULL);
|
||||||
|
TEST_ASSERT_TRUE(getgrnam("vlibc-no-such-group-xyz") == NULL);
|
||||||
|
if (vlibc_test_failures == before)
|
||||||
|
{
|
||||||
|
vlibc_test_say(1, "PASS\n");
|
||||||
|
vlibc_test_say(1, "SUMMARY: 1/1 passed, 0 assertion failure(s)\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vlibc_test_say(1, "FAIL\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Raw exit: the NULL-return paths may have written the library's
|
||||||
|
* errno slot, which collides with the host TCB (house pattern). */
|
||||||
|
__syscall1(SYS_exit_group, vlibc_test_failures == before ? 0 : 1);
|
||||||
|
/* not reached */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pg_db_present())
|
||||||
|
{
|
||||||
|
vlibc_test_say(1, "SKIP: /etc/passwd or /etc/group absent, nothing to look up\n");
|
||||||
|
__syscall1(SYS_exit_group, 0);
|
||||||
|
/* not reached */
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
int before = vlibc_test_failures;
|
||||||
|
|
||||||
|
vlibc_test_say(1, "RUN ");
|
||||||
|
vlibc_test_say(1, tests[i].name);
|
||||||
|
vlibc_test_say(1, ": ");
|
||||||
|
if (tests[i].run() == 0 && vlibc_test_failures == before)
|
||||||
|
{
|
||||||
|
vlibc_test_say(1, "PASS\n");
|
||||||
|
passed++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vlibc_test_say(1, "FAIL\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vlibc_test_say(1, "SUMMARY: ");
|
||||||
|
vlibc_test_say_dec(1, (unsigned long)passed);
|
||||||
|
vlibc_test_say(1, "/");
|
||||||
|
vlibc_test_say_dec(1, (unsigned long)count);
|
||||||
|
vlibc_test_say(1, " passed, ");
|
||||||
|
vlibc_test_say_dec(1, (unsigned long)vlibc_test_failures);
|
||||||
|
vlibc_test_say(1, " assertion failure(s)\n");
|
||||||
|
|
||||||
|
return passed == count ? 0 : 1;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user