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

Alejandro Colomar alx@kernel.org
Sun Sep 28 23:00:06 GMT 2025


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;

		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/>).
-------------- 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/e0074a0d/attachment-0001.sig>


More information about the Libc-alpha mailing list