Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 21 Mar 2003 03:38:21 +0600
From:      Max Khon <fjoe@iclub.nsu.ru>
To:        standards@freebsd.org
Subject:   thread-safe realpath
Message-ID:  <20030321033821.A91713@iclub.nsu.ru>

next in thread | raw e-mail | index | archive | help

--G4iJoqBmSsgzjUCe
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

hi, there!

Constantin Svintsoff has submitted thread-safe realpath() implementation
(implementation that does not use chdir(2)).
The implementation is feature-compatible with FreeBSD implementation, i.e.
if the last component of specified path can't be stat'ed and there is no
trailing slash, realpath succeeds.

I fixed a couple of bugs in it and would like to commit it to HEAD
if there will be no objections.

Test program is attached. The test simply creates two threads and calls
realpath() in each. If the test is compiled with truepath() #if-0'ed
one of the assertions fail after some time (you may need to increase
number of iterations if you have very fast machine, mine is Athlon 850).

Any comments are highly appreciated.
Please reply directly (I am not subscribed).

/fjoe

--G4iJoqBmSsgzjUCe
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename=Makefile

PROG=	test-realpath
CFLAGS=	-g -Wall
LDADD=	-pthread

NOMAN=	yes
NOOBJ=	yes

.include <bsd.prog.mk>

--G4iJoqBmSsgzjUCe
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="test-realpath.c"

/*
 * Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided 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.
 * 3. The names of the authors may not be used to endorse or promote
 *    products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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.
 *
 * $FreeBSD$
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <pthread.h>

#if 1
char *
truepath(const char *path, char resolved_path[MAXPATHLEN])
{
	unsigned num_symlinks = 0;
	int saved_errno = errno;

	char left[MAXPATHLEN];
	size_t left_len, resolved_len;

	if (path[0] == '/') {
		resolved_path[0] = '/';
		resolved_path[1] = '\0';
		if (path[1] == '\0')
			return resolved_path;
		resolved_len = 1;
		left_len = strlcpy(left, path + 1, MAXPATHLEN);
	} else {
		if (getcwd(resolved_path, MAXPATHLEN) == NULL) {
			strlcpy(resolved_path, ".", MAXPATHLEN);
			return NULL;
		}
		resolved_len = strlen(resolved_path);
		left_len = strlcpy(left, path, MAXPATHLEN);
	}
	if (left_len >= MAXPATHLEN || resolved_len >= MAXPATHLEN) {
		errno = ENAMETOOLONG;
		return NULL;
	}

	while (left_len > 0) {
		struct stat st;
		char next_token[MAXPATHLEN];
		char *p;
		char *s = (p = strchr(left, '/')) ? p : left + left_len;

		memmove(next_token, left, s - left);
		left_len -= s - left;
		if (p != NULL)
			memmove(left, s + 1, left_len + 1);

		next_token[s - left] = '\0';
		if (resolved_path[resolved_len - 1] != '/') {
			if (resolved_len + 1 >= MAXPATHLEN) {
				errno = ENAMETOOLONG;
				return NULL;
			}

			resolved_path[resolved_len++] = '/';
			resolved_path[resolved_len] = '\0';
		}

		if (next_token[0] == '\0')
			continue;
		else if (!strcmp(next_token, "."))
			continue;
		else if (!strcmp(next_token, "..")) {
			if (resolved_len > 1) {
				char *q;

				/* trailing slash */
				resolved_path[resolved_len - 1] = '\0';

				q = strrchr(resolved_path, '/');
				*q = '\0';
				resolved_len = q - resolved_path;
			}
			continue;
		}

		/* filename */
		resolved_len = strlcat(resolved_path, next_token, MAXPATHLEN);
		if (resolved_len >= MAXPATHLEN) {
			errno = ENAMETOOLONG;
			return NULL;
		}

		if (lstat(resolved_path, &st) < 0) {
			if (errno == ENOENT && p == NULL) {
				errno = saved_errno;
				return resolved_path;
			}

			return NULL;
		}

		if ((st.st_mode & S_IFLNK) == S_IFLNK) {
			char symlink[MAXPATHLEN];
			int slen;

			if (num_symlinks++ > MAXSYMLINKS) {
				errno = ELOOP;
				return NULL;
			}
			slen = readlink(resolved_path, symlink, MAXPATHLEN);
			if (slen < 0)
				return NULL;
			symlink[slen] = '\0';

			if (symlink[0] == '/') {
				/* absolute link */
				resolved_path[1] = 0;
				resolved_len = 1;
			} else if (resolved_len > 1) {
				char *q;

				/* trailing slash */
				resolved_path[resolved_len - 1] = '\0';

				q = strrchr(resolved_path, '/');
				*q = '\0';
				resolved_len = q - resolved_path;
			}

			if (symlink[slen - 1] != '/' && p != NULL) {
				if (slen >= MAXPATHLEN) {
					errno = ENAMETOOLONG;
					return NULL;
				}

				symlink[slen] = '/';
				symlink[slen + 1] = 0;
			}
			if (p != NULL)
				left_len = strlcat(symlink, left, MAXPATHLEN);
			if (left_len > MAXPATHLEN) {
				errno = ENAMETOOLONG;
				return NULL;
			}
			left_len = strlcpy(left, symlink, MAXPATHLEN);
		}
	}

	if (resolved_len > 1 && resolved_path[resolved_len - 1] == '/')
		resolved_path[resolved_len - 1] = '\0';

	return resolved_path;
}

#define realpath(a, b)	truepath((a), (b))

#endif

pthread_mutex_t m;
pthread_cond_t c;

void *
thread(void *arg)
{
	const char *name = arg;
	char rname[MAXPATHLEN];
	char dname[MAXPATHLEN];
	int i;

	errno = 0;
	if (mkdir(name, 0777) < 0 && errno != EEXIST) {
		perror(name);
		exit(1);
	}
	strlcpy(rname, name, sizeof(rname));
	strlcat(rname, "/foobar", sizeof(rname));
	errno = 0;
	if (mkdir(rname, 0777) < 0 && errno != EEXIST) {
		perror(name);
		exit(1);
	}

	getcwd(dname, sizeof(dname));
	strlcat(dname, "/", sizeof(dname));
        strlcat(dname, rname, sizeof(dname));
	fprintf(stderr, "%s: %s\n", name, dname);

	/*
	 * wait for start
	 */
	fprintf(stderr, "%s: waiting for main thread to wake us up\n", name);
	pthread_mutex_lock(&m);
	pthread_cond_wait(&c, &m);
	pthread_mutex_unlock(&m);
	fprintf(stderr, "%s: started\n", name);

	for (i = 0; i < 100000; i++) {
		char buf[MAXPATHLEN];

		if (realpath(rname, buf) == 0) {
			perror(buf);
			assert(0);
		}

		if (!!strcmp(buf, dname)) {
			fprintf(stderr, "%s: expected '%s' got '%s'\n",
				name, dname, buf);
			assert(0);
		}
	}

        fprintf(stderr, "%s: finished\n", name);
	rmdir(rname);
	rmdir(name);

	return NULL;
}

void
regress()
{
	char resolved[MAXPATHLEN];
	static const char loop[] = "loop";

	errno = 0;
	if (symlink(loop, loop) < 0 && errno != EEXIST) {
		perror(loop);
		return;
	}
	if (realpath(loop, resolved) == NULL) {
		fprintf(stderr, "realpath: %s: %s\n",
			resolved, strerror(errno));
	}
}

int
main()
{
	pthread_t p1, p2;

	regress();

	pthread_mutex_init(&m, NULL);
	pthread_cond_init(&c, NULL);

	pthread_create(&p1, NULL, thread, "p1");
	pthread_create(&p2, NULL, thread, "p2");

	fprintf(stderr, "main: sleeping for 1 sec\n");
	sleep(1);
	fprintf(stderr, "main: waking up the threads\n");
	pthread_mutex_lock(&m);
	pthread_cond_broadcast(&c);
	pthread_mutex_unlock(&m);

	pthread_join(p1, NULL);
	pthread_join(p2, NULL);

	pthread_cond_destroy(&c);
	pthread_mutex_destroy(&m);

	exit(0);
}

--G4iJoqBmSsgzjUCe--


To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-standards" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20030321033821.A91713>