getpwent_r(3) needs gr->gr_mem to be freed, but that's not documented

Alejandro Colomar alx@kernel.org
Sun Sep 28 23:01:19 GMT 2025


On Mon, Sep 29, 2025 at 01:00:09AM +0200, Alejandro Colomar wrote:
> Hi Collin,
> 
> On Sun, Sep 28, 2025 at 03:43:54PM -0700, Collin Funk wrote:
> > Alejandro Colomar <alx@kernel.org> writes:
> > 
> > > Hmmm, that's good.  Thanks!  At least, the user doesn't need to free(3)
> > > anything weird.
> > >
> > > So, a good estimate of the size to be allocated prior to the
> > > sgetgrent_r() call should be:
> > >
> > > 	size = strlen(s) + 1 + strchrcnt(s, ',') + 2;
> 
> Oops, actually:
> 
> 	size = strlen(s) + 1;
> 	size += (strchrcnt(s, ',') + 2) * sizeof(char *);
> 
> > > That would be wasting a little bit if there are any commas outside of
> > > the fourth ':'-delimited field, but it should work.
> > 
> > You can guess a value and then grow the buffer as long as errno == ERANGE.
> 
> TBH, I dislike that approach.  I prefer a good guess, and if that
> doesn't work, I'll report an error.  It may be imperfect, but it's
> simple, which means less bugs.  If I can have an upper bound, and the
> wasted memory is reasonable (in this case, I'd say it's even
> negligible), then we're fine.
> 
> So far, I've written this:
> 
> 	// from-string get group entry
> 	struct group *
> 	sgetgrent(const char *s)
> 	{
> 		int                 e;
> 		size_t              size;
> 		static char         *buf = NULL;
> 		static struct group grent = {};
> 
> 		free(buf);
> 
> 		size = strlen(s) + 1;
> 		size += sizeof(char *) * (strchrcnt(s, ',') + 2);  // For 'grent.gr_mem'
> 
> 		buf = MALLOC(size, char);
> 		if (buf == NULL)
> 			return NULL;
> 
> 		e = sgetgrent_r(s, &grent, buf, size);
> 		if (e != 0) {
> 			errno = e;
> 			return NULL;
> 		}
> 
> 		return &grent;
> 	}
> 
> 
> 	// from-string get group entry re-entrant
> 	int
> 	sgetgrent_r(size_t size;
> 	    const char *restrict s, struct group *restrict grent,
> 	    char buf[restrict size], size_t size)
> 	{
> 		char  *p, *end;
> 		char  *fields[4];
> 
> 		end = buf + size;
> 		p = stpecpy(buf, end, s);
> 		if (p == NULL)
> 			return errno;

I forgot a p++ here.

> 
> 		stpsep(buf, "\n");
> 
> 		if (STRSEP2ARR(buf, ":", fields) == -1)
> 			return EINVAL;
> 
> 		grent->gr_name = fields[0];
> 		grent->gr_passwd = fields[1];
> 		if (get_gid(fields[2], &grent->gr_gid) == -1)
> 			return errno;
> 
> 		grent->gr_mem = csv2ls(fields[3], end - p, p);
> 		if (NULL == grent->gr_mem)
> 			return errno;
> 
> 		return 0;
> 	}
> 
> Which I'm quite happy with.  It's simple, it's loop-less (well, it has
> some necessary loops, but it doesn't retry anything), and it should work
> just fine.
> 
> > > BTW, where's this exactly in the glibc source code?  It's a bit hard to
> > > follow.
> > 
> > In nss you can find <FUNCTION-NAME>.c and then follow the includes and
> > macros. I agree it is a bit hard to follow. :)
> 
> Especially, if we start here (omitting the copyright notice, for
> brevity):
> 
> 	$ cat ./getgrent_r.c
> 	/* Copyright [...]  */
> 
> 	#include <grp.h>
> 
> 
> 	#define LOOKUP_TYPE	struct group
> 	#define SETFUNC_NAME	setgrent
> 	#define	GETFUNC_NAME	getgrent
> 	#define	ENDFUNC_NAME	endgrent
> 	#define DATABASE_NAME	group
> 	#define BUFLEN		NSS_BUFLEN_GROUP
> 
> 	#include "../nss/getXXent_r.c"
> 
> And it doesn't get much better once I start following that.  I think
> I'll just trust Mark.  :-)
> 
> 
> > Collin
> 
> Cheers,
> Alex
> 
> -- 
> <https://www.alejandro-colomar.es>
> Use port 80 (that is, <...:80/>).



-- 
<https://www.alejandro-colomar.es>
Use port 80 (that is, <...:80/>).
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: not available
URL: <https://sourceware.org/pipermail/libc-alpha/attachments/20250929/22f7994e/attachment.sig>


More information about the Libc-alpha mailing list