/* $NetBSD: getcwd.c,v 1.9 2008/04/28 20:23:06 martin Exp $ */ /*- * Copyright (c) 1999 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Bill Sommerfeld. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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. */ /* * test SYS___getcwd. */ #include <assert.h> #include <err.h> #include <errno.h> #include <pwd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h> #include <sys/param.h> /* for MAXPATHLEN */ #include <sys/types.h> #include <sys/stat.h> #include <sys/wait.h> #include "getcwd.h" int main(int, char *[]); static void check1(char *dir, char *buf, char *calltext, int actual, int expected, int experr); static void time_old_getcwd(void); static void time_kern_getcwd(void); static void time_func(char *name, void (*func)(void)); static void test_speed(void); static void test___getcwd (void); static void test___getcwd_perms (void); static void test___getcwd_chroot(void); static void stress_test_getcwd(void); static void usage(char *progname); /* libc-private interface */ int __getcwd(char *, size_t); /* * test cases: * NULL pointer * broken pointer * zero-length buffer * negative length * one-character buffer * two-character buffer * full-length buffer * large (uncacheable) name in path. * deleted directory * after rename of parent. * permission failure. * good pointer near end of address space * really huge length * really large (multi-block) directories * chroot interactions: * chroot, at / inside the directory. * chroot, at some other inside directory. */ /* * test cases not yet done: * -o union mount * chroot interactions: * chroot to mounted directory. * (i.e., proc a: chroot /foo; sleep; * proc b: mount blort /foo) * concurrent with force-unmounting of filesystem. */ #define bigname "Funkelhausersteinweitz.SIPBADMIN.a" /* don't ask */ #define littlename "getcwdtest" #define othername "testgetcwd" static int verbose = 0; static int test = 1; static int fail = 0; static int pass = 0; static int sleepflag = 0; static uid_t altid = -1; static void check1 (dir, buf, calltext, actual, expected, experr) char *dir; char *buf; char *calltext; int actual, expected, experr; { int ntest = test++; if (actual != expected) { fprintf(stderr, "test %d: in %s, %s failed; expected %d, got %d\n", ntest, dir, calltext, expected, actual); if (actual < 0) perror("getcwd"); fail++; } else if ((expected == -1) && (errno != (experr))) { fprintf(stderr, "test %d: in %s, %s failed; expected error %d, got %d\n", ntest, dir, calltext, experr, errno); if (actual < 0) perror("getcwd"); fail++; } else if ((expected > 0) && (buf != NULL) && (strcmp (dir, buf) != 0)) { fprintf(stderr, "test %d: in %s, %s got wrong dir %s\n", ntest, dir, calltext, buf); fail++; } else { if (expected > 0) { char newbuf[1024]; char *cp = old_getcwd(newbuf, sizeof(newbuf)); if (cp == NULL) { fail++; fprintf(stderr, "test %d: in %s, old getcwd failed!\n", ntest, dir); } else if (strcmp(cp, buf)) { fail++; fprintf(stderr, "test %d: in %s, old_getcwd returned different dir %s\n", ntest, dir, cp); } } pass++; if (verbose) printf("test %d: in %s, %s passed\n", ntest, dir, calltext); } if (sleepflag) sleep(1); } int nloops = 100; void time_old_getcwd() { char result_buf[1024]; if (old_getcwd(result_buf, 1024) == NULL) { fprintf(stderr, "old_getcwd failed during timing test!\n"); perror("old_getcwd"); exit(1); } } void time_kern_getcwd() { char result_buf[1024]; if (__getcwd(result_buf, sizeof(result_buf)) < 0) { fprintf(stderr, "getcwd failed during timing test!"); perror("getcwd"); exit(1); } } static void time_func(name, func) char *name; void (*func)(void); { struct timeval before, after; double delta_t; int i; chdir ("/usr/share/examples/emul/ultrix/etc"); gettimeofday(&before, 0); for (i=0; i<nloops; i++) { (*func)(); } gettimeofday(&after, 0); delta_t = after.tv_sec - before.tv_sec; delta_t += ((double)(after.tv_usec - before.tv_usec))/1000000.0; printf("%s: %d calls in %10.3f seconds; ", name, nloops, delta_t); printf("%10.6f ms/call\n", (delta_t*1000.0)/nloops); } void test_speed() { int i; for (i=0; i<5; i++) time_func("kernel getcwd", time_kern_getcwd); for (i=0; i<5; i++) time_func("old user-space getcwd", time_old_getcwd); } #define CHECK(dir, call, ret, err) \ check1((dir), kbuf, #call, (call), (ret), (err)) void test___getcwd_perms() { char kbuf[1024]; if (geteuid() != 0) { fprintf(stderr, "Not root; skipping permission tests\n"); return; } mkdir ("/tmp/permdir", 0700); mkdir ("/tmp/permdir/subdir", 0755); chdir ("/tmp/permdir/subdir"); seteuid(altid); CHECK("/tmp/permdir/subdir", __getcwd(kbuf, sizeof(kbuf)), -1, EACCES); seteuid(0); chdir ("/"); rmdir ("/tmp/permdir/subdir"); rmdir ("/tmp/permdir"); mkdir ("/tmp/permdir", 0755); mkdir ("/tmp/permdir/subdir", 0711); chdir ("/tmp/permdir/subdir"); seteuid(altid); CHECK("/tmp/permdir/subdir", __getcwd(kbuf, sizeof(kbuf)), 20, 0); seteuid(0); chdir ("/"); rmdir ("/tmp/permdir/subdir"); rmdir ("/tmp/permdir"); } void test___getcwd_chroot() { int pid, status; char kbuf[1024]; if (geteuid() != 0) { fprintf(stderr, "Not root; skipping chroot tests\n"); return; } /* XXX we need fchroot to do this properly.. */ mkdir ("/tmp/chrootdir", 0755); mkdir ("/tmp/chrootdir/subdir", 0755); chdir ("/tmp/chrootdir"); CHECK ("/tmp/chrootdir", __getcwd(kbuf, sizeof(kbuf)), 15, 0); fflush(NULL); pid = fork(); if (pid < 0) { perror("fork"); fail++; } else if (pid == 0) { fail = 0; pass = 0; /* chroot to root of filesystem (assuming MFS /tmp) */ chroot ("/tmp"); CHECK ("/chrootdir", __getcwd(kbuf, sizeof(kbuf)), 11, 0); /* chroot to further down */ chroot ("/chrootdir"); CHECK ("/", __getcwd(kbuf, sizeof(kbuf)), 2, 0); chdir("subdir"); CHECK ("/subdir", __getcwd(kbuf, sizeof(kbuf)), 8, 0); if (fail) exit(1); else exit(0); } else { waitpid(pid, &status, 0); if (WIFEXITED(status) && (WEXITSTATUS(status) == 0)) pass++; else fail++; } chdir ("/"); rmdir ("/tmp/chrootdir/subdir"); rmdir ("/tmp/chrootdir"); } void test___getcwd() { int i; static char kbuf[1024]; chdir("/"); CHECK("/", __getcwd(0, 0), -1, ERANGE); CHECK("/", __getcwd(0, -1), -1, ERANGE); CHECK("/", __getcwd(kbuf, 0xdeadbeef), -1, ERANGE); /* large negative */ CHECK("/", __getcwd(kbuf, 0x7000beef), 2, 0); /* large positive, rounds down */ CHECK("/", __getcwd(kbuf, 0x10000), 2, 0); /* slightly less large positive, rounds down */ CHECK("/", __getcwd(kbuf+0x100000, sizeof(kbuf)), -1, EFAULT); /* outside address space */ CHECK("/", __getcwd(0, 30), -1, EFAULT); CHECK("/", __getcwd((void*)0xdeadbeef, 30), -1, EFAULT); CHECK("/", __getcwd(kbuf, 2), 2, 0); assert (strcmp(kbuf, "/") == 0); CHECK("/", __getcwd(kbuf, sizeof(kbuf)), 2, 0); CHECK("/", __getcwd(kbuf, 0), -1, ERANGE); CHECK("/", __getcwd(kbuf, 1), -1, ERANGE); chdir("/sbin"); CHECK("/sbin", __getcwd(kbuf, sizeof(kbuf)), 6, 0); /* verify that cacheable path gets range check right.. */ CHECK("/sbin", __getcwd(kbuf, 3), -1, ERANGE); chdir("/etc/mtree"); CHECK("/etc/mtree", __getcwd(kbuf, sizeof(kbuf)), 11, 0); CHECK("/etc/mtree", __getcwd(kbuf, sizeof(kbuf)), 11, 0); /* mount point */ chdir("/usr/bin"); CHECK("/usr/bin", __getcwd(kbuf, sizeof(kbuf)), 9, 0); /* really large (non-cacheable) entry name */ chdir("/tmp"); (void) rmdir(bigname); mkdir(bigname, 0755); chdir(bigname); /* verify that non-cachable path gets range check right.. */ CHECK("/tmp/" bigname, __getcwd(kbuf, 10), -1, ERANGE); CHECK("/tmp/" bigname, __getcwd(kbuf, sizeof(kbuf)), 40, 0); if (rmdir("/tmp/" bigname) < 0) { perror("rmdir"); } CHECK("deleted directory", __getcwd(kbuf, sizeof(kbuf)), -1, ENOENT); chdir("/tmp"); (void) rmdir(littlename); mkdir(littlename, 0755); chdir(littlename); CHECK("/tmp/" littlename, __getcwd(kbuf, sizeof(kbuf)), 16, 0); if (rename("/tmp/" littlename, "/tmp/" othername) < 0) { perror("rename"); fail++; } CHECK("/tmp/" othername, __getcwd(kbuf, sizeof(kbuf)), 16, 0); if (rmdir("/tmp/" othername) < 0) { perror("rmdir"); fail++; } CHECK("deleted directory", __getcwd(kbuf, sizeof(kbuf)), -1, ENOENT); mkdir("/tmp/bigdir", 0755); for (i=0; i<nloops; i++) { char buf[MAXPATHLEN]; snprintf(buf, MAXPATHLEN, "/tmp/bigdir/bigsubdirwithanamewhichistoolongtocache%04d", i); (void)rmdir(buf); if (mkdir (buf, 0755) < 0) { perror("mkdir"); fail++; break; } } for (i=0; i<nloops; i++) { char buf[MAXPATHLEN]; snprintf(buf, MAXPATHLEN, "/tmp/bigdir/bigsubdirwithanamewhichistoolongtocache%04d", i); if (chdir(buf) < 0) { perror("chdir"); fail++; break; } CHECK(buf, __getcwd(kbuf, sizeof(kbuf)), strlen(buf)+1, 0); } for (i=0; i<nloops; i++) { char buf[MAXPATHLEN]; snprintf(buf, MAXPATHLEN, "/tmp/bigdir/bigsubdirwithanamewhichistoolongtocache%04d", i); (void)rmdir(buf); } (void)rmdir("/tmp/bigdir"); test___getcwd_perms(); test___getcwd_chroot(); } void stress_test_getcwd() { char buf[MAXPATHLEN]; char ubuf[MAXPATHLEN]; char kbuf[MAXPATHLEN]; printf("reading directories from stdin..\n"); while (fgets(buf, MAXPATHLEN, stdin)) { char *cp = strrchr(buf, '\n'); if (cp) *cp = '\0'; if (chdir (buf) < 0) { warn("Can't change directory to %s", buf); continue; } cp = old_getcwd (ubuf, MAXPATHLEN); if (strcmp(buf, ubuf) != 0) { warnx("In %s, old_getcwd says %s", buf, ubuf); } CHECK(buf, __getcwd (kbuf, MAXPATHLEN), strlen(ubuf)+1, 0); } } /* * - large directories. * * - every single filesystem type * * - walk filesystem, compare sys_getcwd with getcwd for each * directory */ void usage(progname) char *progname; { fprintf(stderr, "usage: %s [-srpvw] [-l nloops]\n", progname); exit(1); } int run_stress = 0; int run_regression = 0; int run_performance = 0; int main(argc, argv) int argc; char **argv; { int ch; char *progname = argv[0]; uid_from_user("nobody", &altid); while ((ch = getopt(argc, argv, "srpvwl:u:")) != -1) switch (ch) { case 's': run_stress++; break; case 'r': run_regression++; break; case 'p': run_performance++; break; case 'v': verbose++; break; case 'w': sleepflag++; break; case 'l': nloops = atoi(optarg); if (nloops == 0) nloops = 100; break; case 'u': if (uid_from_user(optarg, &altid) != 0) { fprintf(stderr, "unknown user %s\n", optarg); usage(progname); exit(1); } break; case '?': default: usage(progname); } if (argc != optind) usage(progname); if (run_regression) test___getcwd(); if (!fail && run_performance) test_speed(); if (!fail && run_stress) stress_test_getcwd(); if (verbose) printf ("%d passes\n", pass); if (!fail) exit (0); else { printf("%d failures\n", fail); exit(1); } }