549 lines
12 KiB
C
549 lines
12 KiB
C
/* $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);
|
|
}
|
|
}
|
|
|
|
|