Thanks for the feedback, here's an updated diff that eliminates
pwcache.h, gracefully handles table allocation failure and massages
the comments to be a bit more general.

I can take a look at supporting arbitrary length names in the future.

 - todd

Index: include/pwd.h
===================================================================
RCS file: /cvs/src/include/pwd.h,v
retrieving revision 1.25
diff -u -p -u -r1.25 pwd.h
--- include/pwd.h       9 Mar 2017 10:13:03 -0000       1.25
+++ include/pwd.h       10 Sep 2018 00:46:44 -0000
@@ -106,7 +106,8 @@ void                 endpwent(void);
 #endif
 #if __BSD_VISIBLE
 int             setpassent(int);
-char           *user_from_uid(uid_t, int);
+int             uid_from_user(const char *, uid_t *);
+const char     *user_from_uid(uid_t, int);
 char           *bcrypt_gensalt(u_int8_t);
 char           *bcrypt(const char *, const char *);
 int            bcrypt_newhash(const char *, int, char *, size_t);
Index: include/grp.h
===================================================================
RCS file: /cvs/src/include/grp.h,v
retrieving revision 1.12
diff -u -p -u -r1.12 grp.h
--- include/grp.h       31 Aug 2014 04:04:38 -0000      1.12
+++ include/grp.h       10 Sep 2018 00:46:44 -0000
@@ -70,7 +70,8 @@ int            getgrnam_r(const char *, struct gr
 #endif
 #if __BSD_VISIBLE
 int             setgroupent(int);
-char           *group_from_gid(gid_t, int);
+int             gid_from_group(const char *, gid_t *);
+const char     *group_from_gid(gid_t, int);
 #endif
 __END_DECLS
 
Index: lib/libc/gen/pwcache.3
===================================================================
RCS file: /cvs/src/lib/libc/gen/pwcache.3,v
retrieving revision 1.13
diff -u -p -u -r1.13 pwcache.3
--- lib/libc/gen/pwcache.3      26 Mar 2016 14:36:37 -0000      1.13
+++ lib/libc/gen/pwcache.3      10 Sep 2018 00:46:44 -0000
@@ -32,14 +32,20 @@
 .Os
 .Sh NAME
 .Nm user_from_uid ,
+.Nm uid_from_user ,
 .Nm group_from_gid
+.Nm gid_from_group
 .Nd cache password and group entries
 .Sh SYNOPSIS
-.In grp.h
 .In pwd.h
-.Ft char *
+.Ft int
+.Fn uid_from_user "const char *name" "uid_t *uid"
+.Ft const char *
 .Fn user_from_uid "uid_t uid" "int nouser"
-.Ft char *
+.In grp.h
+.Ft int
+.Fn gid_from_group "const char *name" "gid_t *gid"
+.Ft const char *
 .Fn group_from_gid "gid_t gid" "int nogroup"
 .Sh DESCRIPTION
 The
@@ -60,6 +66,22 @@ unless the argument
 is non-zero, in which case a null pointer is returned.
 .Pp
 The
+.Fn uid_from_user
+function returns the uid associated with the argument
+.Fa name .
+The uid is cached so that multiple calls with the same
+.Fa name
+do not require additional calls to
+.Xr getpwnam 3 .
+If there is no uid associated with the
+.Fa name ,
+the
+.Fn uid_from_user
+function returns -1; otherwise it stores the uid at the location pointed to by
+.Fa uid
+and returns 0.
+.Pp
+The
 .Fn group_from_gid
 function returns the group name associated with the argument
 .Fa gid .
@@ -75,6 +97,22 @@ to a string representation of the
 unless the argument
 .Fa nogroup
 is non-zero, in which case a null pointer is returned.
+.Pp
+The
+.Fn gid_from_group
+function returns the gid associated with the argument
+.Fa name .
+The gid is cached so that multiple calls with the same
+.Fa name
+do not require additional calls to
+.Xr getgrnam 3 .
+If there is no gid associated with the
+.Fa name ,
+the
+.Fn gid_from_group
+function returns -1; otherwise it stores the gid at the location pointed to by
+.Fa gid
+and returns 0.
 .Sh SEE ALSO
 .Xr getgrgid 3 ,
 .Xr getpwuid 3
@@ -85,3 +123,12 @@ and
 .Fn group_from_gid
 functions first appeared in
 .Bx 4.4 .
+.Pp
+The
+.Fn uid_from_user
+and
+.Fn gid_from_group
+functions were ported from
+.Nx
+and first appeared in
+.Ox 6.4 .
Index: lib/libc/gen/pwcache.c
===================================================================
RCS file: /cvs/src/lib/libc/gen/pwcache.c,v
retrieving revision 1.13
diff -u -p -u -r1.13 pwcache.c
--- lib/libc/gen/pwcache.c      25 Nov 2015 23:16:01 -0000      1.13
+++ lib/libc/gen/pwcache.c      12 Sep 2018 13:45:40 -0000
@@ -1,8 +1,13 @@
-/*     $OpenBSD: pwcache.c,v 1.13 2015/11/25 23:16:01 jcs Exp $ */
-/*
- * Copyright (c) 1989, 1993
+/*     $OpenBSD: pwcache.c,v 1.13 2015/11/25 23:16:01 jcs Exp $        */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
  *     The Regents of the University of California.  All rights reserved.
  *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
@@ -30,102 +35,403 @@
 
 #include <sys/types.h>
 
+#include <assert.h>
 #include <grp.h>
 #include <pwd.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
+#include <unistd.h>
+
+/*
+ * Constants and data structures used to implement group and password file
+ * caches.  Name lengths have been chosen to be as large as those supported
+ * by the passwd and group files as well as the standard archive formats.
+ * CACHE SIZES MUST BE PRIME
+ */
+#define UNMLEN         32      /* >= user name found in any protocol */
+#define GNMLEN         32      /* >= group name found in any protocol */
+#define UID_SZ         317     /* size of uid to user_name cache */
+#define UNM_SZ         317     /* size of user_name to uid cache */
+#define GID_SZ         251     /* size of gid to group_name cache */
+#define GNM_SZ         251     /* size of group_name to gid cache */
+#define VALID          1       /* entry and name are valid */
+#define INVALID                2       /* entry valid, name NOT valid */
+
+/*
+ * Node structures used in the user, group, uid, and gid caches.
+ */
+
+typedef struct uidc {
+       int valid;              /* is this a valid or a miss entry */
+       char name[UNMLEN];      /* uid name */
+       uid_t uid;              /* cached uid */
+} UIDC;
+
+typedef struct gidc {
+       int valid;              /* is this a valid or a miss entry */
+       char name[GNMLEN];      /* gid name */
+       gid_t gid;              /* cached gid */
+} GIDC;
+
+/*
+ * Routines that control user, group, uid and gid caches.
+ * Traditional passwd/group cache routines perform quite poorly with
+ * archives. The chances of hitting a valid lookup with an archive is quite a
+ * bit worse than with files already resident on the file system. These misses
+ * create a MAJOR performance cost. To adress this problem, these routines
+ * cache both hits and misses.
+ */
+
+static UIDC **uidtb;   /* uid to name cache */
+static GIDC **gidtb;   /* gid to name cache */
+static UIDC **usrtb;   /* user name to uid cache */
+static GIDC **grptb;   /* group name to gid cache */
+
+static u_int
+st_hash(const char *name, size_t len, int tabsz)
+{
+       u_int key = 0;
+
+       assert(name != NULL);
+
+       while (len--) {
+               key += *name++;
+               key = (key << 8) | (key >> 24);
+       }
+
+       return key % tabsz;
+}
+
+/*
+ * uidtb_start
+ *     creates an an empty uidtb
+ * Return:
+ *     0 if ok, -1 otherwise
+ */
+static int
+uidtb_start(void)
+{
+       static int fail = 0;
+
+       if (uidtb != NULL)
+               return 0;
+       if (fail)
+               return -1;
+       if ((uidtb = calloc(UID_SZ, sizeof(UIDC *))) == NULL) {
+               ++fail;
+               return -1;
+       }
+       return 0;
+}
+
+/*
+ * gidtb_start
+ *     creates an an empty gidtb
+ * Return:
+ *     0 if ok, -1 otherwise
+ */
+static int
+gidtb_start(void)
+{
+       static int fail = 0;
+
+       if (gidtb != NULL)
+               return 0;
+       if (fail)
+               return -1;
+       if ((gidtb = calloc(GID_SZ, sizeof(GIDC *))) == NULL) {
+               ++fail;
+               return -1;
+       }
+       return 0;
+}
+
+/*
+ * usrtb_start
+ *     creates an an empty usrtb
+ * Return:
+ *     0 if ok, -1 otherwise
+ */
+static int
+usrtb_start(void)
+{
+       static int fail = 0;
+
+       if (usrtb != NULL)
+               return 0;
+       if (fail)
+               return -1;
+       if ((usrtb = calloc(UNM_SZ, sizeof(UIDC *))) == NULL) {
+               ++fail;
+               return -1;
+       }
+       return 0;
+}
+
+/*
+ * grptb_start
+ *     creates an an empty grptb
+ * Return:
+ *     0 if ok, -1 otherwise
+ */
+static int
+grptb_start(void)
+{
+       static int fail = 0;
+
+       if (grptb != NULL)
+               return 0;
+       if (fail)
+               return -1;
+       if ((grptb = calloc(GNM_SZ, sizeof(GIDC *))) == NULL) {
+               ++fail;
+               return -1;
+       }
+       return 0;
+}
 
-#define        NCACHE  16                      /* power of 2 */
-#define        NLINES  4                       /* associativity */
-#define        MASK    (NCACHE - 1)            /* bits to store with */
-#define        IDX(x, i)       ((x & MASK) + i * NCACHE)
-
-char *
-user_from_uid(uid_t uid, int nouser)
-{
-       static struct ncache {
-               uid_t   uid;
-               short   noname;
-               char    name[_PW_NAME_LEN + 1];
-       } c_uid[NLINES * NCACHE];
+/*
+ * user_from_uid()
+ *     caches the name (if any) for the uid. If noname clear, we always
+ *     return the stored name (if valid or invalid match).
+ *     We use a simple hash table.
+ * Return
+ *     Pointer to stored name (or a empty string)
+ */
+const char *
+user_from_uid(uid_t uid, int noname)
+{
+       struct passwd pwstore, *pw = NULL;
        char pwbuf[_PW_BUF_LEN];
-       struct passwd pwstore, *pw;
-       struct ncache *cp;
-       unsigned int i;
-
-       for (i = 0; i < NLINES; i++) {
-               cp = &c_uid[IDX(uid, i)];
-               if (!*cp->name) {
-fillit:
-                       cp->uid = uid;
-                       pw = NULL;
-                       getpwuid_r(uid, &pwstore, pwbuf, sizeof(pwbuf), &pw);
-                       if (pw == NULL) {
-                               snprintf(cp->name, sizeof(cp->name), "%u", uid);
-                               cp->noname = 1;
-                       } else {
-                               strlcpy(cp->name, pw->pw_name, 
sizeof(cp->name));
-                       }
-               }
-               if (cp->uid == uid) {
-                       if (nouser && cp->noname)
-                               return NULL;
-                       return cp->name;
+       UIDC **pptr, *ptr = NULL;
+
+       if ((uidtb != NULL) || (uidtb_start() == 0)) {
+               /*
+                * see if we have this uid cached
+                */
+               pptr = uidtb + (uid % UID_SZ);
+               ptr = *pptr;
+
+               if ((ptr != NULL) && (ptr->valid > 0) && (ptr->uid == uid)) {
+                       /*
+                        * have an entry for this uid
+                        */
+                       if (!noname || (ptr->valid == VALID))
+                               return ptr->name;
+                       return NULL;
                }
+
+               if (ptr == NULL)
+                       *pptr = ptr = malloc(sizeof(UIDC));
+       }
+
+       getpwuid_r(uid, &pwstore, pwbuf, sizeof(pwbuf), &pw);
+       if (pw == NULL) {
+               /*
+                * no match for this uid in the local password file
+                * a string that is the uid in numeric format
+                */
+               if (ptr == NULL)
+                       return NULL;
+               ptr->uid = uid;
+               (void)snprintf(ptr->name, UNMLEN, "%u", uid);
+               ptr->valid = INVALID;
+               if (noname)
+                       return NULL;
+       } else {
+               /*
+                * there is an entry for this uid in the password file
+                */
+               if (ptr == NULL)
+                       return pw->pw_name;
+               ptr->uid = uid;
+               (void)strlcpy(ptr->name, pw->pw_name, sizeof(ptr->name));
+               ptr->valid = VALID;
        }
-       /* move everybody down a slot */
-       for (i = 0; i < NLINES - 1; i++) {
-               struct ncache *next;
-
-               cp = &c_uid[IDX(uid, i)];
-               next = &c_uid[IDX(uid, i + 1)];
-               memcpy(next, cp, sizeof(*cp));
-       }
-       cp = &c_uid[IDX(uid, 0)];
-       goto fillit;
-}
-
-char *
-group_from_gid(gid_t gid, int nogroup)
-{
-       static struct ncache {
-               gid_t   gid;
-               short   noname;
-               char    name[_PW_NAME_LEN + 1];
-       } c_gid[NLINES * NCACHE];
+       return ptr->name;
+}
+
+/*
+ * group_from_gid()
+ *     caches the name (if any) for the gid. If noname clear, we always
+ *     return the stored name (if valid or invalid match).
+ *     We use a simple hash table.
+ * Return
+ *     Pointer to stored name (or a empty string)
+ */
+const char *
+group_from_gid(gid_t gid, int noname)
+{
+       struct group grstore, *gr = NULL;
        char grbuf[_GR_BUF_LEN];
-       struct group grstore, *gr;
-       struct ncache *cp;
-       unsigned int i;
-
-       for (i = 0; i < NLINES; i++) {
-               cp = &c_gid[IDX(gid, i)];
-               if (!*cp->name) {
-fillit:
-                       cp->gid = gid;
-                       gr = NULL;
-                       getgrgid_r(gid, &grstore, grbuf, sizeof(grbuf), &gr);
-                       if (gr == NULL) {
-                               snprintf(cp->name, sizeof(cp->name), "%u", gid);
-                               cp->noname = 1;
-                       } else {
-                               strlcpy(cp->name, gr->gr_name, 
sizeof(cp->name));
-                       }
+       GIDC **pptr, *ptr = NULL;
+
+       if ((gidtb != NULL) || (gidtb_start() == 0)) {
+               /*
+                * see if we have this gid cached
+                */
+               pptr = gidtb + (gid % GID_SZ);
+               ptr = *pptr;
+
+               if ((ptr != NULL) && (ptr->valid > 0) && (ptr->gid == gid)) {
+                       /*
+                        * have an entry for this gid
+                        */
+                       if (!noname || (ptr->valid == VALID))
+                               return ptr->name;
+                       return NULL;
                }
-               if (cp->gid == gid) {
-                       if (nogroup && cp->noname)
-                               return NULL;
-                       return cp->name;
+
+               if (ptr == NULL)
+                       *pptr = ptr = malloc(sizeof(GIDC));
+       }
+
+       getgrgid_r(gid, &grstore, grbuf, sizeof(grbuf), &gr);
+       if (gr == NULL) {
+               /*
+                * no match for this gid in the local group file, put in
+                * a string that is the gid in numeric format
+                */
+               if (ptr == NULL)
+                       return NULL;
+               ptr->gid = gid;
+               (void)snprintf(ptr->name, GNMLEN, "%u", gid);
+               ptr->valid = INVALID;
+               if (noname)
+                       return NULL;
+       } else {
+               /*
+                * there is an entry for this group in the group file
+                */
+               if (ptr == NULL)
+                       return gr->gr_name;
+               ptr->gid = gid;
+               (void)strlcpy(ptr->name, gr->gr_name, sizeof(ptr->name));
+               ptr->valid = VALID;
+       }
+       return ptr->name;
+}
+
+/*
+ * uid_from_user()
+ *     caches the uid for a given user name. We use a simple hash table.
+ * Return
+ *     the uid (if any) for a user name, or a -1 if no match can be found
+ */
+int
+uid_from_user(const char *name, uid_t *uid)
+{
+       struct passwd pwstore, *pw = NULL;
+       char pwbuf[_PW_BUF_LEN];
+       UIDC **pptr, *ptr = NULL;
+       size_t namelen;
+
+       /*
+        * return -1 for mangled names
+        */
+       if (name == NULL || ((namelen = strlen(name)) == 0))
+               return -1;
+
+       if ((usrtb != NULL) || (usrtb_start() == 0)) {
+               /*
+                * look up in hash table, if found and valid return the uid,
+                * if found and invalid, return a -1
+                */
+               pptr = usrtb + st_hash(name, namelen, UNM_SZ);
+               ptr = *pptr;
+
+               if ((ptr != NULL) && (ptr->valid > 0) &&
+                   strcmp(name, ptr->name) == 0) {
+                       if (ptr->valid == INVALID)
+                               return -1;
+                       *uid = ptr->uid;
+                       return 0;
+               }
+
+               if (ptr == NULL)
+                       *pptr = ptr = malloc(sizeof(UIDC));
+       }
+
+       /*
+        * no match, look it up, if no match store it as an invalid entry,
+        * or store the matching uid
+        */
+       getpwnam_r(name, &pwstore, pwbuf, sizeof(pwbuf), &pw);
+       if (ptr == NULL) {
+               if (pw == NULL)
+                       return -1;
+               *uid = pw->pw_uid;
+               return 0;
+       }
+       (void)strlcpy(ptr->name, name, sizeof(ptr->name));
+       if (pw == NULL) {
+               ptr->valid = INVALID;
+               return -1;
+       }
+       ptr->valid = VALID;
+       *uid = ptr->uid = pw->pw_uid;
+       return 0;
+}
+
+/*
+ * gid_from_group()
+ *     caches the gid for a given group name. We use a simple hash table.
+ * Return
+ *     the gid (if any) for a group name, or a -1 if no match can be found
+ */
+int
+gid_from_group(const char *name, gid_t *gid)
+{
+       struct group grstore, *gr = NULL;
+       char grbuf[_GR_BUF_LEN];
+       GIDC **pptr, *ptr = NULL;
+       size_t namelen;
+
+       /*
+        * return -1 for mangled names
+        */
+       if (name == NULL || ((namelen = strlen(name)) == 0))
+               return -1;
+
+       if ((grptb != NULL) || (grptb_start() == 0)) {
+               /*
+                * look up in hash table, if found and valid return the uid,
+                * if found and invalid, return a -1
+                */
+               pptr = grptb + st_hash(name, namelen, GID_SZ);
+               ptr = *pptr;
+
+               if ((ptr != NULL) && (ptr->valid > 0) &&
+                   strcmp(name, ptr->name) == 0) {
+                       if (ptr->valid == INVALID)
+                               return -1;
+                       *gid = ptr->gid;
+                       return 0;
                }
+
+               if (ptr == NULL)
+                       *pptr = ptr = malloc(sizeof(GIDC));
        }
-       /* move everybody down a slot */
-       for (i = 0; i < NLINES - 1; i++) {
-               struct ncache *next;
-
-               cp = &c_gid[IDX(gid, i)];
-               next = &c_gid[IDX(gid, i + 1)];
-               memcpy(next, cp, sizeof(*cp));
+
+       /*
+        * no match, look it up, if no match store it as an invalid entry,
+        * or store the matching gid
+        */
+       getgrnam_r(name, &grstore, grbuf, sizeof(grbuf), &gr);
+       if (ptr == NULL) {
+               if (gr == NULL)
+                       return -1;
+               *gid = gr->gr_gid;
+               return 0;
+       }
+
+       (void)strlcpy(ptr->name, name, sizeof(ptr->name));
+       if (gr == NULL) {
+               ptr->valid = INVALID;
+               return -1;
        }
-       cp = &c_gid[IDX(gid, 0)];
-       goto fillit;
+       ptr->valid = VALID;
+       *gid = ptr->gid = gr->gr_gid;
+       return 0;
 }
Index: lib/libc/gen/getgrent.c
===================================================================
RCS file: /cvs/src/lib/libc/gen/getgrent.c,v
retrieving revision 1.46
diff -u -p -u -r1.46 getgrent.c
--- lib/libc/gen/getgrent.c     1 Dec 2015 15:08:25 -0000       1.46
+++ lib/libc/gen/getgrent.c     10 Sep 2018 02:02:31 -0000
@@ -144,6 +144,7 @@ getgrnam_r(const char *name, struct grou
        errno = errnosave;
        return ret;
 }
+DEF_WEAK(getgrnam_r);
 
 static struct group *
 getgrgid_gs(gid_t gid, struct group *p_gr, struct group_storage *gs)
Index: lib/libc/shlib_version
===================================================================
RCS file: /cvs/src/lib/libc/shlib_version,v
retrieving revision 1.199
diff -u -p -u -r1.199 shlib_version
--- lib/libc/shlib_version      13 Jul 2018 09:27:07 -0000      1.199
+++ lib/libc/shlib_version      10 Sep 2018 00:46:44 -0000
@@ -1,4 +1,4 @@
 major=92
-minor=4
+minor=5
 # note: If changes were made to include/thread_private.h or if system
 # calls were added/changed then librthread/shlib_version also be updated.
Index: lib/libc/Symbols.list
===================================================================
RCS file: /cvs/src/lib/libc/Symbols.list,v
retrieving revision 1.63
diff -u -p -u -r1.63 Symbols.list
--- lib/libc/Symbols.list       13 Jul 2018 09:25:22 -0000      1.63
+++ lib/libc/Symbols.list       10 Sep 2018 00:46:44 -0000
@@ -663,6 +663,7 @@ getpwuid_shadow
 getttyent
 getttynam
 getusershell
+gid_from_group
 glob
 globfree
 group_from_gid
@@ -798,6 +799,7 @@ ttyname
 ttyname_r
 ttyslot
 ualarm
+uid_from_user
 uname
 unvis
 user_from_uid
Index: lib/libc/hidden/grp.h
===================================================================
RCS file: /cvs/src/lib/libc/hidden/grp.h,v
retrieving revision 1.2
diff -u -p -u -r1.2 grp.h
--- lib/libc/hidden/grp.h       24 Nov 2015 22:03:33 -0000      1.2
+++ lib/libc/hidden/grp.h       10 Sep 2018 00:46:44 -0000
@@ -29,7 +29,8 @@ PROTO_DEPRECATED(getgrent);
 PROTO_DEPRECATED(getgrgid);
 PROTO_NORMAL(getgrgid_r);
 PROTO_DEPRECATED(getgrnam);
-PROTO_DEPRECATED(getgrnam_r);
+PROTO_NORMAL(getgrnam_r);
+PROTO_DEPRECATED(gid_from_group);
 PROTO_DEPRECATED(group_from_gid);
 PROTO_NORMAL(setgrent);
 PROTO_NORMAL(setgroupent);
Index: lib/libc/hidden/pwd.h
===================================================================
RCS file: /cvs/src/lib/libc/hidden/pwd.h,v
retrieving revision 1.3
diff -u -p -u -r1.3 pwd.h
--- lib/libc/hidden/pwd.h       24 Nov 2015 22:03:33 -0000      1.3
+++ lib/libc/hidden/pwd.h       10 Sep 2018 00:46:44 -0000
@@ -40,6 +40,7 @@ PROTO_NORMAL(getpwuid_shadow);
 PROTO_NORMAL(pw_dup);
 PROTO_NORMAL(setpassent);
 PROTO_DEPRECATED(setpwent);
+PROTO_DEPRECATED(uid_from_user);
 PROTO_DEPRECATED(user_from_uid);
 
 #endif /* !_LIBC_PWD_H_ */

Reply via email to