/*
 * Copyright (c) 2003-2011
 * Distributed Systems Software.  All rights reserved.
 * See the file LICENSE for redistribution information.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2011\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: expr_file.c 2528 2011-09-23 21:54:05Z brachman $";
#endif

#include "dacs_config.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>		/* for MAXPATHLEN */
#if defined(DACS_OS_SOLARIS)
#include <netdb.h>
#endif
#include <sys/mount.h>
#include <sys/time.h>
#include <pwd.h>
#include <time.h>
#include <grp.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

#include "ds.h"
#include "str.h"

#ifdef DACS_OS_CYGWIN
/* For utimes() */
#include <sys/time.h>
#endif

static MAYBE_UNUSED char *log_module_name = "expr_file";

static int do_basename(int argc, char **argv, char **result);
static int do_chmod(int argc, char **argv, char **result);
static int do_dirname(int argc, char **argv, char **result);
static int do_extension(int argc, char **argv, char **result);
static int do_lstat(int argc, char **argv, char **result);
static int do_mkdir(int argc, char **argv, char **result);
static int do_readlink(int argc, char **argv, char **result);
static int do_remove(int argc, char **argv, char **result);
static int do_rename(int argc, char **argv, char **result);
static int do_rmdir(int argc, char **argv, char **result);
static int do_stat(int argc, char **argv, char **result);
static int do_test(int argc, char **argv, char **result);
static int do_touch(int argc, char **argv, char **result);

/*
 * Each command is passed an argc/argv pair, with argv[0] being the name
 * of the command itself.  A command returns -1 to indicate
 * failure and optionally sets RESULT to point to an error message.
 * If 0 is returned, the command failed and the result is an empty string.
 * Any positive returned value means RESULT points to the string value.
 */
typedef struct Command {
  char *name;
  int (*func)(int argc, char **argv, char **result);
} Command;

static Command command[] = {
  { "basename",  do_basename },
  { "chmod",     do_chmod },
  { "dirname",   do_dirname },
  { "extension", do_extension },
  { "lstat",     do_lstat },
  { "mkdir",     do_mkdir },
  { "readlink",  do_readlink },
  { "remove",    do_remove },
  { "rename",    do_rename },
  { "rmdir",     do_rmdir },
  { "stat",      do_stat },
  { "test",      do_test },
  { "touch",     do_touch },
  { NULL,        NULL }
};

static char *fs(char *filename, struct stat *statbuf);
static int stat_to_text(char *filename, char *fmt, struct stat *statbuf,
				  char **result);
static void mode_to_text(Ds *ds, mode_t mode);
static void time_to_text(Ds *ds, time_t t);
static void uid_to_text(Ds *ds, u_long uid);
static void gid_to_text(Ds *ds, u_long gid);

static int
check_args(int argc, char **argv, int n)
{

  if (n != argc - 1)
	return(-1);

  return(0);
}

/*
 * Delete any prefix ending with the last slash character present in STRING
 * (after first stripping trailing slashes), and a SUFFIX, if given.  The
 * suffix is not stripped if it is identical to the remaining
 * characters in string.  The resulting string is returned.
 * A non-existent suffix is ignored.
 */
static int
do_basename(int argc, char **argv, char **result)
{
  char *pathname, *suffix;

  pathname = argv[1];
  suffix = argv[2];
  *result = strbasename(pathname, suffix);

  return(1);
}

static int
abs_mode(char *mode_str, mode_t *mode)
{

  return(strnum_b(mode_str, STRNUM_MODE_T, 8, mode, NULL));
}

/*
 * Limited to an absolute mode (octal number).
 */
static int
do_chmod(int argc, char **argv, char **result)
{
  char *file, *mode_str;
  mode_t mode;

  mode_str = argv[1];
  file = argv[2];

  if (abs_mode(mode_str, &mode) != -1) {
	if (chmod(file, mode) == -1) {
	  *result = strdup(strerror(errno));
	  return(-1);
	}
  }
  else {
	*result = "Invalid mode";
	return(-1);
  }

  *result = "1";
  return(1);
}

static int
do_dirname(int argc, char **argv, char **result)
{
  char *pathname;

  pathname = argv[1];

  *result = strdirname(pathname);

  return(1);
}

static int
do_extension(int argc, char **argv, char **result)
{
  char *ext;

  if ((ext = strextname(argv[1])) != NULL) {
	*result = ext;
	return(1);
  }

  return(0);
}

static int
do_lstat(int argc, char **argv, char **result)
{
  char *filename, *fmt;
  struct stat statbuf;

  if (check_args(argc, argv, 2) == -1) {
	*result = "Incorrect number of arguments";
	return(-1);
  }

  fmt = argv[1];
  filename = argv[2];

  if (lstat(filename, &statbuf) == -1) {
	*result = strdup(strerror(errno));
	return(-1);
  }

  return(stat_to_text(filename, fmt, &statbuf, result));
}

static int
do_mkdir(int argc, char **argv, char **result)
{
  char *dirname, *mode_str;
  mode_t mode;

  dirname = argv[1];
  if ((mode_str = argv[2]) == NULL)
	mode = 0755;
  else if (abs_mode(mode_str, &mode) == -1) {
	*result = "Invalid mode";
	return(-1);
  }

  if (mkdir(dirname, mode) == -1) {
	*result = strdup(strerror(errno));
	return(-1);
  }
  *result = "1";

  return(1);
}

static int
do_readlink(int argc, char **argv, char **result)
{
  int n;
  char buf[MAXPATHLEN], *filename;

  filename = argv[1];
  if ((n = readlink(filename, buf, sizeof(buf))) == -1) {
	*result = strdup(strerror(errno));
	return(-1);
  }
  buf[n] = '\0';
  *result = strdup(buf);

  return(1);
}

static int
do_remove(int argc, char **argv, char **result)
{
  char *filename;

  filename = argv[1];
  if (unlink(filename) == -1) {
	*result = strdup(strerror(errno));
	return(-1);
  }

  *result = "1";
  return(1);
}

static int
do_rename(int argc, char **argv, char **result)
{
  char *from_file, *to_file;

  from_file = argv[1];
  to_file = argv[2];
  if (rename(from_file, to_file) == -1) {
	*result = strdup(strerror(errno));
	return(-1);
  }

  *result = "1";
  return(1);
}

static int
do_rmdir(int argc, char **argv, char **result)
{
  char *dirname;

  dirname = argv[1];
  if (rmdir(dirname) == -1) {
	*result = strdup(strerror(errno));
	return(-1);
  }

  *result = "1";
  return(1);
}

/*
 * Functionality is modeled after that of test(1).
 *
 * -b file       True if file exists and is a block special file.
 * -c file       True if file exists and is a character special file.
 * -d file       True if file exists and is a directory.
 * -e file       True if file exists (regardless of type).
 * -f file       True if file exists and is a regular file.
 * -g file       True if file exists and its set group ID flag is set.
 * -k file       True if file exists and its sticky bit is set.
 * -p file       True if file is a named pipe (FIFO).
 * -r file       True if file exists and is readable.
 * -s file       True if file exists and has a size greater than zero.
 * -u file       True if file exists and its set user ID flag is set.
 * -w file       True if file exists and is writable.  True indicates only
 *               that the write flag is on.  The file is not writable on a
 *               read-only file system even if this test indicates true.
 * -x file       True if file exists and is executable.  True indicates only
 *               that the execute flag is on.  If file is a directory, true
 *               indicates that file can be searched.
 * -L file       True if file exists and is a symbolic link.
 * -O file       True if file exists and its owner matches the effective
 *               user id of this process.
 * -G file       True if file exists and its group matches the effective
 *               group id of this process.
 * -S file       True if file exists and is a socket.
 * -nt file1 file2
 *               True if file1 exists and is newer than file2.
 * -ot file1 file2
 *               True if file1 exists and is older than file2.
 * -ef file1 file2
 *               True if file1 and file2 exist and refer to the same file.
 */
static int
do_test(int argc, char **argv, char **result)
{
  int st;
  char *file, *op;
  struct stat statb;

  op = argv[1];
  file = argv[2];
  st = stat(file, &statb);

  if (!strcmp(op, "-b")) {
	if (st == -1) goto stat_failed;
	if ((statb.st_mode & S_IFMT) == S_IFBLK) {
	  *result = "1";
	  return(1);
	}
	return(0);
  }
  else if (!strcmp(op, "-c")) {
	if (st == -1) goto stat_failed;
	if ((statb.st_mode & S_IFMT) == S_IFCHR) {
	  *result = "1";
	  return(1);
	}
	return(0);
  }
  else if (!strcmp(op, "-d")) {
	if (st == -1) goto stat_failed;
	if ((statb.st_mode & S_IFMT) == S_IFDIR) {
	  *result = "1";
	  return(1);
	}
	return(0);
  }
  else if (!strcmp(op, "-e")) {
	if (st == -1)
	  return(0);
	*result = "1";
	return(1);
  }
  else if (!strcmp(op, "-f")) {
	if (st == -1) goto stat_failed;
	if ((statb.st_mode & S_IFMT) == S_IFREG) {
	  *result = "1";
	  return(1);
	}
	return(0);
  }
  else if (!strcmp(op, "-g")) {
	if (st == -1) goto stat_failed;
	if ((statb.st_mode & S_IFMT) == S_ISGID) {
	  *result = "1";
	  return(1);
	}
	return(0);
  }
  else if (!strcmp(op, "-k")) {
	if (st == -1) goto stat_failed;
	if ((statb.st_mode & S_IFMT) == S_ISVTX) {
	  *result = "1";
	  return(1);
	}
	return(0);
  }
  else if (!strcmp(op, "-p")) {
	if (st == -1) goto stat_failed;
	if ((statb.st_mode & S_IFMT) == S_IFIFO) {
	  *result = "1";
	  return(1);
	}
	return(0);
  }
  else if (!strcmp(op, "-r")) {
	if (st == -1) goto stat_failed;
	if (access(file, R_OK) == -1)
	  return(0);
	*result = "1";
	return(1);
  }
  else if (!strcmp(op, "-s")) {
	if (st == -1) goto stat_failed;
	if (statb.st_size > 0) {
	  *result = "1";
	  return(1);
	}
	return(0);
  }
  else if (!strcmp(op, "-u")) {
	if (st == -1) goto stat_failed;
	if ((statb.st_mode & S_IFMT) == S_ISUID) {
	  *result = "1";
	  return(1);
	}
	return(0);
  }
  else if (!strcmp(op, "-w")) {
	if (st == -1) goto stat_failed;
	if (access(file, W_OK) == -1)
	  return(0);
	*result = "1";
	return(1);
  }
  else if (!strcmp(op, "-x")) {
	if (st == -1) goto stat_failed;
	if (access(file, X_OK) == -1)
	  return(0);
	if ((statb.st_mode & S_IFMT) == S_IFDIR || getuid() != 0
		|| (statb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) {
	  *result = "1";
	  return(1);
	}
	return(0);
  }
  else if (!strcmp(op, "-G")) {
	gid_t egid;

	if (st == -1) goto stat_failed;
	egid = getegid();
	if (egid == statb.st_gid) {
	  *result = "1";
	  return(1);
	}
	return(0);
  }
  else if (!strcmp(op, "-L")) {
	if (st == -1) goto stat_failed;
	if ((statb.st_mode & S_IFMT) == S_IFLNK) {
	  *result = "1";
	  return(1);
	}
	return(0);
  }
  else if (!strcmp(op, "-O")) {
	uid_t euid;

	if (st == -1) goto stat_failed;
	euid = geteuid();
	if (euid == statb.st_uid) {
	  *result = "1";
	  return(1);
	}
	return(0);
  }
  else if (!strcmp(op, "-S")) {
	if (st == -1) goto stat_failed;
	if ((statb.st_mode & S_IFMT) == S_IFSOCK) {
	  *result = "1";
	  return(1);
	}
	return(0);
  }
  else if (!strcmp(op, "-nt")) {
	char *file2;
	struct stat statb2;

	if (st == -1) goto stat_failed;
	file2 = argv[3];
	if (stat(file2, &statb2) == -1)
	  goto stat_failed;
	if (statb.st_mtime > statb2.st_mtime) {
	  *result = "1";
	  return(1);
	}
	return(0);
  }
  else if (!strcmp(op, "-ot")) {
	char *file2;
	struct stat statb2;

	if (st == -1) goto stat_failed;
	file2 = argv[3];
	if (stat(file2, &statb2) == -1)
	  goto stat_failed;
	if (statb.st_mtime < statb2.st_mtime) {
	  *result = "1";
	  return(1);
	}
	return(0);
  }
  else if (!strcmp(op, "-ef")) {
	char *file2;
	struct stat statb2;

	if (st == -1) goto stat_failed;
	file2 = argv[3];
	if (stat(file2, &statb2) == -1)
	  goto stat_failed;
	if (statb.st_dev == statb2.st_dev && statb.st_ino == statb2.st_ino) {
	  *result = "1";
	  return(1);
	}
	return(0);
  }
  else {
	*result = "Unrecognized operation";
	return(-1);
  }

  return(0);

stat_failed:
  if (errno == ENOENT)
	return(0);
  *result = strdup(strerror(errno));

  return(-1);
}

#include <fcntl.h>
#include <utime.h>

static int
do_touch(int argc, char **argv, char **result)
{
  int fd, st;
  char *file, *mode_str;
  mode_t mode;
  struct stat statb;

  file = argv[1];
  mode_str = argv[2];

  st = stat(file, &statb);
  if (st == -1) {
	if (errno != ENOENT) {
	  *result = strdup(strerror(errno));
	  return(-1);
	}
	/* Doesn't exist, create it. */
	if (mode_str == NULL)
	  mode = 0644;
	else if (abs_mode(mode_str, &mode) == -1) {
	  *result = "Invalid mode";
	  return(-1);
	}
	fd = open(file, O_WRONLY | O_CREAT, mode);
	if (fd == -1 || fstat(fd, &statb) != 0 || close(fd) != 0) {
	  *result = strdup(strerror(errno));
	  return(-1);
	}
  }
  else {
	/* Exists, update it. */
	if (utimes(file, NULL) == -1) {
	  *result = strdup(strerror(errno));
	  return(-1);
	}
  }

  return(0);
}

static int
do_stat(int argc, char **argv, char **result)
{
  char *filename, *fmt;
  struct stat statbuf;

  fmt = argv[1];
  filename = argv[2];
  if (stat(filename, &statbuf) == -1) {
	*result = strdup(strerror(errno));
	return(-1);
  }

  return(stat_to_text(filename, fmt, &statbuf, result));
}

static int
stat_to_text(char *filename, char *fmt, struct stat *statbuf, char **result)
{
  char *p, *str;
  Ds ds;

  p = fmt;
  ds_init(&ds);

  while (*p != '\0') {
	if (*p == '\\') {
	  p++;
	  switch (*p) {
	  case '\\':
		ds_asprintf(&ds, "\\");
		break;
	  case 'n':
		ds_asprintf(&ds, "\n");
		break;
	  case 't':
		ds_asprintf(&ds, "\t");
		break;
	  default:
		*result = ds_xprintf("Bad escape sequence: \\%c\n", *p);
		return(-1);
	  }
	}
	else if (*p == '%') {
	  p++;
	  switch (*p) {
	  case 'b':
		ds_asprintf(&ds, "%ld", (u_long) statbuf->st_blksize);
		break;
	  case 'd':
		ds_asprintf(&ds, "%d", (unsigned short) statbuf->st_dev);
		break;
	  case 'f':
		if ((str = fs(filename, statbuf)) != NULL)
		  ds_asprintf(&ds, "%s", str);
		else
		  ds_asprintf(&ds, "<unknown>");
		break;
	  case 'i':
		ds_asprintf(&ds, "%ld", (u_long) statbuf->st_ino);
		break;
	  case 'm':
		ds_asprintf(&ds, "%lo", (u_long) statbuf->st_mode);
		break;
	  case 'M':
		mode_to_text(&ds, statbuf->st_mode);
		break;
	  case 'l':
		ds_asprintf(&ds, "%ld", (u_long) statbuf->st_nlink);
		break;
	  case 'u':
		ds_asprintf(&ds, "%ld", (u_long) statbuf->st_uid);
		break;
	  case 'U':
		uid_to_text(&ds, (u_long) statbuf->st_uid);
		break;
	  case 'g':
		ds_asprintf(&ds, "%ld", (u_long) statbuf->st_gid);
		break;
	  case 'G':
		gid_to_text(&ds, (gid_t) statbuf->st_gid);
		break;
	  case 'r':
		ds_asprintf(&ds, "%ld", (u_long) statbuf->st_rdev);
		break;
	  case 's':
		ds_asprintf(&ds, "%ld", (u_long) statbuf->st_size);
		break;
	  case 't':
		p++;
		switch (*p) {
		case 'a':
		  ds_asprintf(&ds, "%ld", (u_long) statbuf->st_atime);
		  break;
		case 'A':
		  time_to_text(&ds, (time_t) statbuf->st_atime);
		  break;
		case 'm':
		  ds_asprintf(&ds, "%ld", (u_long) statbuf->st_mtime);
		  break;
		case 'M':
		  time_to_text(&ds, (time_t) statbuf->st_mtime);
		  break;
		case 'c':
		  ds_asprintf(&ds, "%ld", (u_long) statbuf->st_ctime);
		  break;
		case 'C':
		  time_to_text(&ds, (time_t) statbuf->st_ctime);
		  break;
		default:
		  *result = ds_xprintf("Bad escape sequence: %%t%c\n", *p);
		  return(-1);
		}
		break;
	  case 'n':
		ds_asprintf(&ds, "%s", filename);
		break;
	  case 'N':
		{
		  char buf[MAXPATHLEN];		/* plenty big */
		  struct stat lstatbuf;

		  if (lstat(filename, &lstatbuf) < 0) {
			*result = ds_xprintf("%s: %s\n", filename, strerror(errno));
			return(-1);
		  }
		  if ((lstatbuf.st_mode & S_IFMT) == S_IFLNK) {
			if (readlink(filename, buf, sizeof(buf)) >= 0)
			  printf("%s", buf);
			else {
			  *result = ds_xprintf("%s: %s\n",
								   filename, strerror(errno));
			  return(-1);
			}
		  }
		  else
			ds_asprintf(&ds, "%s", filename);
		  break;
		}
	  case '%':
		ds_asprintf(&ds, "%%");
		break;
	  default:
		*result = ds_xprintf("Bad escape sequence: %%%c\n", *p);
		return(-1);
	  }
	}
	else
	  ds_appendc(&ds, (int) *p);
	p++;
  }

  ds_appendc(&ds, (int) '\0');
  *result = ds_buf(&ds);

  return(1);
}

static struct flags {
  u_int truemask;
  u_int falsemask;
  u_int flag;
  u_int incr;
} flags[24] = {
  { S_IREAD,         0,              'r', 2 },
  { ~0,              0,              '-', 1 },
  { S_IWRITE,        0,              'w', 2 },
  { ~0,              0,              '-', 1 },
  { S_ISUID,         (S_IEXEC),      'S', 4 },
  { S_ISUID,         0,              's', 3 },
  { S_IEXEC,         0,              'x', 2 },
  { ~0,              0,              '-', 1 },

  { (S_IREAD  >> 3), 0,              'r', 2 },
  { ~0,              0,              '-', 1 },
  { (S_IWRITE >> 3), 0,              'w', 2 },
  { ~0,              0,              '-', 1 },
  { S_ISGID,         (S_IEXEC >> 3), 'S', 4 },
  { S_ISGID,         0,              's', 3 },
  { (S_IEXEC  >> 3), 0,              'x', 2 },
  { ~0,              0,              '-', 1 },

  { (S_IREAD  >> 6), 0,              'r', 2 },
  { ~0,              0,              '-', 1 },
  { (S_IWRITE >> 6), 0,              'w', 2 },
  { ~0,              0,              '-', 1 },
  { S_ISVTX,         (S_IEXEC >> 6), 'T', 4 },
  { S_ISVTX,         0,              't', 3 },
  { (S_IEXEC  >> 6), 0,              'x', 2 },
  { ~0,              0,              '-', 1 }
};

static void
mode_to_text(Ds *ds, mode_t mode)
{
  struct flags *f, *last;

  if (mode & S_IFIFO)
	ds_appendc(ds, (int) 'p');
  else if (mode & S_IFCHR)
	ds_appendc(ds, (int) 'c');
  else if (mode & S_IFDIR)
	ds_appendc(ds, (int) 'd');
  else if (mode & S_IFBLK)
	ds_appendc(ds, (int) 'b');
  else if (mode & S_IFREG)
	ds_appendc(ds, (int) '-');
  else if (mode & S_IFLNK)
	ds_appendc(ds, (int) 'l');
  else if (mode & S_IFSOCK)
	ds_appendc(ds, (int) 's');
  else
	ds_appendc(ds, (int) '?');

  f = flags;
  last = &flags[24];
  while (f < last) {
	if ((f->falsemask && (f->truemask & mode) && !(f->falsemask & mode))
		|| (!f->falsemask && (f->truemask & mode))) {
	  ds_appendc(ds, f->flag);
	  f += f->incr;
	}
	else
	  f++;
  }
}

static void
time_to_text(Ds *ds, time_t t)
{
  char *p, *q;

  p = q = ctime(&t);
  while (*q != '\n' && *q != '\0')
	q++;
  if (*q == '\n')
	*q = '\0';
  ds_asprintf(ds, "%s", p);
}

static void
uid_to_text(Ds *ds, u_long uid)
{
  struct passwd *pw;

  if ((pw = getpwuid(uid)) == NULL)
	ds_asprintf(ds, "%lu", uid);
  else
	ds_asprintf(ds, "%s", pw->pw_name);
}

static void
gid_to_text(Ds *ds, u_long gid)
{
  struct group *grp;

  if ((grp = getgrgid(gid)) == NULL)
	ds_asprintf(ds, "%d", gid);
  else
	ds_asprintf(ds, "%s", grp->gr_name);
}

#include <sys/param.h>
#include <sys/mount.h>

#if defined(HAVE_GETMNTINFO)
/*
 * Given a name like /usr/src/etc/foo.c, return the name
 * of the mount point of the file system it lives in.  If it's remote,
 * the name is assumed to be prefixed by the hostname.
 * Return NULL if we fail.
 */
static char *
getmntpt(char *name, struct stat *statbuf)
{
  char *abs_name, *p;
  long best, best_len, i, n;
  struct statfs *mntbuf;

  if ((n = getmntinfo(&mntbuf, MNT_NOWAIT)) == -1)
	return(NULL);

  if (name[0] != '/') {
	char buf[MAXPATHLEN], *cwd;

	if ((cwd = getcwd(buf, MAXPATHLEN)) == NULL)
	  return(NULL);
	abs_name = ds_xprintf("%s/%s", cwd, name);
  }
  else
	abs_name = name;

  best = -1;
  best_len = -1;
  for (i = 0; i < n; i++) {
	if ((p = strprefix(abs_name, mntbuf[i].f_mntonname)) != NULL
		&& (p - abs_name) > best_len) {
	  best = i;
	  best_len = p - abs_name;
	}
  }

  if (best == -1)
	return(NULL);
  return(strdup(mntbuf[best].f_mntonname));
}
#elif defined(HAVE_GETMNTENT)
static char *
getmntpt(char *name, struct stat *statbuf)
{
#ifdef NOTDEF
  char *fsname;
  FILE *fp;
  struct mntent *mnt;
  struct stat dirstat;

  if ((fp = setmntent(name, "r")) == NULL)
	return(NULL);

  /*
   * If 'name' is on a remote file system then mnt_fsname will be
   * of the form "host:name", oth. it will look like "/dev/..."
   */
  fsname = NULL;
  if ((mnt = getmntpt(fp)) != NULL) { 
	if (!strcmp(mnt->mnt_type, MNTTYPE_IGNORE)
		|| !strcmp(mnt->mnt_type, MNTTYPE_SWAP))
	  continue;
	if (stat(mnt->mnt_dir, &dirstat) != -1
		&& filestat->st_dev == dirstat.st_dev) {
	  fsname = strdup(mnt->fs_name);
	  break;
	}
  }

  endmntent(fs);
  return(fsname);
#else
  return(NULL);
#endif
}
#else
static char *
getmntpt(char *name, struct stat *statbuf)
{

  return(NULL);
}
#endif

static char *
fs(char *filename, struct stat *statbuf)
{
  char *p, *mntonname;
  static char *hostname = NULL;

  if (hostname == NULL) {
	hostname = (char *) malloc(MAXHOSTNAMELEN);
	if (gethostname(hostname, MAXHOSTNAMELEN) == -1)
	  return(NULL);
	if ((p = index(hostname, '.')) != NULL)
	  *p = '\0';
  }

  /*
   * If 'file' is on a remote file system then f_mntonname will be
   * of the form "host:file", oth. it will look like "/dev/..."
   */
  if ((mntonname = getmntpt(filename, statbuf)) != NULL) { 
	if ((p = index(mntonname, ':')) == NULL)
	  return(hostname);
	else {
	  *p = '\0';
	  if ((p = index(mntonname, '.')) != NULL)
		*p = '\0';
	  return(mntonname);
	}
  }

  return(NULL);
}

int
file_func(int argc, char **argv, char **result)
{
  int i, st;

  for (i = 0; command[i].name != NULL; i++) {
	if (streq(command[i].name, argv[0])) {
	  st = command[i].func(argc, argv, result);
	  return(st);
	}
  }

  return(-1);
}

#ifdef PROG

static void
usage(void)
{

  fprintf(stderr, "Usage: expr_file cmd\n");
  fprintf(stderr, "where cmd is one of the following commands:\n");
  fprintf(stderr, "  basename string\n");
  fprintf(stderr, "  chmod abs-mode file\n");
  fprintf(stderr, "  dirname string [suffix]\n");
  fprintf(stderr, "  extension string\n");
  fprintf(stderr, "  lstat fmt file\n");
  fprintf(stderr, "  mkdir dir [mode]\n");
  fprintf(stderr, "  readlink file\n");
  fprintf(stderr, "  remove file\n");
  fprintf(stderr, "  rename source-file target-file\n");
  fprintf(stderr, "  rmdir dir\n");
  fprintf(stderr, "  stat fmt file\n");
  fprintf(stderr, "  test op arg [...]\n");
  fprintf(stderr, "  touch file [mode]\n");

  fprintf(stderr, "For stat/lstat, fmt is like the first arg to printf:\n");
  fprintf(stderr, "%%d st_dev\n");
  fprintf(stderr, "%%i st_ino\n");
  fprintf(stderr, "%%m st_mode in octal\n");
  fprintf(stderr, "%%M st_mode permissions as text\n");
  fprintf(stderr, "%%l st_nlink\n");
  fprintf(stderr, "%%u st_uid in decimal\n");
  fprintf(stderr, "%%U st_uid in text\n");
  fprintf(stderr, "%%g st_gid in decimal\n");
  fprintf(stderr, "%%G st_gid in text\n");
  fprintf(stderr, "%%r st_rdev\n");
  fprintf(stderr, "%%s st_size\n");
  fprintf(stderr, "%%b st_blksize\n");
  fprintf(stderr, "%%n echo the file argument\n");
  fprintf(stderr, "%%N if the argument is a symbolic link, print the\n");
  fprintf(stderr, "   contents of the link; otherwise print the file argument\n");
  fprintf(stderr, "%%t prefixes a time request:\n");
  fprintf(stderr, "   a st_atime in decimal\n");
  fprintf(stderr, "   A st_atime in text\n");
  fprintf(stderr, "   m st_mtime in decimal\n");
  fprintf(stderr, "   M st_mtime in text\n");
  fprintf(stderr, "   c st_ctime in decimal\n");
  fprintf(stderr, "   C st_ctime in text\n");
  fprintf(stderr, "%%f fileserver for the file\n");
  fprintf(stderr, "%%%% print a %%\n");
  fprintf(stderr, "\\n print a newline\n");
  fprintf(stderr, "\\t print a tab\n");
  fprintf(stderr, "\\\\ print a \\\n");

  exit(1);
}

int
main(int argc, char **argv)
{
  int st;
  char *result;

  if (argc < 3) {
	usage();
	/*NOTREACHED*/
  }

  result = NULL;
  if ((st = file_func(argc - 1, argv + 1, &result)) == -1)
	exit(1);

  if (result != NULL)
	printf("%s\n", result);

  exit(st);
}

#endif
