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>