/*
 * Copyright, 1991, 1992, by Leendert van Doorn (leendert@cs.vu.nl)
 *
 * This material is copyrighted by Leendert van Doorn, 1991, 1992. The usual
 * standard disclaimer applies, especially the fact that the author nor the
 * Vrije Universiteit, Amsterdam are liable for any damages caused by direct or
 * indirect use of the information or functionality provided by this program.
 */

/*
 * nfs - A shell that provides access to NFS file systems
 */
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <setjmp.h>
#include <netdb.h>
#include <errno.h>
#include <rpc/rpc.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/vnode.h>
#include <sys/vfs.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <ufs/inode.h>
#include "mount.h"
#include "nfs_prot.h"

/*
 * Fundamental constants
 */
#define	NARGVEC		100	/* maximum number of arguments */

/*
 * List of command identifiers
 */
#define	CMD_UNKNOWN	0	/* unknown command */
#define	CMD_HOST	1	/* host <host> */
#define	CMD_UID		2	/* uid [<uid>] */
#define	CMD_GID		3	/* gid [<gid>] */
#define	CMD_CD		4	/* cd [<path>] */
#define	CMD_LCD		5	/* lcd [<path>] */
#define	CMD_CAT		6	/* cat <filespec> */
#define	CMD_LS		7	/* ls [-l] <filespec> */
#define	CMD_GET		8	/* get <filespec> */
#define	CMD_DF		9	/* df */
#define	CMD_MOUNT	10	/* mount [-u] [-p] [<host>:]<path> */
#define	CMD_UMOUNT	11	/* umount */
#define	CMD_UMOUNTALL	12	/* umountall */
#define	CMD_EXPORT	13	/* export */
#define	CMD_DUMP	14	/* dump */
#define	CMD_STATUS	15	/* status */
#define	CMD_HELP	16	/* help */
#define	CMD_QUIT	17	/* quit */
#define	CMD_RM		18	/* rm <file> */
#define	CMD_LN		19	/* ln <file1> <file2> */
#define	CMD_MV		20	/* mv <file1> <file2> */
#define	CMD_MKDIR	21	/* mkdir <dir> */
#define	CMD_RMDIR	22	/* rmdir <dir> */
#define	CMD_CHMOD	23	/* chmod <mode> <file> */
#define	CMD_CHOWN	24	/* chown <uid>[.<gid>] <file> */
#define	CMD_PUT		25	/* put <local-file> [<remote-file>] */
#define CMD_HANDLE	26	/* handle [<file-handle>] */
#define CMD_MKNOD	27	/* mknod <file> <major> <minor> */
#define CMD_MROOT	28	/* mroot */

/*
 * Key word table
 */
struct keyword {
    char *kw_command;
    int kw_value;
    char *kw_help;
} keyword[] = {
    "host",	CMD_HOST,	"<host> - set remote host name",
    "uid",	CMD_UID,	"[<uid>] - set remote user id",
    "gid",	CMD_GID,	"[<gid>] - set remote group id",
    "cd",	CMD_CD,		"[<path>] - change remote working directory",
    "lcd",	CMD_LCD,	"[<path>] - change local working directory",
    "cat",	CMD_CAT,	"<filespec> - display remote file",
    "ls",	CMD_LS,		"[-l] <filespec> - list remote directory",
    "get",	CMD_GET,	"<filespec> - get remote files",
    "df",	CMD_DF,		"- file system information",
    "rm",	CMD_RM,		"<file> - delete remote file",
    "ln",	CMD_LN,		"<file1> <file2> - link file",
    "mv",	CMD_MV,		"<file1> <file2> - move file",
    "mkdir",	CMD_MKDIR,	"<dir> - make remote directory",
    "rmdir",	CMD_RMDIR,	"<dir> - remove remote directory",
    "chmod",	CMD_CHMOD,	"<mode> <file> - change mode",
    "chown",	CMD_CHOWN,	"<uid>[.<gid>] <file> -  change owner",
    "put",	CMD_PUT,	"<local-file> [<remote-file>] - put file",
    "mount",	CMD_MOUNT,	"[-u] [-p] [<host>:]<path> - mount file system",
    "umount",	CMD_UMOUNT,	"- umount remote file system",
    "umountall",CMD_UMOUNTALL,	"- umount all remote file systems",
    "export",	CMD_EXPORT,	"- show all exported file systems",
    "dump",	CMD_DUMP,	"- show all remote mounted file systems",
    "status",	CMD_STATUS,	"- general status report",
    "help",	CMD_HELP,	"- this help message",
    "quit",	CMD_QUIT,	"- name says it all",
    "handle",	CMD_HANDLE,	"[<handle>] - get/set directory file handle",
    "mknod",	CMD_MKNOD,	"mknod <file> <major> <minor> - make devices",
    "mroot", 	CMD_MROOT,	"- attempt to masquerade as root"
};

/* run-time settable flags */
int verbose = 1;		/* verbosity flag */
int interact = 1;		/* interactive mode */

/* user provided credentials */
int uid = -2;			/* remote user id (initialy nobody) */
int gid = -2;			/* remote group id (initialy nobody) */

/* server information (also used as state information) */
 char *mountpath;		/* remote mount path */ char *remotehost;		/* remote host name */ struct sockaddr_in server_addr;	/* remote server address information */ struct sockaddr_in mntserver_addr; /* remote mount server address */
struct sockaddr_in nfsserver_addr; /* remote mount server address */
CLIENT *mntclient;		/* mount RPC client */
CLIENT *nfsclient;		/* nfs RPC client */
fhstatus *mountpoint;		/* remote mount point */
fhandle directory_handle;	/* current directory handle */
struct timeval timeout = { 60, 0 }; /* default time out */
int transfersize;		/* NFS default transfer size */

/* interrupt environments */
jmp_buf intenv;			/* where to go in interrupts */

fhstatus *pmap_mnt();

char *malloc();
char *calloc();
char *realloc();
char *strsave();
char *strchr();
char *nfs_error();
char *getenv();
void interrupt();

main(argc, argv)
    int argc;
    char **argv;
{
    int opt, cmd, argcount;
    char *argvec[NARGVEC];
    char buffer[BUFSIZ];

    /* command line option processing */
    while ((opt = getopt(argc, argv, "vi")) != EOF) {
	switch (opt) {
	case 'v':
	    verbose = 0;
	    break;
	case 'i':
	    interact = 0;
	    break;
	default:
	    fprintf(stderr, "Usage: %s [-v]\n", argv[0]);
	    exit(1);
	}
    }

    signal(SIGINT, interrupt);

    /* interpreter's main command loop */
    if (setjmp(intenv)) putchar('\n');
    while (getline(buffer, BUFSIZ, &argcount, argvec, NARGVEC)) {
	if (argcount == 0) continue;
	if ((cmd = command(argvec[0])) == CMD_QUIT)
	    break;
	else switch (cmd) {
	case CMD_HOST:
	    do_host(argcount, argvec);
	    break;
	case CMD_UID:
	    do_setid(&uid, argcount, argvec);
	    break;
	case CMD_GID:
	    do_setid(&gid, argcount, argvec);
	    break;
	case CMD_CD:
	    do_cd(argcount, argvec);
	    break;
	case CMD_LCD:
	    do_lcd(argcount, argvec);
	    break;
	case CMD_CAT:
	    do_cat(argcount, argvec);
	    break;
	case CMD_LS:
	    do_ls(argcount, argvec);
	    break;
	case CMD_GET:
	    do_get(argcount, argvec);
	    break;
	case CMD_DF:
	    do_df(argcount, argvec);
	    break;
	case CMD_RM:
	    do_rm(argcount, argvec);
	    break;
	case CMD_LN:
	    do_ln(argcount, argvec);
	    break;
	case CMD_MV:
	    do_mv(argcount, argvec);
	    break;
	case CMD_MKDIR:
	    do_mkdir(argcount, argvec);
	    break;
	case CMD_RMDIR:
	    do_rmdir(argcount, argvec);
	    break;
	case CMD_CHMOD:
	    do_chmod(argcount, argvec);
	    break;
	case CMD_CHOWN:
	    do_chown(argcount, argvec);
	    break;
	case CMD_PUT:
	    do_put(argcount, argvec);
	    break;
	case CMD_HANDLE:
	    do_handle(argcount, argvec);
	    break;
	case CMD_MKNOD:
	    do_mknod(argcount, argvec);
	    break;
	case CMD_MROOT:
	    do_mroot(&uid, argcount, argvec);
	    break;
	case CMD_MOUNT:
	    do_mount(argcount, argvec);
	    break;
	case CMD_UMOUNT:
	    do_umount(argcount, argvec);
	    break;
	case CMD_UMOUNTALL:
	    do_umountall(argcount, argvec);
	    break;
	case CMD_EXPORT:
	    do_export(argcount, argvec);
	    break;
	case CMD_DUMP:
	    do_dump(argcount, argvec);
	    break;
	case CMD_STATUS:
	    do_status(argcount, argvec);
	    break;
	case CMD_HELP:
	    do_help(argcount, argvec);
	    break;
	case CMD_UNKNOWN:
	    if (buffer[0] == '!') {
		system(buffer + 1);
		printf("!\n");
	    } else
	        fprintf(stderr, "%s: unrecognized command\n", argvec[0]);
	    break;
	default:
	    fprintf(stderr, "internal error: '%s' not is case\n", argvec[0]);
	    break;
	}
    }
    if (remotehost) close_mount();
    exit(0);
}

void
interrupt()
{
    longjmp(intenv, 1);
}

/*
 * Read a line from standard input and break
 * it up into an argument vector.
 */
int
getline(buf, bufsize, argc, argv, argvsize)
    char *buf, **argv;
    int bufsize, *argc, argvsize;
{
    register char *p;

    if (interact) printf("nfs> ");
    if (fgets(buf, bufsize, stdin) == NULL)
	return 0;
    *argc = 0;
    for (p = buf; *p == ' ' || *p == '\t'; p++)
	/* skip white spaces */;
    while (*p != ' ' && *p != '\t' && *p != '\n' && *p != '\0') {
	if (*argc > argvsize) break;
	argv[(*argc)++] = p;
	for (; *p != ' ' && *p != '\t' && *p != '\n' && *p != '\0'; p++)
	    /* skip word */;
	if (*p != '\0') *p++ ='\0';
	for (; *p == ' ' || *p == '\t'; p++)
	   /* skip white spaces */;
    }
    return 1;
}

/*
 * Search for command in keyword table
 */
int
command(cmd)
    char *cmd;
{
    register int i;

    for (i = 0; i < sizeof(keyword)/sizeof(struct keyword); i++)
	if (strcmp(keyword[i].kw_command, cmd) == 0)
	    return keyword[i].kw_value;
    return CMD_UNKNOWN;
}

/*
 * Set remote host and initialize RPC channel
 * to mount daemon.
 */
do_host(argc, argv)
    int argc;
    char **argv;
{
    if (argc != 2)
	fprintf(stderr, "Usage: host <host>\n");
    else
	open_mount(argv[1]);
}

/*
 * Set user or group id (updating RPC authentication info)
 */
do_setid(var, argc, argv)
    int *var, argc;
    char **argv;
{
    *var = argc == 2 ? atoi(argv[1]) : -2;
    if (nfsclient && nfsclient->cl_auth) {
	auth_destroy(nfsclient->cl_auth);
	nfsclient->cl_auth = authunix_create_default(uid, gid);
    }
}

/*
 * Change remote working directory
 */
do_cd(argc, argv)
    int argc;
    char **argv;
{
    register char *p;
    char *component;
    diropargs args;
    diropres *res;
    fhandle handle;

    if (mountpath == NULL) {
	fprintf(stderr, "cd: no remote file system mounted\n");
	return;
    }

    /* easy case: cd to root */
    if (argc == 1) {
	bcopy(mountpoint->fhstatus_u.fhs_fhandle, directory_handle, NFS_FHSIZE);
	return;
    }

    /* if a directory start with '/', we search from the root */
    if (*(p = argv[1]) == '/') {
	bcopy(mountpoint->fhstatus_u.fhs_fhandle, handle, NFS_FHSIZE);
	p++;
    } else
	bcopy(directory_handle, handle, NFS_FHSIZE);

    /*
     * Break path up into directory components and check every
     * component for its validity.
     */
    for (;;) {
	if (*p == '\0') break;
	for (component = p; *p != '/' && *p != '\0'; p++)
	    /* do nothing */;
	*p++ = '\0';
	args.name = component;
	bcopy(handle, &args.dir, NFS_FHSIZE);
	if ((res = nfsproc_lookup_2(&args, nfsclient)) == NULL) {
	    clnt_perror(nfsclient, "nfsproc_lookup");
	    return;
	}
	if (res->status != NFS_OK) {
	    fprintf(stderr, "%s: %s\n", component, nfs_error(res->status));
	    return;
	}
	if (res->diropres_u.diropres.attributes.type != NFDIR) {
	    fprintf(stderr, "%s: is not a directory\n", component);
	    return;
	}
	bcopy(&res->diropres_u.diropres.file, handle, NFS_FHSIZE);
    }
    bcopy(handle, directory_handle, NFS_FHSIZE);
}

/*
 * Change local working directory
 */
do_lcd(argc, argv)
    int argc;
    char **argv;
{
    if (argc == 1) {
	char *home = getenv("HOME");
	if (home != NULL)
	    if (chdir(home) != 0) perror("lcd");
    } else
	if (chdir(argv[1]) != 0) perror("lcd");
}

/*
 * Display a remote file
 */
do_cat(argc, argv)
    int argc;
    char **argv;
{
    diropargs dargs;
    diropres *dres;
    readargs rargs;
    readres *rres;
    long offset;

    if (mountpath == NULL) {
	fprintf(stderr, "cat: no remote file system mounted\n");
	return;
    }
    if (argc != 2) {
	fprintf(stderr, "Usage: cat <filespec>\n");
	return;
    }

    /* lookup name in current directory */
    dargs.name = argv[1];
    bcopy(directory_handle, &dargs.dir, NFS_FHSIZE);
    if ((dres = nfsproc_lookup_2(&dargs, nfsclient)) == NULL) {
	clnt_perror(nfsclient, "nfsproc_lookup");
	return;
    }
    if (dres->status != NFS_OK) {
	fprintf(stderr, "%s: %s\n", argv[1], nfs_error(dres->status));
	return;
    }
    if (dres->diropres_u.diropres.attributes.type != NFREG) {
	fprintf(stderr, "%s: is not a regular file\n", argv[1]);
	return;
    }
    bcopy(&dres->diropres_u.diropres.file, &rargs.file, NFS_FHSIZE);
    for (offset = 0; offset < dres->diropres_u.diropres.attributes.size; ) {
	rargs.offset = offset;
	rargs.count = rargs.totalcount = transfersize;
	if ((rres = nfsproc_read_2(&rargs, nfsclient)) == NULL) {
	    clnt_perror(nfsclient, "nfsproc_read_2");
	    break;
        }
	if (rres->status != NFS_OK) {
	    fprintf(stderr, "%s: %s\n", argv[1], nfs_error(rres->status));
	    break;
	}
        fwrite(rres->readres_u.reply.data.data_val,
            rres->readres_u.reply.data.data_len, 1, stdout);
	offset += transfersize;
    }
}

/*
 * List remote directory
 */
do_ls(argc, argv)
    int argc;
    char **argv;
{
    char **table, **ptr, **p;
    int lflag = 0;

    argv++; argc--;
    if (mountpath == NULL) {
	fprintf(stderr, "ls: no remote file system mounted\n");
	return;
    }
    if (argc >= 1 && strcmp(argv[0], "-l") == 0) {
	argv++; argc--;
	lflag = 1;
    }

    if (!getdirentries(directory_handle, &table, &ptr, 20))
	return;
    for (p = table; p < ptr; p++) {
	if (!match(*p, argc, argv)) continue;
	if (lflag == 1)
	    printfilestatus(*p);
	else
	    printf("%s\n", *p);
	free(*p);
    }
    free(table);
}

/*
 * Print long listing of a files, much in the way ``ls -l'' does
 */
printfilestatus(name)
    char *name;
{
    diropargs args;
    diropres *res;
    int mode;

    args.name = name;
    bcopy(directory_handle, &args.dir, NFS_FHSIZE);

    if ((res = nfsproc_lookup_2(&args, nfsclient)) == NULL) {
	clnt_perror(nfsclient, "nfsproc_lookup");
	return;
    }
    if (res->status != NFS_OK) {
	fprintf(stderr, "Lookup failed: %s\n", nfs_error(res->status));
	return;
    }

    switch (res->diropres_u.diropres.attributes.type) {
	case NFNON:
	    putchar('s');
	    break;
	case NFREG:
	    putchar('-');
	    break;
	case NFDIR:
	    putchar('d');
	    break;
	case NFBLK:
	    putchar('b');
	    break;
	case NFCHR:
	    putchar('c');
	    break;
	case NFLNK:
	    putchar('l');
	    break;
	default:
	    putchar('?');
	    break;
    }
    mode = res->diropres_u.diropres.attributes.mode;
    if (mode & 0400) putchar('r'); else putchar('-');
    if (mode & 0200) putchar('w'); else putchar('-');
    if (mode & 0100)
	if (mode & 04000) putchar('s'); else putchar('x');
    else
	if (mode & 04000) putchar('S'); else putchar('-');
    if (mode & 040) putchar('r'); else putchar('-');
    if (mode & 020) putchar('w'); else putchar('-');
    if (mode & 010)
	if (mode & 02000) putchar('s'); else putchar('x');
    else
	if (mode & 02000) putchar('S'); else putchar('-');
    if (mode & 04) putchar('r'); else putchar('-');
    if (mode & 02) putchar('w'); else putchar('-');
    if (mode & 01)
	if (mode & 01000) putchar('t'); else putchar('x');
    else
	if (mode & 01000) putchar('T'); else putchar('-');
    printf("%3d%9d%6d%10d ",
	res->diropres_u.diropres.attributes.nlink,
	res->diropres_u.diropres.attributes.uid,
	res->diropres_u.diropres.attributes.gid,
	res->diropres_u.diropres.attributes.size);
    writefiledate(res->diropres_u.diropres.attributes.ctime.seconds);
    printf(" %s", name);
    if (res->diropres_u.diropres.attributes.type == NFLNK) {
	readlinkres *rlres;
	nfs_fh rlargs;

	bcopy(&res->diropres_u.diropres.file, &rlargs, NFS_FHSIZE);
	if ((rlres = nfsproc_readlink_2(&rlargs, nfsclient)) == NULL) {
	    clnt_perror(nfsclient, "nfsproc_readlink");
	    return;
	}
	if (res->status != NFS_OK) {
	    fprintf(stderr, "Lookup failed: %s\n", nfs_error(res->status));
	    return;
	}
	printf(" -> %s\n", rlres->readlinkres_u.data);
    } else
	putchar('\n');
}

int
writefiledate(d)
    time_t d;
{
    time_t now, sixmonthsago, onehourfromnow;
    char *cp;

    (void) time(&now);
    sixmonthsago = now - 6L*30L*24L*60L*60L;
    onehourfromnow = now + 60L*60L;
    cp = ctime(&d);
    if ((d < sixmonthsago) || (d > onehourfromnow))
	return printf(" %-7.7s %-4.4s ", cp+4, cp+20);
    else
	return printf(" %-12.12s ", cp+4);
}

/*
 * Get remote files
 */
do_get(argc, argv)
    int argc;
    char **argv;
{
    char **table, **ptr, **p;
    char answer[512];
    diropargs args;
    diropres *res;
    readargs rargs;
    readres *rres;
    int iflag = 0;
    long offset;
    FILE *fp;

    argv++; argc--;
    if (mountpath == NULL) {
	fprintf(stderr, "get: no remote file system mounted\n");
	return;
    }
    if (argc >= 1 && strcmp(argv[0], "-i") == 0) {
	argv++; argc--;
	iflag = 1;
    }

    if (!getdirentries(directory_handle, &table, &ptr, 20))
	return;
    for (p = table; p < ptr; p++) {
	/* match before going over the wire */
	if (!match(*p, argc, argv)) continue;

	/* only regular files can be transfered */
	args.name = *p;
	bcopy(directory_handle, &args.dir, NFS_FHSIZE);
	if ((res = nfsproc_lookup_2(&args, nfsclient)) == NULL) {
	    clnt_perror(nfsclient, "nfsproc_lookup");
	    return;
	}
	if (res->status != NFS_OK) {
	    fprintf(stderr, "Lookup failed: %s\n", nfs_error(res->status));
	    return;
	}
	if (res->diropres_u.diropres.attributes.type != NFREG)
	    continue;

	/* ask for confirmation */
	printf("%s? ", *p);
	if (!iflag) {
	    gets(answer);
	    if (answer[0] != 'y' && answer[0] != 'Y') continue;
	} else
	    printf("Yes\n");

	/* get actual file */
	if ((fp = fopen(*p, "w")) == NULL) {
	    fprintf(stderr, "get: cannot create %s\n", *p);
	    continue;
	}
	bcopy(&res->diropres_u.diropres.file, &rargs.file, NFS_FHSIZE);
	for (offset = 0; offset < res->diropres_u.diropres.attributes.size; ) {
	    rargs.offset = offset;
	    rargs.count = rargs.totalcount = transfersize;
	    if ((rres = nfsproc_read_2(&rargs, nfsclient)) == NULL) {
		clnt_perror(nfsclient, "nfsproc_read");
		break;
            }
	    if (rres->status != NFS_OK) {
		fprintf(stderr, "%s: %s\n", argv[1], nfs_error(rres->status));
		break;
	    }
	    fwrite(rres->readres_u.reply.data.data_val,
		rres->readres_u.reply.data.data_len, 1, fp);
	    offset += transfersize;
	}
	fclose(fp);
	free(*p);
    }
    free(table);
}

/*
 * Show file system information
 */
/* ARGUSED */
do_df(argc, argv)
    int argc;
    char **argv;
{
    statfsres *res;

    if (mountpath == NULL) {
	fprintf(stderr, "df: no remote file system mounted\n");
	return;
    }
    if (argc != 1) {
	fprintf(stderr, "Usage: df\n");
	return;
    }
    if ((res = nfsproc_statfs_2(directory_handle, nfsclient)) == NULL) {
	clnt_perror(nfsclient, "nfsproc_statfs");
	return;
    }
    if (res->status != NFS_OK) {
	fprintf(stderr, "Df failed: %s\n", nfs_error(res->status));
	return;
    }

#define x res->statfsres_u.reply
    printf("%s:%s    %dK, %dK used, %dK free (%dK useable).\n",
	remotehost, mountpath, x.blocks, (x.blocks-x.bfree), x.bfree, x.bavail);

    if (x.bsize != 1024)
	printf("Warning: This filesystem has a blocksize of %d bytes.\n",
	    x.bsize);
#undef x
}

/*
 * Delete a remote file
 */
do_rm(argc, argv)
    int argc;
    char **argv;
{
    diropargs args;
    nfsstat *res;

    if (mountpath == NULL) {
	fprintf(stderr, "rm: no remote file system mounted\n");
	return;
    }
    if (argc != 2) {
	fprintf(stderr, "Usage: rm <file>\n");
	return;
    }
    args.name = argv[1];
    bcopy(directory_handle, &args.dir, NFS_FHSIZE);
    if ((res = nfsproc_remove_2(&args, nfsclient)) == NULL) {
	clnt_perror(nfsclient, "nfsproc_remove");
	return;
    }
    if (*res != NFS_OK) {
	fprintf(stderr, "Remove failed: %s\n", nfs_error(*res));
	return;
    }
}

/*
 * Link a file
 */
do_ln(argc, argv)
    int argc;
    char **argv;
{
    diropargs dargs;
    linkargs largs;
    diropres *dres;
    nfsstat *lres;

    if (mountpath == NULL) {
	fprintf(stderr, "ln: no remote file system mounted\n");
	return;
    }
    if (argc != 3) {
	fprintf(stderr, "Usage: ln <file1> <file2>\n");
	return;
    }

    dargs.name = argv[1];
    bcopy(directory_handle, &dargs.dir, NFS_FHSIZE);
    if ((dres = nfsproc_lookup_2(&dargs, nfsclient)) == NULL) {
	clnt_perror(nfsclient, "nfsproc_lookup");
	return;
    }
    if (dres->status != NFS_OK) {
	fprintf(stderr, "%s: %s\n", argv[1], nfs_error(dres->status));
	return;
    }

    bcopy(&dres->diropres_u.diropres.file, &largs.from, NFS_FHSIZE);
    largs.to.name = argv[2];
    bcopy(directory_handle, &largs.to.dir, NFS_FHSIZE);

    if ((lres = nfsproc_link_2(&largs, nfsclient)) == NULL) {
	clnt_perror(nfsclient, "nfsproc_link");
	return;
    }
    if (*lres != NFS_OK) {
	fprintf(stderr, "Link failed: %s\n", nfs_error(*lres));
	return;
    }
}

/*
 * Move a file or directory
 */
do_mv(argc, argv)
    int argc;
    char **argv;
{
    renameargs args;
    nfsstat *res;

    if (mountpath == NULL) {
	fprintf(stderr, "mv: no remote file system mounted\n");
	return;
    }
    if (argc != 3) {
	fprintf(stderr, "Usage: mv <file1> <file2>\n");
	return;
    }
    args.from.name = argv[1];
    bcopy(directory_handle, &args.from.dir, NFS_FHSIZE);
    args.to.name = argv[2];
    bcopy(directory_handle, &args.to.dir, NFS_FHSIZE);
    if ((res = nfsproc_rename_2(&args, nfsclient)) == NULL) {
	clnt_perror(nfsclient, "nfsproc_rename");
	return;
    }
    if (*res != NFS_OK) {
	fprintf(stderr, "Rename failed: %s\n", nfs_error(*res));
	return;
    }
}

/*
 * Make remote directory
 */
do_mkdir(argc, argv)
    int argc;
    char **argv;
{
    createargs args;
    diropres *res;

    if (mountpath == NULL) {
	fprintf(stderr, "mkdir: no remote file system mounted\n");
	return;
    }
    if (argc != 2) {
	fprintf(stderr, "Usage: mkdir <directory>\n");
	return;
    }

    args.where.name = argv[1];
    bcopy(directory_handle, &args.where.dir, NFS_FHSIZE);
    args.attributes.mode = 040755;
    args.attributes.uid = uid;
    args.attributes.gid = gid;
    args.attributes.size = -1;
    args.attributes.atime.seconds = -1;
    args.attributes.atime.useconds = -1;
    args.attributes.mtime.seconds = -1;
    args.attributes.mtime.useconds = -1;

    if ((res = nfsproc_mkdir_2(&args, nfsclient)) == NULL) {
	clnt_perror(nfsclient, "nfsproc_mkdir");
	return;
    }
    if (res->status != NFS_OK) {
	fprintf(stderr, "Make directory failed: %s\n", nfs_error(res->status));
	return;
    }
}

/*
 * Remove remote directory
 */
do_rmdir(argc, argv)
    int argc;
    char **argv;
{
    diropargs args;
    nfsstat *res;

    if (mountpath == NULL) {
	fprintf(stderr, "rmdir: no remote file system mounted\n");
	return;
    }
    if (argc != 2) {
	fprintf(stderr, "Usage: rmdir <directory>\n");
	return;
    }

    args.name = argv[1];
    bcopy(directory_handle, &args.dir, NFS_FHSIZE);
    if ((res = nfsproc_rmdir_2(&args, nfsclient)) == NULL) {
	clnt_perror(nfsclient, "nfsproc_rmdir");
	return;
    }
    if (*res != NFS_OK) {
	fprintf(stderr, "Remove directory failed: %s\n", nfs_error(*res));
	return;
    }
}

/*
 * Change mode of remote file or directory
 */
do_chmod(argc, argv)
    int argc;
    char **argv;
{
    sattrargs aargs;
    diropargs dargs;
    attrstat *ares;
    diropres *dres;
    int mode;

    if (mountpath == NULL) {
	fprintf(stderr, "chmod: no remote file system mounted\n");
	return;
    }
    if (argc != 3) {
	fprintf(stderr, "Usage: chmod <mode> <file>\n");
	return;
    }
    if (sscanf(argv[1], "%o", &mode) != 1) {
	fprintf(stderr, "chmod: invalid mode\n");
	return;
    }

    dargs.name = argv[2];
    bcopy(directory_handle, &dargs.dir, NFS_FHSIZE);
    if ((dres = nfsproc_lookup_2(&dargs, nfsclient)) == NULL) {
	clnt_perror(nfsclient, "nfsproc_lookup");
	return;
    }
    if (dres->status != NFS_OK) {
	fprintf(stderr, "%s: %s\n", argv[2], nfs_error(dres->status));
	return;
    }

    bcopy(&dres->diropres_u.diropres.file, &aargs.file, NFS_FHSIZE);
    aargs.attributes.mode = mode;
    aargs.attributes.uid = -1;
    aargs.attributes.gid = -1;
    aargs.attributes.size = -1;
    aargs.attributes.atime.seconds = -1;
    aargs.attributes.atime.useconds = -1;
    aargs.attributes.mtime.seconds = -1;
    aargs.attributes.mtime.useconds = -1;

    if ((ares = nfsproc_setattr_2(&aargs, nfsclient)) == NULL) {
	clnt_perror(nfsclient, "nfsproc_setattr");
	return;
    }
    if (ares->status != NFS_OK) {
	fprintf(stderr, "Set attributes failed: %s\n", nfs_error(ares->status));
	return;
    }
}

/*
 * Change owner (and group) of remote file or directory
 */
do_chown(argc, argv)
    int argc;
    char **argv;
{
    sattrargs aargs;
    diropargs dargs;
    attrstat *ares;
    diropres *dres;
    int own_uid, own_gid;

    if (mountpath == NULL) {
	fprintf(stderr, "chown: no remote file system mounted\n");
	return;
    }
    if (argc != 3) {
	fprintf(stderr, "Usage: chown <uid>[.<gid>] <file>\n");
	return;
    }
    if (sscanf(argv[1], "%d.%d", &own_uid, &own_gid) != 2) {
	own_gid = -1;
	if (sscanf(argv[1], "%d", &own_uid) != 1) {
	    fprintf(stderr, "chown: invalid uid[.gid]\n");
	    return;
	}
    }

    dargs.name = argv[2];
    bcopy(directory_handle, &dargs.dir, NFS_FHSIZE);
    if ((dres = nfsproc_lookup_2(&dargs, nfsclient)) == NULL) {
	clnt_perror(nfsclient, "nfsproc_lookup");
	return;
    }
    if (dres->status != NFS_OK) {
	fprintf(stderr, "%s: %s\n", argv[2], nfs_error(dres->status));
	return;
    }

    bcopy(&dres->diropres_u.diropres.file, &aargs.file, NFS_FHSIZE);
    aargs.attributes.mode = -1;
    aargs.attributes.uid = own_uid;
    aargs.attributes.gid = own_gid;
    aargs.attributes.size = -1;
    aargs.attributes.atime.seconds = -1;
    aargs.attributes.atime.useconds = -1;
    aargs.attributes.mtime.seconds = -1;
    aargs.attributes.mtime.useconds = -1;

    if ((ares = nfsproc_setattr_2(&aargs, nfsclient)) == NULL) {
	clnt_perror(nfsclient, "nfsproc_setattr");
	return;
    }
    if (ares->status != NFS_OK) {
	fprintf(stderr, "Set attributes failed: %s\n", nfs_error(ares->status));
	return;
    }
}

/*
 * Put file from local to remote
 */
do_put(argc, argv)
    int argc;
    char **argv;
{
    createargs cargs;
    diropargs dargs;
    diropres *cres;
    diropres *dres;
    char buf[BUFSIZ];
    fhandle handle;
    FILE *fp;
    int n;
    long offset;

    if (mountpath == NULL) {
	fprintf(stderr, "put: no remote file system mounted\n");
	return;
    }
    if (argc != 2 && argc != 3) {
	fprintf(stderr, "Usage: put <local-file> [<remote-file>]\n");
	return;
    }

    if ((fp = fopen(argv[1], "r")) == NULL) {
	fprintf(stderr, "put: cannot open %s\n", argv[1]);
	return;
    }

    /*
     * Create remote file name
     */
    cargs.where.name = argc == 3 ? argv[2] : argv[1];
    bcopy(directory_handle, &cargs.where.dir, NFS_FHSIZE);
    cargs.attributes.mode = 0666;
    cargs.attributes.uid = uid;
    cargs.attributes.gid = gid;
    cargs.attributes.size = -1;
    cargs.attributes.atime.seconds = -1;
    cargs.attributes.atime.useconds = -1;
    cargs.attributes.mtime.seconds = -1;
    cargs.attributes.mtime.useconds = -1;
    if ((cres = nfsproc_create_2(&cargs, nfsclient)) == NULL) {
	clnt_perror(nfsclient, "nfsproc_create");
	fclose(fp);
	return;
    }
    if (cres->status != NFS_OK)
	fprintf(stderr, "WARNING: Create failed: %s\n", nfs_error(*cres));

    /*
     * Look up remote file name, to get its handle
     */
    dargs.name = argc == 3 ? argv[2] : argv[1];
    bcopy(directory_handle, &dargs.dir, NFS_FHSIZE);
    if ((dres = nfsproc_lookup_2(&dargs, nfsclient)) == NULL) {
	clnt_perror(nfsclient, "nfsproc_lookup");
	fclose(fp);
	return;
    }
    if (dres->status != NFS_OK) {
	fprintf(stderr, "%s: %s\n", argv[1], nfs_error(dres->status));
	fclose(fp);
	return;
    }
    bcopy(&dres->diropres_u.diropres.file, handle, NFS_FHSIZE);

    for (offset = 0; (n = fread(buf, 1, sizeof(buf), fp)) > 0; offset += n) {
	writeargs wargs;
	attrstat *wres;

	bcopy(handle, &wargs.file, NFS_FHSIZE);
	wargs.beginoffset = wargs.offset = offset;
	wargs.totalcount = n;
	wargs.data.data_len = n;
	wargs.data.data_val = buf;
	if ((wres = nfsproc_write_2(&wargs, nfsclient)) == NULL) {
	    clnt_perror(nfsclient, "nfsproc_write");
	    fclose(fp);
	    return;
	}
	if (wres->status != NFS_OK) {
	    fprintf(stderr, "Write failed: %s\n", nfs_error(wres->status));
	    fclose(fp);
	    return;
	}
    }
    fclose(fp);
}

/*
 * Get/set file handle
 */
do_handle(argc, argv)
    int argc;
    char **argv;
{
    register char *p;
    register int i;
    int sock;

    if (argc <= 1) {
	if (mountpath == NULL) {
	    fprintf(stderr, "handle: no remote file system mounted\n");
	    return;
	}
	printf("%s:", mountpath);
	for (i = 0, p = (char *)directory_handle; i < NFS_FHSIZE; i++)
	    printf(" %02x", *p++ & 0xFF);
	printf("\n");
	return;
    }

    if (argc != NFS_FHSIZE + 1) {
	fprintf(stderr, "Usage: handle [<file handle>]\n");
	return;
    }

    /* umount previous mounted remote file system */
    if (mountpath != NULL)
	close_nfs();

    /* setup communication channel with NFS server */
    sock = privileged();
    nfsserver_addr = server_addr;
    if ((nfsclient = clntudp_create(&nfsserver_addr, NFS_PROGRAM,
      NFS_VERSION, timeout, &sock)) == NULL) {
        clnt_pcreateerror("nfs clntudp_create");
        return 0;
    }
    clnt_control(nfsclient, CLSET_TIMEOUT, &timeout);
    nfsclient->cl_auth = authunix_create_default(uid, gid);

    /* provide some generic name for it */
    if ((mountpath = malloc(8 + 1)) == NULL) {
	fprintf(stderr, "internal error: no more core for mountpath\n");
	return;
    }
    strcpy(mountpath, "<handle>");

    /* copy handle from command line argument */
    for (i = 0, p = (char *)directory_handle; i < NFS_FHSIZE; i++)
	*p++ = (char) xtoa(argv[i + 1]);

    /* get transfer size */
    if ((transfersize = determine_transfersize()) <= 0) {
	fprintf(stderr, "Handle failed: Unable to obtain transfer size\n");
	return;
    }
}

/*
 * Create a character device
 */
do_mknod(argc, argv)
    int argc;
    char **argv;

{
    createargs cargs;
    diropargs dargs;
    diropres *cres;
    diropres *dres;
    char buf[BUFSIZ];
    fhandle handle;
    FILE *fp;
    int n;
    long offset;

    if (mountpath == NULL) {
	fprintf(stderr, "mknod: no remote file system mounted\n");
	return;
    }
    if (argc != 4) {
	fprintf(stderr, "Usage: mknod <file> <major> <minor>]\n");
	return;
    }
printf("%s\n", argv[1]);

    cargs.where.name=argv[1];
    bcopy(directory_handle, &cargs.where.dir, NFS_FHSIZE);
    cargs.attributes.mode = IFCHR;
    cargs.attributes.uid = uid;
    cargs.attributes.gid = gid;
    cargs.attributes.size = makedev(0, 0);
    cargs.attributes.atime.seconds = -1;
    cargs.attributes.atime.useconds = -1;
    cargs.attributes.mtime.seconds = -1;
    cargs.attributes.mtime.useconds = -1;
    if ((cres = nfsproc_create_2(&cargs, nfsclient)) == NULL) {
	clnt_perror(nfsclient, "nfsproc_create");
	return;
    }
}

/*
 * Attempt to masquerade as root (and update RPC authenticaton info)
 */
do_mroot(var, argc, argv)
    int *var, argc;
    char **argv;

{
    *var = 0x00100000;
    if (nfsclient && nfsclient->cl_auth) {
	auth_destroy(nfsclient->cl_auth);
	nfsclient->cl_auth = authunix_create_default(uid, gid);
    }
/*
 * Put in some type of code here
 * to find a directory owned by root
 * and write a zero byte file to it.
 * once written, it would be possible
 * to test to make sure this method
 * works and tell the person upon
 * startup.
 */
}

int
xtoa(s)
    char *s;
{
    register int n;

    for (n = 0; *s; s++) {
	n <<= 4;
	if (*s >= '0' && *s <= '9')
	    n += *s - '0';
	else if (*s >= 'a' && *s <= 'f')
	    n += *s - 'a' + 10;
	else if (*s >= 'A' && *s <= 'F')
	    n += *s - 'A' + 10;
	else
	    break;
    }
    return n;
}

/*
 * Set up a channel to the NFS server and
 * mount remote file system.
 */
do_mount(argc, argv)
    int argc;
    char **argv;
{
    char *host, *path;
    int umount = 0, portmap = 0;

    argv++; argc--;
    if (argc >= 1 && strcmp(argv[0], "-u") == 0) {
	argv++; argc--;
	umount = 1;
    }
    if (argc >= 1 && strcmp(argv[0], "-p") == 0) {
	argv++; argc--;
	portmap = 1;
    }
    if (argc == 0) {
	fprintf(stderr, "Usage: mount [-u] [-p] [<host>:]<path>\n");
	return;
    }
    if ((path = strchr(host = argv[0], ':')) != NULL) {
	*path++ = '\0';
	/* this heuristic is far too simple */
	if (remotehost && strcmp(remotehost, host) == 0) {
	    if (mountpath) close_nfs();
	} else if (!open_mount(host))
	    return;
    } else
	path = argv[0];
    if (remotehost == NULL) {
	fprintf(stderr, "mount: no host specified\n");
	return;
    }
    open_nfs(path, umount, portmap);
}

/*
 * Unmount remote file system, and close
 * RPC channel.
 */
/* ARGUSED */
do_umount(argc, argv)
    int argc;
    char **argv;
{
    if (argc != 1) {
	fprintf(stderr, "Usage: umount\n");
	return;
    }
    if (mountpath == NULL)
	fprintf(stderr, "umount: no remote file system mounted\n");
    else
	close_nfs();
}

/*
 * Unmount all remote file system from this host
 */
/* ARGUSED */
do_umountall(argc, argv)
    int argc;
    char **argv;
{
    if (argc != 1) {
	fprintf(stderr, "Usage: umountall\n");
	return;
    }
    if (remotehost == NULL) {
	fprintf(stderr, "umountall: no host specified\n");
	return;
    }
    if (mountpath != NULL) close_nfs();
    (void) mountproc_umntall_1(NULL, mntclient);
}

/*
 * Display all exported file systems on remote system
 */
/* ARGUSED */
do_export(argc, argv)
    int argc;
    char **argv;
{
    exports ex, *exp;
    groups gr;

    if (argc != 1) {
	fprintf(stderr, "Usage: export\n");
	return;
    }
    if (remotehost == NULL) {
	fprintf(stderr, "export: no host specified\n");
	return;
    }
    if ((exp = mountproc_export_1(NULL, mntclient)) == NULL) {
	clnt_perror(mntclient, "mountproc_export");
	return;
    }
    printf("Export list for %s:\n", remotehost);
    for (ex = *exp; ex != NULL; ex = ex->ex_next) {
	printf("%-25s", ex->ex_dir);
	if (strlen(ex->ex_dir) >= 25)
	    printf("\n                    ");
	if ((gr = ex->ex_groups) == NULL)
	    printf("everyone");
	while (gr) {
	    printf("%s ", gr->gr_name);
	    gr = gr->gr_next;
	}
	putchar('\n');
    }
}

/*
 * Display all remote mounted file systems
 */
/* ARGUSED */
do_dump(argc, argv)
    int argc;
    char **argv;
{
    mountlist ml, *mlp;

    if (argc != 1) {
	fprintf(stderr, "Usage: dump\n");
	return;
    }
    if (remotehost == NULL) {
	fprintf(stderr, "dump: no host specified\n");
	return;
    }
    if ((mlp = mountproc_dump_1(NULL, mntclient)) == NULL) {
	clnt_perror(mntclient, "mountproc_dump");
	return;
    }
    for (ml = *mlp; ml != NULL; ml = ml->ml_next)
	printf("%s:%s\n", ml->ml_hostname, ml->ml_directory);
}

/*
 * Generic status report
 */
/* ARGUSED */
do_status(argc, argv)
    int argc;
    char **argv;
{
    if (argc != 1) {
	fprintf(stderr, "Usage: status\n");
	return;
    }
    printf("User id      : %d\n", uid);
    printf("Group id     : %d\n", gid);
    if (remotehost)
	printf("Remote host  : `%s'\n", remotehost);
    if (mountpath)
	printf("Mount path   : `%s'\n", mountpath);
    printf("Transfer size: %d\n", transfersize);
}

/*
 * Simple on-line help facility
 */
/* ARGUSED */
do_help(argc, argv)
    int argc;
    char **argv;
{
    register int i;

    for (i = 0; i < sizeof(keyword)/sizeof(struct keyword); i++) {
	if (argc == 2 && strcmp(keyword[i].kw_command, argv[1]) != 0)
	    continue;
	printf("%s %s\n", keyword[i].kw_command, keyword[i].kw_help);
    }
}

/*
 * Open a channel to remote Mount daemon,
 * possibly closing an already open connection.
 */
int
open_mount(host)
    char *host;
{
    int sock = privileged();

    /* close previous mounted host */
    if (remotehost != NULL)
	close_mount();

    /* convert hostname to IP address */
    if (isdigit(*host)) {
        server_addr.sin_addr.s_addr = inet_addr(host);
    } else {
        struct hostent *hp = gethostbyname(host);
        if (hp == NULL) {
            fprintf(stderr, "%s: unknown host\n", host);
	    return 0;
        }
        bcopy(hp->h_addr, &server_addr.sin_addr.s_addr, hp->h_length);
        host = hp->h_name;
    }
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = 0;

    /* set host name */
    if ((remotehost = malloc(strlen(host) + 1)) == NULL) {
	fprintf(stderr, "internal error: no more core for host\n");
	return 0;
    }
    strcpy(remotehost, host);

    /* setup communication channel with mount daemon */
    mntserver_addr = server_addr;
    if ((mntclient = clntudp_create(&mntserver_addr, MOUNTPROG,
      MOUNTVERS, timeout, &sock)) == NULL) {
        clnt_pcreateerror("mount clntudp_create");
        return 0;
    }
    clnt_control(mntclient, CLSET_TIMEOUT, &timeout);
    mntclient->cl_auth = authunix_create_default(0, 0);
    if (verbose)
	printf("Open %s (%s)\n",
	    remotehost, inet_ntoa(server_addr.sin_addr));
    return 1;
}

/*
 * Close channel to mount daemon,
 * possibly umounting a NFS file system.
 */
close_mount()
{
    if (mountpath) close_nfs();
    if (verbose) printf("Close `%s'\n", remotehost);
    free(remotehost);
    remotehost = NULL;
    if (mntclient) {
	auth_destroy(mntclient->cl_auth);
	clnt_destroy(mntclient);
    }
}

/*
 * Mount an NFS file system, perhaps closing
 * a previous mounted one. The umount option
 * fools the accounting system.
 */
int
open_nfs(path, umount, portmap)
    char *path;
    int umount, portmap;
{
    int sock = privileged();

    /* umount previous mounted remote file system */
    if (mountpath != NULL)
	close_nfs();

    /* setup communication channel with NFS server */
    nfsserver_addr = server_addr;
    if ((nfsclient = clntudp_create(&nfsserver_addr, NFS_PROGRAM,
      NFS_VERSION, timeout, &sock)) == NULL) {
        clnt_pcreateerror("nfs clntudp_create");
        return 0;
    }
    clnt_control(nfsclient, CLSET_TIMEOUT, &timeout);
    nfsclient->cl_auth = authunix_create_default(uid, gid);

    /*
     * Get file handle for this path from the mount daemon. There
     * are two ways to get it, either ask it directly or get it
     * through the port mapper.
     */
    if (portmap) {
	if ((mountpoint = pmap_mnt(&path, &mntserver_addr)) == NULL)
	    return 0;
    } else if ((mountpoint = mountproc_mnt_1(&path, mntclient)) == NULL) {
        clnt_perror(mntclient, "mountproc_mnt");
	return 0;
    }
    if (mountpoint->fhs_status != NFS_OK) {
        fprintf(stderr, "Mount failed: %s\n",
            nfs_error(mountpoint->fhs_status));
	return 0;
    }
    bcopy(mountpoint->fhstatus_u.fhs_fhandle, directory_handle, NFS_FHSIZE);
    if (umount) (void) mountproc_umnt_1(&path, mntclient);

    /* get transfer size */
    if ((transfersize = determine_transfersize()) <= 0) {
	fprintf(stderr, "Mount failed: Unable to obtain transfer size\n");
	(void) mountproc_umnt_1(&path, mntclient);
	return 0;
    }

    /* set mount path */
    if ((mountpath = malloc(strlen(path) + 1)) == NULL) {
	fprintf(stderr, "internal error: no more core for mountpath\n");
	return 0;
    }
    strcpy(mountpath, path);
    if (verbose)
	printf("Mount `%s'%s, transfer size %d bytes.\n",
	    mountpath, umount ? " (unmount)":"", transfersize);
    return 1;
}

/*
 * Make a mount call via the port mapper
 */
fhstatus *
pmap_mnt(argp, server_addr)
    dirpath *argp;
    struct sockaddr_in *server_addr;
{
    enum clnt_stat stat;
    static fhstatus res;
    u_long port;

    bzero((char *)&res, sizeof(res));
    if ((stat = pmap_rmtcall(server_addr, MOUNTPROG, MOUNTVERS, MOUNTPROC_MNT,
      xdr_dirpath, argp, xdr_fhstatus, &res, timeout, &port)) != RPC_SUCCESS) {
	clnt_perrno(stat);
	return NULL;
    }
    return &res;
}

/*
 * Determine NFS server's transfer size
 */
static int
determine_transfersize()
{
    statfsres *res;

    if ((res = nfsproc_statfs_2(directory_handle, nfsclient)) == NULL)
	return 0;
    if (res->status != NFS_OK)
	return 0;
    return res->statfsres_u.reply.tsize;
}

/*
 * Acquire a privileged port when possible
 */
int
privileged()
{
    int lport = IPPORT_RESERVED - 1;
    struct sockaddr_in sin;
    int s, dontblock = 1;

    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = 0;
    s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (s < 0)
	return RPC_ANYSOCK;
    for (;;) {
	sin.sin_port = htons((u_short)lport);
	if (bind(s, (caddr_t)&sin, sizeof (sin), 0) >= 0) {
	    (void)ioctl(s, FIONBIO, (char *) &dontblock);
	    if (verbose)
		fprintf(stderr, "Using a privileged port (%d)\n", lport);
	    fflush(stderr);
	    return s;
	}
	if (errno != EADDRINUSE && errno != EADDRNOTAVAIL)
	    return RPC_ANYSOCK;
	(lport)--;
	if (lport == IPPORT_RESERVED/2) {
	    fprintf(stderr, "privileged socket: All ports in use\n");
	    return RPC_ANYSOCK;
	}
    }
}

/*
 * Close an NFS mounted file system
 */
close_nfs()
{
    if (mountpath == NULL) return;
    if (verbose) printf("Unmount `%s'\n", mountpath);
    (void) mountproc_umnt_1(&mountpath, mntclient);
    free(mountpath);
    mountpath = NULL;
    if (nfsclient) {
	auth_destroy(nfsclient->cl_auth);
	clnt_destroy(nfsclient);
    }
}

/*
 * Returns an auth handle with parameters determined by doing lots of
 * syscalls.
 */
AUTH *
authunix_create_default(uid, gid)
    int uid, gid;
{
    char machname[MAX_MACHINE_NAME + 1];
    int gids[1];

    if (gethostname(machname, MAX_MACHINE_NAME) == -1) {
	fprintf(stderr, "authunix_create_default: cannot get hostname\n");
	exit(1);
    }
    machname[MAX_MACHINE_NAME] = 0;
    gids[0] = gid;
    return (authunix_create(machname, uid, gid, 1, gids));
}

/*
 * Read all entries (names) in directory 'dirhandle' into
 * a dynamically build table. It is up to the caller to free
 * this table.
 */
getdirentries(dirhandle, table, ptr, nentries)
    fhandle *dirhandle;
    register char ***table;
    register char ***ptr;
    int nentries;
{
    readdirargs args;
    readdirres *res;
    entry *ep;
    int dircmp();
    char **last;

    *ptr = *table = (char **) calloc(nentries, sizeof(char *));
    last = *ptr + nentries;
    if (*ptr == NULL) {
	fprintf(stderr, "getdirentries: out of memory\n");
	return 0;
    }

    bcopy(*dirhandle, &args.dir, NFS_FHSIZE);
    bzero(args.cookie, NFS_COOKIESIZE);
    args.count = 1000;
    for (;;) {
        if ((res = nfsproc_readdir_2(&args, nfsclient)) == NULL) {
            clnt_perror(nfsclient, "nfsproc_readdir");
            break;
        }
        if (res->status != NFS_OK) {
            fprintf(stderr, "Readdir failed: %s\n", nfs_error(res->status));
            break;
        }

        ep = res->readdirres_u.reply.entries;
        while (ep != NULL) {
	    if (*ptr == last) {
		*table = (char **)realloc(*table, 2*nentries*sizeof(char *));
		if (*table == NULL) {
		    fprintf(stderr, "getdirentries: out of memory\n");
		    exit(1);
		}
		*ptr = *table + nentries;
		last = *ptr + nentries;
		nentries *= 2;
	    }
	    if ((*(*ptr)++ = strsave(ep->name)) == NULL)
		return 0;

            if (ep->nextentry == NULL)
                break;
            ep = ep->nextentry;
        }
        if (res->readdirres_u.reply.eof)
            break;
        bcopy(ep->cookie, args.cookie, NFS_COOKIESIZE);
    }
    qsort(*table, *ptr - *table, sizeof(char **), dircmp);
    return 1;
}

char *
strsave(str)
    char *str;
{
    char *cp = malloc(strlen(str) + 1);

    if (cp == NULL) {
	fprintf(stderr, "strsave: out of memory\n");
	return NULL;
    }
    strcpy(cp, str);
    return cp;
}

int
dircmp(p, q)
    char **p, **q;
{
    return strcmp(*p, *q);
}

/*
 * Match string against a normal shell pattern (*?[])
 */
int
match(s, argc, argv)
    char *s;
    int argc;
    char **argv;
{
    register int i;

    if (argc == 0) return 1;
    for (i = 0; i < argc; i++)
	if (matchpattern(s, argv[i]))
	    return 1;
    return 0;
}

int
matchpattern(s, p)
    char *s, *p;
{
    if (*s == '.' && *p != '.')
	return 0;
    return amatchpattern(s, p);
}

int
amatchpattern(s, p)
    register char *s, *p;
{
    register int scc;
    int c, cc, ok, lc;

    if (scc = *s++)
	if ((scc &= 0177) == 0)
	    scc = 0200;

    switch (c = *p++) {
    case '[':
	ok = 0;
	lc = 077777;
	while (cc = *p++) {
	    if (cc == ']') {
		if (ok)
		    return amatchpattern(s, p);
		else
		    return 0;
	    } else if (cc == '-') {
		if (lc <= scc && scc <= (c = *p++))
		    ok++;
	    } else
		if (scc == (lc = cc))
		    ok++;
	}
	return 0;
    case '*':
	return umatchpattern(--s, p);
    case '\0':
	return !scc;
    default:
	if (c != scc)
	    return 0;
    case '?':
	if (scc)
	    return amatchpattern(s, p);
	return 0;
    }
}

int
umatchpattern(s, p)
    register char *s, *p;
{
    if (*p == '\0')
	return 1;
    while (*s != '\0')
	if (amatchpattern(s++, p))
	    return 1;
    return 0;
}

/*
 * NFS errors
 */
char *
nfs_error(nfsstat)
    enum nfsstat nfsstat;
{
    switch (nfsstat) {
    case NFS_OK:
	return "No error";
    case NFSERR_PERM:
	return "Not owner";
    case NFSERR_NOENT:
	return "No such file or directory";
    case NFSERR_IO:
	return "I/O error";
    case NFSERR_NXIO:
	return "No such device or address";
    case NFSERR_ACCES:
	return "Permission denied";
    case NFSERR_EXIST:
	return "File exists";
    case NFSERR_NODEV:
	return "No such device";
    case NFSERR_NOTDIR:
	return "Not a directory";
    case NFSERR_ISDIR:
	return "Is a directory";
    case NFSERR_FBIG:
	return "File too large";
    case NFSERR_NOSPC:
	return "No space left on device";
    case NFSERR_ROFS:
	return "Read-only file system";
    case NFSERR_NAMETOOLONG:
	return "File name too long";
    case NFSERR_NOTEMPTY:
	return "Directory not empty";
    case NFSERR_DQUOT:
	return "Disc quota exceeded";
    case NFSERR_STALE:
	return "Stale NFS file handle";
    case NFSERR_WFLUSH:
	return "Write cache flushed";
    default:
	return "UKNOWN NFS ERROR";
    }
}

