root/regress/sys/kern/getcwd/getcwd.c
/*      $NetBSD: getcwd.c,v 1.10 2011/08/18 02:32:32 christos 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((void *)0x10000, 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);
        }
}