NetBSD/regress/sys/kern/getcwd/getcwd.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);
}
}