/* Filesystem related system calls
   Copyright (C) 1991 Free Software Foundation

This file is part of the GNU Hurd.

The GNU Hurd is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 1, or (at your option)
any later version.

The GNU Hurd is distributed in the hope that it will be useful, 
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with the GNU Hurd; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* Written by Michael I. Bushnell.  */

#include "mach.h"
#include <cthreads.h>
#include <gnu/types.h>
#include <gnu/param.h>
#include <gnu/errno.h>
#include <gnu/file.h>
#include <gnu/stat.h>
#include <gnu/signal.h>
#include <hurd/hurd_types.h>

#include "io.h"
#include "auth.h"
#include "fs.h"

#include "alix.h"
#include "alix_io.h"
#include "alix_auth.h"
#include <machine/alix_machdep.h>

/* Missing: fchdir, fchroot, fchcore, getfsstat, access, chflags, 
            fchflags, revoke?, mkfifo, getdirentries, statfs, fstatfs */

/* Take a pathname and translate it.  Start at crdir or cwdir as
   appropriate.  Do the lookup with the specified flags and creation
   mode.  If no error, set *ret to a port to the opened file.  */
int
path_lookup (char *path,
	     int flags,
	     int mode,
	     file_t *ret)
{
  file_t startdir;
  int err;
  enum retry_type doretry;
  char retryname[MAXPATHLEN];
  u_int retrynamelen;
  file_t result, newpt;
  u_int pathlen;
  int dealloc_dir;
  int nloops;

  if (*path == '/')
    {
      startdir = crdir;
      while (*path == '/')
	path++;
    }
  else
    startdir = cwdir;
  dealloc_dir = 0;
  nloops = 0;
  pathlen = strlen (path) + 1;
  
  for (;;)
    {
      err = dir_pathtrans (startdir, path, pathlen, flags, mode,
			   &doretry, retryname, &retrynamelen, &result);

      if (dealloc_dir)
	mach_port_deallocate (mach_task_self (), startdir);
      if (err)
	return h2ae (err);

      switch (doretry)
	{
	case FS_RETRY_NONE:
	  *ret = result;
	  return 0;
	  
	case FS_RETRY_REAUTH:
	  io_reauthenticate (result);
	  auth_user_authenticate (authserver, result, &newpt);
	  mach_port_deallocate (mach_task_self (), result);
	  result = newpt;
	  /* fall through... */

	case FS_RETRY_NORMAL:
	  if (nloops++ >= MAXSYMLINKS)
	    return ELOOP;
	      
	  if (*retryname == '/')
	    {
	      startdir = crdir;
	      dealloc_dir = 0;
	      path = retryname;
	      pathlen = retrynamelen;
	      while (*path == '/')
		{
		  path++;
		  pathlen--;
		}
	    }
	  else
	    {
	      startdir = result;
	      dealloc_dir = 1;
	      path = retryname;
	      pathlen = retrynamelen;
	    }
	}
    }
}

/* Take a pathname and return a port to the directory holding the
   referenced name, and a pointer to the name.  */
int
path_split (char *path,
	    file_t *dir,
	    char **name)
{
  char *lastslash;
  
  /* Skip leading slashes in the pathname */
  if (*path == '/')
    {
      while (*path == '/')
	path++;
      path--;			/* leave on one slash */
    }
  
  lastslash = rindex (path, '/');
  
  if (lastslash)
    {
      if (lastslash == path)
	{
	  *name = path;
	  *dir = crdir;
	  mach_port_mod_refs (mach_task_self (), crdir, 
			      MACH_PORT_RIGHT_SEND, 1);
	  return 0;
	}
      else
	{
	  *lastslash = '\0';
	  *name = lastslash + 1;
	  return path_lookup (path, 0, 0, dir);
	}
    }
  else
    {
      *name = path;
      *dir = cwdir;
      mach_port_mod_refs (mach_task_self (), cwdir, MACH_PORT_RIGHT_SEND, 1);
      return 0;
    }
}

/* Change the directory pointer OLDPORT to point to NEWPORT (do all
   the necessary work for chdir and chroot, etc.  */
int
change_a_dir (file_t newport,
	      file_t *oldport)
{
  struct io_statbuf fsb;
  int err;
  
  err = h2ae (io_stat (newport, &fsb));
  if (!err && (fsb.stb_mode & S_IFMT) == S_IFDIR)
    {
      mach_port_deallocate (mach_task_self (), *oldport);
      *oldport = newport;
      return 0;
    }
  else
    {
      mach_port_deallocate (mach_task_self (), newport);
      return err ? err : ENOTDIR;
    }
}

/* Look up PATH returning a directory DIR.  */
int
look_up_dir (char *upath,
	     file_t *dir)
{
   int err;
   char path[MAXPATHLEN];

   if (err = copyinstr (upath, path))
     return err;

   return path_lookup (path, FS_LOOKUP_EXEC, 0, dir);
}

/* Turn D into a port to directory DIR.  */
int
flook_up_dir (int d,
	      file_t *dir)
{
  if (d <= 0 || d >= dtablesize || !dtable[d].ioserver)
    return EBADF;
  mach_port_mod_refs (mach_task_self (), cwdir, MACH_PORT_RIGHT_SEND, 1);

  *dir = dtable[d].ioserver;
  return 0;
}
  
/* SYSCALL: Change the current working directory (starting point for
   pathname translation where the path doesn't start with '/')
   from pathname PATH.  */
int
chdir (void *ap, int *ret1, int *ret2)
{
  struct
    {
      char *path;
    }
  *args = ap;

  file_t dir;
  int err;

  mutex_lock (&iolock);
  err = look_up_dir (args->path, &dir);
  if (!err)
    err = change_a_dir (dir, &cwdir);
  mutex_unlock (&iolock);

  return err;
}

/* SYSCALL: Change the current working directory from a file descriptor. */
int
fchdir (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int d;
    }
  *args = ap;
  
  file_t dir;
  int err;
  
  mutex_lock (&iolock);
  err = flook_up_dir (args->d, &dir);
  if (!err)
    err = change_a_dir (dir, &cwdir);
  mutex_unlock (&iolock);
  return err;
}

/* SYSCALL: Change the current root directory from a file descriptor. */
int
fchroot (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int d;
    }
  *args = ap;
  
  file_t dir;
  int err;
  
  mutex_lock (&iolock);
  err = flook_up_dir (args->d, &dir);
  if (!err)
    err = change_a_dir (dir, &crdir);
  mutex_unlock (&iolock);
  return err;
}

/* SYSCALL: Change the current core directory from a file descriptor. */
int
fchcore (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int d;
    }
  *args = ap;
  
  file_t dir;
  int err;
  
  mutex_lock (&iolock);
  err = flook_up_dir (args->d, &dir);
  if (!err)
    err = change_a_dir (dir, &ccdir);
  mutex_unlock (&iolock);
  return err;
}

/* SYSCALL: Change the current core file directory.  */
int
chcore (void *ap, int *ret1, int *ret2)
{
  struct
    {
      char *path;
    }
  *args = ap;

  file_t dir;
  int err;

  mutex_lock (&iolock);
  err = look_up_dir (args->path, &dir);
  if (!err)
    err = change_a_dir (dir, &ccdir);
  mutex_unlock (&iolock);

  return err;
}

/* SYSCALL: Change the current root directory (starting point for
   pathname translation where the path starts with '/').  */
int
chroot (void *ap, int *ret1, int *ret2)
{
  struct
    {
      char *path;
    }
  *args = ap;

  file_t dir;
  int err;

  mutex_lock (&iolock);
  err = look_up_dir (args->path, &dir);
  if (!err)
    err = change_a_dir (dir, &crdir);
  mutex_unlock (&iolock);

  return err;
}

/* SYSCALL: Set the current working directory from a port.  */
int
setcwdir (void *ap, int *ret1, int *ret2)
 {
  struct
    {
      file_t dir;
    }
  *args = ap;

  int err;

  mutex_lock (&iolock);
  mach_port_mod_refs (mach_task_self (), args->dir, MACH_PORT_RIGHT_SEND, 1);
  err = change_a_dir (args->dir, &cwdir);
  mutex_unlock (&iolock);
  return err;
}

/* SYSCALL: Set the current root directory from a port.  */
int
setcrdir (void *ap, int *ret1, int *ret2)
{
  struct
    {
      file_t dir;
    }
  *args = ap;

  int err;

  mutex_lock (&iolock);
  mach_port_mod_refs (mach_task_self (), args->dir, MACH_PORT_RIGHT_SEND, 1);
  err = change_a_dir (args->dir, &crdir);
  mutex_unlock (&iolock);
  return err;
}
 /* SYSCALL: Set the current core directory from a port.  */
int
setccdir (void *ap, int *ret1, int *ret2)
{
  struct
    {
      file_t dir;
    }
  *args = ap;

  int err;
 
  mutex_lock (&iolock);
  mach_port_mod_refs (mach_task_self (), args->dir, MACH_PORT_RIGHT_SEND, 1);
  err = change_a_dir (args->dir, &ccdir);
  mutex_unlock (&iolock);
  return err;
}

/* SYSCALL: Get the current working directory port.  */
int
getcwdir (void *ap, int *ret1, int *ret2)
{
  mutex_lock (&iolock);
  mach_port_mod_refs (mach_task_self (), cwdir, MACH_PORT_RIGHT_SEND, 1);
  *ret1 = cwdir;
  mutex_unlock (&iolock);
  return 0;
}

/* SYSCALL: Get the current root directory port.  */
int
getcrdir (void *ap, int *ret1, int *ret2)
{
  mutex_lock (&iolock);
  mach_port_mod_refs (mach_task_self (), crdir, MACH_PORT_RIGHT_SEND, 1);
  *ret1 = crdir;
  mutex_unlock (&iolock);
  return 0;
}

/* SYSCALL: Get the currect core directory port.  */
int
getccdir (void *ap, int *ret1, int *ret2)
{
  mutex_lock (&iolock);
  mach_port_mod_refs (mach_task_self (), ccdir, MACH_PORT_RIGHT_SEND, 1);
  *ret1 = ccdir;
  mutex_unlock (&iolock);
  return 0;
}

/* SYSCALL: Create a directory using dir_mkdir.  */
int
mkdir (void *ap, int *ret1, int *ret2)
{
  struct 
    {
      char *path;
      int mode;
    }
  *args = ap;
  
  int err;
  file_t dir;
  char path[MAXPATHLEN];
  char *name;
  
  if (err = copyinstr (args->path, path))
    return err;
  
  mutex_lock (&iolock);
  
  if (!(err = path_split (path, &dir, &name)))
    {
      err = h2ae (dir_mkdir (dir, name, strlen (name)+1, args->mode));
      mach_port_deallocate (mach_task_self (), dir);
    }
  
  mutex_unlock (&iolock);
  return err;
}

/* SYSCALL: Remove a directory using dir_rmdir.  */
int
rmdir (void *ap, int *ret1, int *ret2)
{
  struct 
    {
      char *path;
    }
  *args = ap;
  
  int err;
  file_t dir;
  char path[MAXPATHLEN];
  char *name;
  
  if (err = copyinstr (args->path, path))
    return err;
  
  mutex_lock (&iolock);
  
  if (!(err = path_split (path, &dir, &name)))
    {
      err = h2ae (dir_rmdir (dir, name, strlen(name)+1));
      mach_port_deallocate (mach_task_self (), dir);
    }
  
  mutex_unlock (&iolock);
  return err;
}

/* Do the actual work for an open, with specified flags and mode,
   returning a port.  */
int
open1 (char *pathp,
       int hurdflags,
       int mode,
       int *ret,
       int tryctty)
{
  int d;
  int err;
  char path[MAXPATHLEN];
  
  mutex_lock (&iolock);

  d = falloc ();
  if (d == -1)
    {
      mutex_unlock (&iolock);
      return EMFILE;
    }
  
  err = copyinstr (pathp, path);

  if (!err)
    err = path_lookup (path, hurdflags, mode, &dtable[d].ioserver);
  
  if (!err)
    *ret = d;

  if (!err && !hasctty && tryctty)
    {
      mach_port_t cttyid;
      struct iostatbuf stb;
      
      err = io_stat (dtable[d].ioserver, &stb);
      if (!err)
	err = term_getctty (dtable[d].ioserver, &cttyid);
      if (!err)
	{
	  err = proc_set_ctty (procserver, cttyid);
	  mach_port_deallocate (mach_task_self (), cttyid);
	  if (!err)
	    {
	      dtable[d].isctty = 1;
	      hasctty = 1;
	      ctty_fstype = stb.stb_fstype;
	      ctty_fsid.val[0] = stb.stb_fsid.val[0];
	      ctty_fsid.val[1] = stb.stb_fsid.val[1];
	      ctty_fileid = stb.stb_file_id;
	    }
	}
      err = 0;			/* none of these block success */
    }
  mutex_unlock (&iolock);
  return err;
}
  
/* SYSCALL: Open a file, with specified Posix flags and mode.  */
int
open (void *ap, int *ret1, int *ret2)
{
  struct 
    {
      char *path;
      int flags;
      int mode;
    }
  *args = ap;
  
  int hurdflags;
  
  switch (args->flags & O_ACCMODE)
    {
    case O_RDONLY:
      hurdflags = FS_LOOKUP_READ;
      break;
    case O_WRONLY:
      hurdflags = FS_LOOKUP_WRITE;
      break;
    case O_RDWR:
      hurdflags = FS_LOOKUP_WRITE | FS_LOOKUP_READ;
      break;
    default:
      return EINVAL;
    }
  
  hurdflags |= (args->flags & O_NDELAY) ? FS_LOOKUP_NDELAY : 0;
  hurdflags |= (args->flags & O_APPEND) ? FS_LOOKUP_APPEND : 0;
  hurdflags |= (args->flags & O_CREAT) ? FS_LOOKUP_CREATE : 0;
  hurdflags |= (args->flags & O_TRUNC) ? FS_LOOKUP_TRUNCATE : 0;
  hurdflags |= (args->flags & O_EXCL) ? FS_LOOKUP_EXCL : 0;
  
  return open1 (args->path, hurdflags, args->mode, ret1,
		!(args->flags & O_NOCTTY));
}

#ifdef COMPAT_43
/* SYSCALL: Create a new file.  */
int
creat_compat43 (void *ap, int *ret1, int *ret2)
{
  struct
    {
      char *path;
      int mode;
    }
  *args = ap;
  
  return open1 (args->path, 
		FS_LOOKUP_WRITE | FS_LOOKUP_CREATE | FS_LOOKUP_TRUNCATE,
		args->mode, ret1, 1);
}
#endif

#ifdef COMPAT_44
/* SYSCALL: Make a special file.  */
int
mknod_compat44 (void *ap, int *ret1, int *ret2)
{
  struct 
    {
      char *path;
      int mode;
      int dev;
    }
  *args = ap;
  
  int err;
  file_t dir;
  char path[MAXPATHLEN];
  char *name;
  file_t node;
  char *translator;

  if (err = copyinstr (args->path, path))
    return err;
  
  
  switch (mode & S_IFMT)
    {
    case S_IFBLK:
      translator = "/hurd/blkdev";
      break;

    case S_IFCHR:
      translator = "/hurd/chrdev";
      break;
      
    case S_IFIFO:
      translator = "/hurd/fifo";
      break;

    default:
      return EINVAL;
    }

  mutex_lock (&iolock);
  
  err = path_lookup (path, FS_LOOKUP_CREAT | FS_LOOKUP_EXCL, 
		     ap->mode & 0666, &node);
  if (err)
    {
      mutex_unlock (&iolock);
      return err;
    }

  mach_port_deallocate (mach_task_self (), node);
  
  if (!(err = path_split (path, &dir, &name)))
    {
      err = h2ae (dir_set_translator (dir, name, FS_TRANS_EXCL, 0,
				      translator, MACH_PORT_NULL);
      mach_port_deallocate (mach_task_self (), dir);
    }
  
  mutex_unlock (&iolock);
  return err;
}
#endif

/* SYSCALL: Remove a non-directory using dir_unlink.  */
int
unlink (void *ap, int *ret1, int *ret2)
{
  struct 
    {
      char *path;
    }
  *args = ap;
  
  int err;
  file_t dir;
  char path[MAXPATHLEN];
  char *name;
  
  if (err = copyinstr (args->path, path))
    return err;
  
  mutex_lock (&iolock);
  
  if (!(err = path_split (path, &dir, &name)))
    {
      err = h2ae (dir_unlink (dir, name, strlen(name)+1));
      mach_port_deallocate (mach_task_self (), dir);
    }
  
  mutex_unlock (&iolock);
  return err;
}

#ifdef COMPAT_44
/* Copy information from a hurd fs_statbuf to a BSD struct ostat.  */
void
ostbufcopy (struct io_statbuf *fsb,
	    struct ostat *stb)
{
  stb->st_dev = 0;		/* XXX */
  stb->st_ino = fsb->stb_file_id;
  stb->st_mode = fsb->stb_mode;
  stb->st_nlink = fsb->stb_nlink;
  stb->st_uid = fsb->stb_uid;
  stb->st_gid = fsb->stb_gid;
  stb->st_rdev = fsb->stb_rdev;
  stb->st_size = fsb->stb_size;
  stb->st_atime = fsb->stb_atime.seconds;
  stb->st_atime_usec = fsb->stb_atime.microseconds;
  stb->st_mtime = fsb->stb_mtime.seconds;
  stb->st_mtime_usec = fsb->stb_mtime.microseconds;
  stb->st_ctime = fsb->stb_ctime.seconds;
  stb->st_ctime_usec = fsb->stb_ctime.microseconds;
  stb->st_blksize = fsb->stb_blksize;
  stb->st_blocks = fsb->stb_blocks;
}  
#endif

/* Copy information from a hurd fs_statbuf to a Posix struct stat.  */
void
stbufcopy (struct io_statbuf *fsb,
	   struct stat *stb)
{
  stb->st_fstype = fsb->stb_fstype;
  stb->st_fsid = fsb->stb_fsid;
  stb->st_fileid = fsb->stb_file_id;
  stb->st_genid = fsb->stb_generation_id;
  stb->st_mode = fsb->stb_mode;
  stb->st_nlink = fsb->stb_nlink;
  stb->st_uid = fsb->stb_uid;
  stb->st_gid = fsb->stb_gid;
  stb->st_size = fsb->stb_size;
  stb->st_rdev = fsb->stb_rdev;
  stb->st_atime = fsb->stb_atime.seconds;
  stb->st_atime_usec = fsb->stb_atime.microseconds;
  stb->st_ctime = fsb->stb_ctime.seconds;
  stb->st_ctime_usec = fsb->stb_ctime.microseconds;
  stb->st_mtime = fsb->stb_mtime.seconds;
  stb->st_mtime_usec = fsb->stb_mtime.microseconds;
  stb->st_blksize = fsb->stb_blksize;
  stb->st_blocks = fsb->stb_blocks;
  stb->st_author = fsb->stb_author;
}


/* SYSCALL: Get valuable information about a file using io_stat.  */
int
#ifdef COMPAT_44
stat1 (void *ap, int *ret1, int *ret2, int compat44)
#else
stat (void *ap, int *ret1, int *ret2)
#endif
{
  struct 
    {
      char *path;
      struct stat *stbp;
    }
  *args = ap;
  
  int err;
  file_t file;
  char path[MAXPATHLEN];
  struct io_statbuf fsb;
  
  if (err = copyinstr (args->path, path))
    return err;
  
  mutex_lock (&iolock);
  
  if (!(err = path_lookup (path, 0, 0, &file)))
    {
      mutex_unlock (&iolock);
      if (!(err = h2ae (io_stat (file, &fsb))))
	{
#ifdef COMPAT_44
	  if (compat44)
	    {
	      struct ostat ostb;
	      ostbufcopy (&fsb, &ostb);
	      err = copyout (&ostb, args->stbp, sizeof (struct ostat));
	    }
	  else
#endif
	    {
	      struct stat stb;
	      stbufcopy (&fsb, &stb);
	      err = copyout (&stb, args->stbp, sizeof (struct stat));
	    }
	}
      mach_port_deallocate (mach_task_self (), file);
    }
  else
    mutex_unlock (&iolock);

  return err;
}

#ifdef COMPAT_44
int 
stat (void *ap, int *ret1, int *ret2)
{
  return stat1 (ap, ret1, ret2, 0);
}

int
stat_compat44 (void *ap, int *ret1, int *ret2)
{
  return stat1 (ap, ret1, ret2, 1);
}
#endif


/* SYSCALL: Get info about a file like stat, but don't follow a
   symlink in the last pathname component.  Uses dir_lstat.  */
int
#ifdef COMPAT_44
lstat1 (void *ap, int *ret1, int *ret2, int compat44)
#else
lstat (void *ap, int *ret1, int *ret2)
#endif
{
  struct 
    {
      char *path;
      struct stat *stbp;
    }
  *args = ap;
  
  int err;
  file_t dir;
  char path[MAXPATHLEN];
  char *name;
  struct io_statbuf fsb;
  
  if (err = copyinstr (args->path, path))
    return err;
  
  mutex_lock (&iolock);
  
  if (!(err = path_split (path, &dir, &name)))
    {
      if (!(err = h2ae (dir_lstat (dir, name, strlen (name)+1, &fsb))))
	{
#ifdef COMPAT_44
	  if (compat44)
	    {
	      struct ostat ostb;
	      ostbufcopy (&fsb, &ostb);
	      err = copyout (&ostb, args->stbp, sizeof (struct ostat));
	    }
	  else
#endif
	    {
	      struct stat stb;
	      stbufcopy (&fsb, &stb);
	      err = copyout (&stb, args->stbp, sizeof (struct stat));
	    }
	}
      mach_port_deallocate (mach_task_self (), dir);
    }
  
  mutex_unlock (&iolock);
  return err;
}

#ifdef COMPAT_44
int
lstat (void *ap, int *ret1, int *ret2)
{
  return lstat1 (ap, ret1, ret2, 0);
}

int
lstat_compat44 (void *ap, int *ret1, int *ret2)
{
  return lstat1 (ap, ret1, ret2, 1);
}
#endif


/* SYSCALL: Get valuable information from an open file descriptor
   using io_stat.  */
int
#ifdef COMPAT_44
fstat1 (void *ap, int *ret1, int *ret2, int compat44)
#else
fstat (void *ap, int *ret1, int *ret2)
#endif
{
  struct
    {
      int fd;
      struct stat *stbp;
    }
  *args = ap;
  
  struct io_statbuf fsb;
  int err;
  
  mutex_lock (&iolock);
  
  if (args->fd < 0 || args->fd >= dtablesize || !dtable[args->fd].ioserver)
    {
      mutex_unlock (&iolock);
      return EBADF;
    }
  
  if (!(err = h2ae (io_stat (dtable[args->fd].ioserver, &fsb))))
    {
#ifdef COMPAT_44
      if (compat44)
	{
	  struct ostat ostb;
	  ostbufcopy (&fsb, &ostb);
	  err = copyout (&ostb, args->stbp, sizeof (struct ostat));
	}
      else
#endif
	{
	  struct stat stb;
	  stbufcopy (&fsb, &stb);
	  err = copyout (&stb, args->stbp, sizeof (struct stat));
	}
    }
  
  return err;
}

#ifdef COMPAT_44
int
fstat (void *ap, int *ret1, int *ret2)
{
  return fstat1 (ap, ret1, ret2, 0);
}

int
fstat_compat44 (void *ap, int *ret1, int *ret2)
{
  return fstat1 (ap, ret1, ret2, 1);
}
#endif

/* SYSCALL: Change the owner of a file using file_chown, given a path.  */
int
chown (void *ap, int *ret1, int *ret2)
{
  struct 
    {
      char *path;
      uid_t owner;
      gid_t group;
    }
  *args = ap;
  
  file_t file;
  int err;
  char path[MAXPATHLEN];
  
  if (err = copyinstr (args->path, path))
    return err;
  
  if (err = path_lookup (path, 0, 0, &file))
    return err;
  
  err = h2ae (file_chown (file, args->owner, args->group));

  mach_port_deallocate (mach_task_self (), file);
  return err;
}

/* SYSCALL: Change the owner of a file using file_chown, given a file
   descriptor.  */
int
fchown (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int fd;
      uid_t owner;
      gid_t group;
    }
  *args = ap;
  
  int err;

  mutex_lock (&iolock);
  
  if (args->fd < 0 || args->fd >= dtablesize || !(dtable[args->fd].ioserver))
    {
      mutex_unlock (&iolock);
      return EBADF;
    }
  
  err = h2ae (file_chown (dtable[args->fd].ioserver, args->owner,
			  args->group));

  mutex_unlock (&iolock);
  return err;
}

/* SYSCALL: Change the file access bits on a file, using file_chmod,
   given a path.  */
int
chmod (void *ap, int *ret1, int *ret2)
{
  struct 
    {
      char *path;
      mode_t mode;
    }
  *args = ap;
  
  file_t file;
  int err;
  char path[MAXPATHLEN];
  
  if (err = copyinstr (args->path, path))
    return err;
  
  if (err = path_lookup (path, 0, 0, &file))
    return err;
  
  err = h2ae (file_chmod (file, args->mode));

  mach_port_deallocate (mach_task_self (), file);
  return err;
}

/* SYSCALL: Change the file access bits on a file, using file_chmod,
   given a file descriptor.  */
int
fchmod (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int fd;
      mode_t mode;
    }
  *args = ap;
  
  int err;

  mutex_lock (&iolock);
  
  if (args->fd < 0 || args->fd >= dtablesize || !(dtable[args->fd].ioserver))
    {
      mutex_unlock (&iolock);
      return EBADF;
    }
  
  err = h2ae (file_chmod (dtable[args->fd].ioserver, args->mode));

  mutex_unlock (&iolock);
  return err;
}

/* SYSCALL: Change the file flags for a file, using file_chmod,
   given a path.  */
int
chflags (void *ap, int *ret1, int *ret2)
{
  struct 
    {
      char *path;
      long flags;
    }
  *args = ap;
  
  file_t file;
  int err;
  char path[MAXPATHLEN];
  
  if (err = copyinstr (args->path, path))
    return err;
  
  if (err = path_lookup (path, 0, 0, &file))
    return err;
  
  err = h2ae (file_chflags (file, args->flags));

  mach_port_deallocate (mach_task_self (), file);
  return err;
}

/* SYSCALL: Change the file flags for a file, using file_chmod,
   given a file descriptor.  */
int
fchflags (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int fd;
      long flags;
    }
  *args = ap;
  
  int err;

  mutex_lock (&iolock);
  
  if (args->fd < 0 || args->fd >= dtablesize || !(dtable[args->fd].ioserver))
    {
      mutex_unlock (&iolock);
      return EBADF;
    }
  
  err = h2ae (file_chflags (dtable[args->fd].ioserver, args->flags));

  mutex_unlock (&iolock);
  return err;
}

/* SYSCALL: Change the access and modify times on a file using
   file_utimes.  */
int
utimes (void *ap, int *ret1, int *ret2)
{
  struct 
    {
      char *path;
      struct time_value_t *times;
    }
  *args = ap;
  
  file_t file;
  int err;
  char path[MAXPATHLEN];
  struct time_value times[2];
  
  if (err = copyin (args->times, times, sizeof (struct time_value) * 2))
    return err;

  if (err = copyinstr (args->path, path))
    return err;
  
  if (err = path_lookup (path, 0, 0, &file))
    return err;
  
  err = h2ae (file_utimes (file, times[0], times[1]));

  mach_port_deallocate (mach_task_self (), file);
  return err;
}

/* SYSCALL: Create another link to an existing file using dir_link.  */
int
link (void *ap, int *ret1, int *ret2)
{
  struct 
    {
      char *from;
      char *to;
    }
  *args = ap;
  
  char from[MAXPATHLEN];
  char to[MAXPATHLEN];
  int err;
  
  file_t fromfile;
  file_t todir;
  char *toname;
  
  if (err = copyinstr (args->from, from))
    return err;
  if (err = copyinstr (args->to, to))
    return err;
  
  if (err = path_lookup (from, 0, 0, &fromfile))
    return err;
  if (err = path_split (to, &todir, &toname))
    {
      mach_port_deallocate (mach_task_self (), fromfile);
      return err;
    }
  
  err = h2ae (dir_link (fromfile, todir, toname, strlen (toname)+1));

  mach_port_deallocate (mach_task_self (), fromfile);
  mach_port_deallocate (mach_task_self (), todir);
  return err;
}

/* SYSCALL: Create a symbolic link using dir_symlink.  */
int
symlink (void *ap, int *ret1, int *ret2)
{
  struct 
    {
      char *from;
      char *to;
    }
  *args = ap;
  
  char from[MAXPATHLEN];
  char to[MAXPATHLEN];
  int err;
  
  file_t todir;
  char *toname;
  
  if (err = copyinstr (args->from, from))
    return err;
  if (err = copyinstr (args->to, to))
    return err;
  
  if (err = path_split (to, &todir, &toname))
    return err;
  
  err = h2ae (dir_symlink (todir, toname, strlen (toname) + 1,
			   from, strlen (from) + 1));
  
  mach_port_deallocate (mach_task_self (), todir);
  return err;
}

/* SYSCALL: Read the target pathname of a symbolic link using
   dir_readlink.  */
int
readlink (void *ap, int *ret1, int *ret2)
{
  struct
    {
      char *path;
      char *buf;
      u_int buflen;
    }
  *args = ap;
  
  int err;
  file_t dir;
  char *name;
  char path[MAXPATHLEN];
  u_int len;
  
  if (err = copyinstr (args->path, path))
    return err;
  
  if (err = path_split (path, &dir, &name))
    return err;
  
  err = h2ae (dir_readlink (dir, name, strlen (name)+1, path, &len));
  mach_port_deallocate (mach_task_self (), dir);

  if (!err)
    {
      if (len > args->buflen)
	len = args->buflen;
      if (!(err = copyout (path, args->buf, len)))
	*ret1 = len;
    }
  
  return err;
}

/* SYSCALL: Semi-atomically change the name of a file or directory
   using dir_rename.  */
int
rename (void *ap, int *ret1, int *ret2)
{
  struct
    {
      char *from;
      char *to;
    }
  *args = ap;

  char to[MAXPATHLEN];
  char from[MAXPATHLEN];
  
  file_t fromdir;
  file_t todir;
  char *fromname;
  char *toname;

  int err;
  
  if (err = copyinstr (args->from, from))
    return err;
  if (err = copyinstr (args->to, to))
    return err;
  
  if (err = path_split (from, &fromdir, &fromname))
    return err;
  if (err = path_split (to, &todir, &toname))
    {
      mach_port_deallocate (mach_task_self (), fromdir);
      return err;
    }
  
  err = h2ae (dir_rename (fromdir, fromname, strlen (fromname) + 1, 
			  todir, toname, strlen (toname) + 1));
  
  mach_port_deallocate (mach_task_self (), fromdir);
  mach_port_deallocate (mach_task_self (), todir);
  return err;
}

/* SYSCALL: Change the current access offset for a file, using
   file_seek.  */
int
lseek (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int fd;
      off_t offset;
      int type;
    }
  *args = ap;
  
  int err;
  
  mutex_lock (&iolock);
  
  if (args->fd < 0 || args->fd >= dtablesize || !dtable[args->fd].ioserver)
    {
      mutex_unlock (&iolock);
      return EBADF;
    }
  
  err = h2ae (file_seek (dtable[args->fd].ioserver, args->offset, 
			 args->type, ret1));

  mutex_unlock (&iolock);
  return err;
}

/* SYSCALL: Shorten a file, given a pathname, using file_truncate.  */
int
truncate (void *ap, int *ret1, int *ret2)
{
  struct 
    {
      char *path;
      int length;
    }
  *args = ap;
  
  file_t file;
  int err;
  char path[MAXPATHLEN];
  
  if (err = copyinstr (args->path, path))
    return err;
  
  if (err = path_lookup (path, 0, 0, &file))
    return err;
  
  err = h2ae (file_truncate (file, args->length));

  mach_port_deallocate (mach_task_self (), file);
  return err;
}

/* SYSCALL: Shorten a file, given a file descriptor, using
   file_truncate.  */
int
ftruncate (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int fd;
      int length;
    }
  *args = ap;
  
  int err;

  mutex_lock (&iolock);
  
  if (args->fd < 0 || args->fd >= dtablesize || !(dtable[args->fd].ioserver))
    {
      mutex_unlock (&iolock);
      return EBADF;
    }
  
  err = h2ae (file_truncate (dtable[args->fd].ioserver, args->length));

  mutex_unlock (&iolock);
  return err;
}

/* SYSCALL: Apply an advisory lock to a file using file_lock.  */
int
flock (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int fd;
      int how;
    }
  *args = ap;
  
  int err;
  
  mutex_lock (&iolock);
  if (args->fd < 0 || args->fd >= dtablesize || !dtable[args->fd].ioserver)
    {
      mutex_unlock (&iolock);
      return EBADF;
    }
  
  err = h2ae (file_lock (dtable[args->fd].ioserver, args->how));
  
  mutex_unlock (&iolock);
  return err;
}

