Training courses

Kernel and Embedded Linux

Bootlin training courses

Embedded Linux, kernel,
Yocto Project, Buildroot, real-time,
graphics, boot time, debugging...

Bootlin logo

Elixir Cross Referencer

/*
 * Copyright 2016 Chris Torek <chris.torek@gmail.com>
 * All rights reserved
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted providing that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#if defined(WITH_CASPER)
#include <libcasper.h>
#include <casper/cap_pwd.h>
#include <casper/cap_grp.h>
#endif

#include "rfuncs.h"

/*
 * This is essentially a clone of the BSD basename_r function,
 * which is like POSIX basename() but puts the result in a user
 * supplied buffer.
 *
 * In BSD basename_r, the buffer must be least MAXPATHLEN bytes
 * long.  In our case we take the size of the buffer as an argument.
 *
 * Note that it's impossible in general to do this without
 * a temporary buffer since basename("foo/bar") is "bar",
 * but basename("foo/bar/") is still "bar" -- no trailing
 * slash is allowed.
 *
 * The return value is your supplied buffer <buf>, or NULL if
 * the length of the basename of the supplied <path> equals or
 * exceeds your indicated <bufsize>.
 *
 * As a special but useful case, if you supply NULL for the <buf>
 * argument, we allocate the buffer dynamically to match the
 * basename, i.e., the result is basically strdup()ed for you.
 * In this case <bufsize> is ignored (recommended: pass 0 here).
 */
char *
r_basename(const char *path, char *buf, size_t bufsize)
{
	const char *endp, *comp;
	size_t len;

	/*
	 * NULL or empty path means ".".  This is perhaps overly
	 * forgiving but matches libc basename_r(), and avoids
	 * breaking the code below.
	 */
	if (path == NULL || *path == '\0') {
		comp = ".";
		len = 1;
	} else {
		/*
		 * Back up over any trailing slashes.  If we reach
		 * the top of the path and it's still a trailing
		 * slash, it's also a leading slash and the entire
		 * path is just "/" (or "//", or "///", etc).
		 */
		endp = path + strlen(path) - 1;
		while (*endp == '/' && endp > path)
			endp--;
		/* Invariant: *endp != '/' || endp == path */
		if (*endp == '/') {
			/* then endp==path and hence entire path is "/" */
			comp = "/";
			len = 1;
		} else {
			/*
			 * We handled empty strings earlier, and
			 * we just proved *endp != '/'.  Hence
			 * we have a non-empty basename, ending
			 * at endp.
			 *
			 * Back up one path name component.  The
			 * part between these two is the basename.
			 *
			 * Note that we only stop backing up when
			 * either comp==path, or comp[-1] is '/'.
			 *
			 * Suppose path[0] is '/'.  Then, since *endp
			 * is *not* '/', we had comp>path initially, and
			 * stopped backing up because we found a '/'
			 * (perhaps path[0], perhaps a later '/').
			 *
			 * Or, suppose path[0] is NOT '/'.  Then,
			 * either there are no '/'s at all and
			 * comp==path, or comp[-1] is '/'.
			 *
			 * In all cases, we want all bytes from *comp
			 * to *endp, inclusive.
			 */
			comp = endp;
			while (comp > path && comp[-1] != '/')
				comp--;
			len = (size_t)(endp - comp + 1);
		}
	}
	if (buf == NULL) {
		buf = malloc(len + 1);
		if (buf == NULL)
			return (NULL);
	} else {
		if (len >= bufsize) {
			errno = ENAMETOOLONG;
			return (NULL);
		}
	}
	memcpy(buf, comp, len);
	buf[len] = '\0';
	return (buf);
}

/*
 * This is much like POSIX dirname(), but is reentrant.
 *
 * We examine a path, find the directory portion, and copy that
 * to a user supplied buffer <buf> of the given size <bufsize>.
 *
 * Note that dirname("/foo/bar/") is "/foo", dirname("/foo") is "/",
 * and dirname("////") is "/". However, dirname("////foo/bar") is
 * "////foo" (we do not resolve these leading slashes away -- this
 * matches the BSD libc behavior).
 *
 * The return value is your supplied buffer <buf>, or NULL if
 * the length of the dirname of the supplied <path> equals or
 * exceeds your indicated <bufsize>.
 *
 * As a special but useful case, if you supply NULL for the <buf>
 * argument, we allocate the buffer dynamically to match the
 * dirname, i.e., the result is basically strdup()ed for you.
 * In this case <bufsize> is ignored (recommended: pass 0 here).
 */
char *
r_dirname(const char *path, char *buf, size_t bufsize)
{
	const char *endp, *dirpart;
	size_t len;

	/*
	 * NULL or empty path means ".".  This is perhaps overly
	 * forgiving but matches libc dirname(), and avoids breaking
	 * the code below.
	 */
	if (path == NULL || *path == '\0') {
		dirpart = ".";
		len = 1;
	} else {
		/*
		 * Back up over any trailing slashes, then back up
		 * one path name, then back up over more slashes.
		 * In all cases, stop as soon as endp==path so
		 * that we do not back out of the buffer entirely.
		 *
		 * The first loop takes care of trailing slashes
		 * in names like "/foo/bar//" (where the dirname
		 * part is to be "/foo"), the second strips out
		 * the non-dir-name part, and the third leaves us
		 * pointing to the end of the directory component.
		 *
		 * If the entire name is of the form "/foo" or
		 * "//foo" (or "/foo/", etc, but we already
		 * handled trailing slashes), we end up pointing
		 * to the leading "/", which is what we want; but
		 * if it is of the form "foo" (or "foo/", etc) we
		 * point to a non-slash.  So, if (and only if)
		 * endp==path AND *endp is not '/', the dirname is
		 * ".", but in all cases, the LENGTH of the
		 * dirname is (endp-path+1).
		 */
		endp = path + strlen(path) - 1;
		while (endp > path && *endp == '/')
			endp--;
		while (endp > path && *endp != '/')
			endp--;
		while (endp > path && *endp == '/')
			endp--;

		len = (size_t)(endp - path + 1);
		if (endp == path && *endp != '/')
			dirpart = ".";
		else
			dirpart = path;
	}
	if (buf == NULL) {
		buf = malloc(len + 1);
		if (buf == NULL)
			return (NULL);
	} else {
		if (len >= bufsize) {
			errno = ENAMETOOLONG;
			return (NULL);
		}
	}
	memcpy(buf, dirpart, len);
	buf[len] = '\0';
	return (buf);
}

static void
r_pginit(struct r_pgdata *pg)
{

	/* Note: init to half size since the first thing we do is double it */
	pg->r_pgbufsize = 1 << 9;
	pg->r_pgbuf = NULL;	/* note that realloc(NULL) == malloc */
}

static int
r_pgexpand(struct r_pgdata *pg)
{
	size_t nsize;

	nsize = pg->r_pgbufsize << 1;
	if (nsize >= (1 << 20) ||
	    (pg->r_pgbuf = realloc(pg->r_pgbuf, nsize)) == NULL)
		return (ENOMEM);
	return (0);
}

void
r_pgfree(struct r_pgdata *pg)
{

	free(pg->r_pgbuf);
}

struct passwd *
r_getpwuid(uid_t uid, struct r_pgdata *pg)
{
	struct passwd *result = NULL;
	int error;

	r_pginit(pg);
	do {
		error = r_pgexpand(pg);
		if (error == 0)
			error = getpwuid_r(uid, &pg->r_pgun.un_pw,
			    pg->r_pgbuf, pg->r_pgbufsize, &result);
	} while (error == ERANGE);

	return (error ? NULL : result);
}

struct group *
r_getgrgid(gid_t gid, struct r_pgdata *pg)
{
	struct group *result = NULL;
	int error;

	r_pginit(pg);
	do {
		error = r_pgexpand(pg);
		if (error == 0)
			error = getgrgid_r(gid, &pg->r_pgun.un_gr,
			    pg->r_pgbuf, pg->r_pgbufsize, &result);
	} while (error == ERANGE);

	return (error ? NULL : result);
}

#if defined(WITH_CASPER)
struct passwd *
r_cap_getpwuid(cap_channel_t *cap, uid_t uid, struct r_pgdata *pg)
{
	struct passwd *result = NULL;
	int error;

	r_pginit(pg);
	do {
		error = r_pgexpand(pg);
		if (error == 0)
			error = cap_getpwuid_r(cap, uid, &pg->r_pgun.un_pw,
			    pg->r_pgbuf, pg->r_pgbufsize, &result);
	} while (error == ERANGE);

	return (error ? NULL : result);
}

struct group *
r_cap_getgrgid(cap_channel_t *cap, gid_t gid, struct r_pgdata *pg)
{
	struct group *result = NULL;
	int error;

	r_pginit(pg);
	do {
		error = r_pgexpand(pg);
		if (error == 0)
			error = cap_getgrgid_r(cap, gid, &pg->r_pgun.un_gr,
			    pg->r_pgbuf, pg->r_pgbufsize, &result);
	} while (error == ERANGE);

	return (error ? NULL : result);
}
#endif