/* Cheapo /dev translator; does /dev/tty, /dev/console, /dev/null, /dev/fd.
   Copyright (C) 1994 Free Software Foundation

   This program 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 2, or (at
   your option) any later version.

   This program 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 this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */

#include <hurd.h>
#include <fcntl.h>
#include <hurd/ports.h>
#include <hurd/fshelp.h>
#include <stdio.h>
#include <hurd/fsys.h>
#include <string.h>
#include <dirent.h>
#include <hurd/paths.h>
#include <device/device.h>
#include <assert.h>

#include "fs_S.h"
#include "io_S.h"
#include "fsys_S.h"
#include "notify_S.h"

struct port_info *fs_control;
struct port_info *null_port;
struct port_info *dir_port;
struct port_info *console_port;
struct port_info *fddir_port;
struct port_info *stdin_port;
struct port_info *stdout_port;
struct port_info *stderr_port;
struct port_info *tty_port;

struct trans_link console_link;
char console_trans[] = "/hurd/term\0console";

mach_port_t dotdot;

mach_port_t authserver;

mach_port_t underlying;

#define PT_CTL 0
#define PT_NULL 1
#define PT_DIR 2
#define PT_CONSOLE 3
#define PT_TRANSBOOT 4
#define PT_FDDIR 5
#define PT_STDIN 6
#define PT_STDOUT 7
#define PT_STDERR 8
#define PT_TTY 9
#define PT_DISKMIN 10
/* All following numbers reserved for disks. */

int fshelp_transboot_port_type = PT_TRANSBOOT;

#ifndef DISKNAME
#error DISKNAME not set
#endif


char *disknames[] = 
{
  DISKNAME,
  "sd0a",
  "sd0b",
  "sd0c",
  "sd0d",
  "sd0e",
  "sd0f",
  "sd0g",
  "sd0h",
  "fd0", "fd1",
};
#define NDISKS (sizeof disknames/sizeof disknames[0])

struct diskspec
{
  struct port_info *port;
  off_t offset;
  device_t device;
  off_t size;
  int blksize;
} disks[NDISKS];


/* Functions for disk */
void
init_disk_vars ()
{
  mach_port_t host_priv, master_dev;
  error_t err;
  int sizes[DEV_GET_SIZE_COUNT];
  u_int sizescnt = DEV_GET_SIZE_COUNT;
  int i;

  err = get_privileged_ports (&host_priv, &master_dev);
  assert (!err);

  for (i = 0; i < NDISKS; i++)
    {
      disks[i].offset = 0;
  
      err = device_open (master_dev, D_WRITE|D_READ, disknames[i],
			 &disks[i].device);
      if (err)
	{
	  disks[0].device = MACH_PORT_NULL;
	  continue;
	}
      
      err = device_get_status (disks[i].device, DEV_GET_SIZE,
			       sizes, &sizescnt);
      assert (!err && sizescnt == DEV_GET_SIZE_COUNT);
      disks[i].size = sizes[DEV_GET_SIZE_DEVICE_SIZE];
      disks[i].blksize = sizes[DEV_GET_SIZE_RECORD_SIZE];

      disks[i].port = ports_allocate_port (sizeof (struct port_info), 
					   PT_DISKMIN + i);
      ports_port_ref (disks[i].port);
    }
}

error_t
raw_disk_write (int idx,
		char *data, 
		u_int datalen,
		off_t offset,
		int *amount)
{
  error_t err = 0;
  vm_address_t newbuf = 0;
  vm_size_t newbuflen = 0;
  
  if (disks[idx].device == MACH_PORT_NULL)
    return ENXIO;

  if (offset % disks[idx].blksize || datalen % disks[idx].blksize)
    return EIO;
  
  if ((vm_address_t)data % vm_page_size)
    {
      /* Copy the data into a page-aligned buffer. */
      newbuflen = round_page (datalen);
      vm_allocate (mach_task_self (), &newbuf, newbuflen, 1);
      bcopy (data, (char *) newbuf, datalen);
      data = (char *) newbuf;
    }

  /* Write all the even pages */
  if (trunc_page (datalen))
    {
      err = device_write (disks[idx].device, 0, offset / disks[idx].blksize,
			  data, trunc_page (datalen), amount);
      if (err)
	goto out;
    }
  else
    *amount = 0;
  
  offset += trunc_page (datalen);
  data += trunc_page (datalen);
  datalen -= trunc_page (datalen);

  /* Write out the last bit */
  if (datalen)
    {
      char *diskpage;
      u_int diskpagelen = vm_page_size;
      off_t pageoffset;
      
      pageoffset = offset;
      if (pageoffset + vm_page_size > disks[idx].size)
	pageoffset = disks[idx].size - vm_page_size;
	
      /* Read in the page holding this data */
      err = device_read (disks[idx].device, 0, pageoffset / disks[idx].blksize,
			 vm_page_size, &diskpage, &diskpagelen);
      if (!err && diskpagelen != vm_page_size)
	err = EIO;
      if (err)
	goto out;
      
      /* Copy the last bit */
      bcopy (data, diskpage + (offset - pageoffset), datalen);
      
      /* Write it out */
      err = device_write (disks[idx].device, 0,
			  pageoffset / disks[idx].blksize,
			  diskpage, vm_page_size, &diskpagelen);
      if (!err && diskpagelen != vm_page_size)
	err = EIO;
      if (err)
	goto out;
      
      *amount += datalen;
    }
  
 out:
  if (newbuf)
    vm_deallocate (mach_task_self (), newbuf, newbuflen);
  return err;
}

error_t
raw_disk_read (int idx,
	       char **datap,
	       u_int *datalen,
	       off_t offset,
	       int amount)
{
  if (disks[idx].device == MACH_PORT_NULL)
    return ENXIO;

  if (offset % disks[idx].blksize || amount % disks[idx].blksize)
    return EIO;
  
  return device_read (disks[idx].device, 0, offset / disks[idx].blksize,
		      amount, datap, datalen);
}
/* End disk special functions */
      
int
main(int argc, 
     char **argv)
{
  file_t dir;
  char *foo;
  mach_port_t bootstrap;
  
  _libports_initialize ();	/* XXX */

  task_get_bootstrap_port (mach_task_self (), &bootstrap);
  
  fs_control = ports_allocate_port (sizeof (struct port_info), PT_CTL);
  null_port = ports_allocate_port (sizeof (struct port_info), PT_NULL);
  dir_port = ports_allocate_port (sizeof (struct port_info), PT_DIR);
  console_port = ports_allocate_port (sizeof (struct port_info), PT_CONSOLE);
  fddir_port = ports_allocate_port (sizeof (struct port_info), PT_FDDIR);
  stdin_port = ports_allocate_port (sizeof (struct port_info), PT_STDIN);
  stdout_port = ports_allocate_port (sizeof (struct port_info), PT_STDOUT);
  stderr_port = ports_allocate_port (sizeof (struct port_info), PT_STDERR);
  tty_port = ports_allocate_port (sizeof (struct port_info), PT_TTY);

  ports_port_ref (fs_control);
  ports_port_ref (null_port);
  ports_port_ref (dir_port);
  ports_port_ref (console_port);
  ports_port_ref (fddir_port);
  ports_port_ref (stdin_port);
  ports_port_ref (stdout_port);
  ports_port_ref (stderr_port);
  ports_port_ref (tty_port);

  if (bootstrap == MACH_PORT_NULL)
    {
      if (argc != 2)
	{
	  fprintf (stderr, "Usage: %s device-dir\n", argv[0]);
	  exit (1);
	}

      underlying = file_name_lookup (argv[1], O_CREAT|O_NOTRANS, 0666);
      if (underlying == MACH_PORT_NULL)
	{
	  perror (argv[1]);
	  exit (1);
	}

      dir = file_name_split (argv[1], &foo);
      if (dir == MACH_PORT_NULL)
	{
	  perror (argv[1]);
	  exit (1);
	}
  
      errno = io_restrict_auth (dir, &dotdot, 0, 0, 0, 0);
      if (errno)
	{
	  perror (argv[1]);
	  exit (1);
	}
      mach_port_deallocate (mach_task_self (), dir);

      errno = file_set_translator (underlying, 0, FS_TRANS_SET|FS_TRANS_EXCL, 
				   0, 0, 0,
				   ports_get_right (fs_control),
				   MACH_MSG_TYPE_MAKE_SEND);
      if (errno)
	{
	  perror (argv[1]);
	  exit (1);
	}
    }
  else
    {
      if (argc != 1)
	{
	  fprintf (stderr, "Translator usage: %s\n", argv[0]);
	  exit (1);
	}
      
      errno = fsys_startup (bootstrap, ports_get_right (fs_control),
			    MACH_MSG_TYPE_MAKE_SEND, &underlying);
      if (errno)
	{
	  perror ("Starting translator");
	  exit (1);
	}
      dotdot = MACH_PORT_NULL;	/* XXX */
    }

  authserver = getauth ();

  fshelp_init_trans_link (&console_link);
  
  init_disk_vars ();

  ports_manage_port_operations_multithread ();
  return 0;
}

int
ports_demuxer (mach_msg_header_t *inp, mach_msg_header_t *outp)
{
  extern int fsys_server (mach_msg_header_t *, mach_msg_header_t *);
  extern int fs_server (mach_msg_header_t *, mach_msg_header_t *);
  extern int io_server (mach_msg_header_t *, mach_msg_header_t *);
  extern int notify_server (mach_msg_header_t *, mach_msg_header_t *);

  return (fsys_server (inp, outp)
	  || fs_server (inp, outp)
	  || io_server (inp, outp)
	  || notify_server (inp, outp));
}

void (*ports_cleanroutines[])(void *) =
{
  [PT_TRANSBOOT] = fshelp_transboot_clean,
};
				
void
ports_notice_idle (int h, int s)
{
}

void 
ports_no_live_ports ()
{
}

void
ports_no_hard_ports ()
{
}

kern_return_t
S_fsys_startup (mach_port_t port,
		mach_port_t control,
		mach_port_t *realnode,
		mach_msg_type_name_t *realnodetype)
{
  struct port_info *pi = ports_check_port_type (port, PT_TRANSBOOT);
  error_t err;

  err = fshelp_handle_fsys_startup (pi, control, realnode, realnodetype);
  ports_done_with_port (pi);
  return err;
}

kern_return_t
S_fsys_goaway (mach_port_t port,
	       int flags)
{
  return EOPNOTSUPP;
}

kern_return_t
S_fsys_getroot (mach_port_t port,
		mach_port_t dd,
		uid_t *uids,
		u_int nuids,
		uid_t *gids,
		u_int ngids,
		int flags,
		retry_type *retry,
		char *retry_name,
		mach_port_t *retrypt,
		mach_msg_type_name_t *retrypttype)
{
  struct port_info *pi = ports_check_port_type (port, PT_CTL);
  if (!pi)
    return EOPNOTSUPP;
  ports_done_with_port (pi);

  /* XXX */
  if (dotdot == MACH_PORT_NULL)
    dotdot = dd;
  else
    mach_port_deallocate (mach_task_self (), dd);
  
  *retry = FS_RETRY_NORMAL;
  *retry_name = '\0';
  *retrypt = ports_get_right (dir_port);
  *retrypttype = MACH_MSG_TYPE_MAKE_SEND;
  return 0;
}

kern_return_t
S_fsys_syncfs (mach_port_t port,
	       int wait,
	       int recurse)
{
  return recurse ? S_file_syncfs (0, wait, 1) : 0;
}

kern_return_t
S_fsys_mod_readonly (mach_port_t port,
		     int val,
		     int force)
{
  return EOPNOTSUPP;
}

kern_return_t
S_fsys_getfile (mach_port_t port,
		uid_t *uids,
		u_int nuids,
		uid_t *gids,
		u_int ngids,
		char *handle,
		u_int handlelen,
		mach_port_t *pt,
		mach_msg_type_name_t *pttype)
{
  return EOPNOTSUPP;
}

kern_return_t
S_fsys_getpriv (mach_port_t port,
		mach_port_t *host,
		mach_port_t *device,
		mach_port_t *task)
{
  return EOPNOTSUPP;
}

kern_return_t
S_fsys_init (mach_port_t port,
	     mach_port_t reply,
	     mach_msg_type_name_t replytype,
	     mach_port_t proc,
	     mach_port_t auth)
{
  return EOPNOTSUPP;
}

kern_return_t
do_mach_notify_no_senders (mach_port_t notify,
			   mach_port_mscount_t mscount)
{
  struct port_info *pi = ports_get_port (notify);
  if (!pi)
    return EOPNOTSUPP;

  ports_no_senders (pi, mscount);
  ports_done_with_port (pi);
  return 0;
}

kern_return_t
S_file_exec (mach_port_t port,
	     task_t task,
	     int flags,
	     char *argv,
	     u_int argvlen,
	     char *envp,
	     u_int envplen,
	     mach_port_t *fds,
	     u_int fdslen,
	     mach_port_t *portarray,
	     u_int portarraylen,
	     int *intarray,
	     u_int intarraylen,
	     mach_port_t *deallocnames,
	     u_int deallocnameslen,
	     mach_port_t *destroynames,
	     u_int destroynameslen)
{
  return EOPNOTSUPP;
}

kern_return_t
S_file_chown (mach_port_t port,
	      uid_t uid,
	      gid_t gid)
{
  return EROFS;
}

kern_return_t
S_file_chauthor (mach_port_t port,
		 uid_t author)
{
  return EROFS;
}

kern_return_t
S_file_chmod (mach_port_t port,
	      mode_t mode)
{
  return EROFS;
}

kern_return_t
S_file_chflags (mach_port_t port,
		int flags)
{
  return EROFS;
}

kern_return_t
S_file_utimes (mach_port_t port,
	       time_value_t atime,
	       time_value_t mtime)
{
  return EROFS;
}

kern_return_t
S_file_truncate (mach_port_t port,
		 off_t length)
{
  /* Truncate on devices always succeeds. */
  struct port_info *pi = ports_get_port (port);
  int type = pi->type;
  
  ports_done_with_port (pi);

  if (type == PT_CTL || type == PT_TRANSBOOT)
    return EOPNOTSUPP;
  if (type == PT_DIR)
    return EBADF;
  
  return 0;
}

kern_return_t
S_file_lock (mach_port_t port,
	     int flags)
{
  return EOPNOTSUPP;
}

kern_return_t
S_file_lock_stat (mach_port_t port,
		  int *mystat,
		  int *otherstat)
{
  return EOPNOTSUPP;
}

kern_return_t
S_file_check_access (mach_port_t port,
		     int *allowed)
{
  struct port_info *pi = ports_get_port (port);
  int type = pi->type;
  ports_done_with_port (pi);
  
  if (type == PT_CTL || type == PT_TRANSBOOT)
    return EOPNOTSUPP;
  
  if (type == PT_DIR)
    *allowed = O_READ|O_EXEC;
  else if (type == PT_NULL)
    *allowed = O_READ|O_WRITE|O_EXEC;
  else 
    /* Lie for console real node */
    *allowed = O_READ|O_WRITE;
  
  return 0;
}

kern_return_t
S_file_notice_changes (mach_port_t port,
		       mach_port_t notify)
{
  return EOPNOTSUPP;
}

kern_return_t
S_file_getcontrol (mach_port_t port,
		   mach_port_t *ctl,
		   mach_msg_type_name_t *ctltype)
{
  /* Should check access, of course.  XXX */
  *ctl = ports_get_right (fs_control);
  *ctltype = MACH_MSG_TYPE_MAKE_SEND;
  return 0;
}

kern_return_t
S_file_statfs (mach_port_t port,
	       struct fsys_statfsbuf *stb)
{
  stb->fsys_stb_type = FSTYPE_DEV;
  stb->fsys_stb_bsize = 0;
  stb->fsys_stb_fsize = 0;
  stb->fsys_stb_blocks = 0;
  stb->fsys_stb_bfree = 0;
  stb->fsys_stb_bavail = 0;
  stb->fsys_stb_files = 4;	/* null, console, tty, dir */
  stb->fsys_stb_ffree = 0;
  stb->fsys_stb_fsid = 0;	/* ??? XXX */
  return 0;
}

kern_return_t
S_file_sync (mach_port_t port,
	     int wait)
{
  return 0;
}

kern_return_t
S_file_syncfs (mach_port_t port,
	       int wait,
	       int dochildren)
{
  if (dochildren)
    {
      file_t root;
      error_t err;
      retry_type retry;
      string_t retryname;
      mach_port_t control;

      mutex_lock (&console_link.lock);
      if (console_link.control != MACH_PORT_NULL)
	{
	  control = console_link.control;
	  mach_port_mod_refs (mach_task_self (), control, 
			      MACH_PORT_RIGHT_SEND, 1);
	  mutex_unlock (&console_link.lock);
	  err = fsys_getroot (console_link.control, 0, 0, 0, 0, 0, 
			      0, O_NOTRANS, &retry, retryname, &root);
	  if (err == MACH_SEND_INVALID_DEST)
	    {
	      mutex_lock (&console_link.lock);
	      if (control == console_link.control)
		fshelp_translator_drop (&console_link);
	      mutex_unlock (&console_link.lock);
	    }
	  else if (!err && retry == FS_RETRY_NORMAL && retryname[0] == '\0')
	    file_syncfs (root, wait, 1);

	  mach_port_deallocate (mach_task_self (), control);

	  if (root)
	    mach_port_deallocate (mach_task_self (), root);
	}
      mutex_unlock (&console_link.lock);
    }
  return 0;
}

kern_return_t
S_file_pathconf (mach_port_t port,
		 int name,
		 int *val)
{
  return EOPNOTSUPP;
}

kern_return_t
S_file_getlinknode (mach_port_t port,
		    mach_port_t *linkpt,
		    mach_msg_type_name_t *linkpttype)
{
  return EROFS;			/* XXX How to deny right??? */
}

kern_return_t
S_file_getfh (mach_port_t port,
	      char **handle,
	      u_int *handlelen)
{
  return EOPNOTSUPP;
}

kern_return_t
S_dir_lookup (mach_port_t port,
	      char *name,
	      int flags,
	      mode_t mode,
	      retry_type *doretry,
	      char *retryname,
	      mach_port_t *retrypt,
	      mach_msg_type_name_t *retrypttype)
{
  struct port_info *pi = ports_get_port (port);
  error_t err;
  char *endcomp;
  int i;
  
  if (pi->type == PT_CTL || pi->type == PT_TRANSBOOT)
    {
      ports_done_with_port (pi);
      return EOPNOTSUPP;
    }
  else if (pi->type != PT_DIR && pi->type != PT_FDDIR)
    {
      ports_done_with_port (pi);
      return ENOTDIR;
    }
  
  /* Skip leading slashes */
  while (name[0] == '/')
    name++;

  if ((flags & O_CREAT) && (flags & O_EXCL))
    {
      ports_done_with_port (pi);
      return EROFS;
    }

  endcomp = index (name, '/');
  if (!endcomp)
    endcomp = index (name, '\0');
#define N(string) (endcomp - name == strlen (string) && \
		   !memcmp (name, string, strlen (string)))

  if (pi->type == PT_FDDIR)
    {
      if (N (".") || name[0] == '\0')
	{
	  if (endcomp [0] == '\0' && (flags & O_WRITE))
	    return EISDIR;
	  *doretry = FS_RETRY_NORMAL;
	  strcpy (retryname, endcomp);
	  *retrypt = ports_get_right (fddir_port);
	  *retrypttype = MACH_MSG_TYPE_MAKE_SEND;
	  return 0;
	}
      
      if (N (".."))
	{
	  *doretry = FS_RETRY_NORMAL;
	  strcpy (retryname, endcomp);
	  *retrypt = ports_get_right (dir_port);
	  *retrypttype = MACH_MSG_TYPE_MAKE_SEND;
	  return 0;
	}
      
      *doretry = FS_RETRY_MAGICAL;
      sprintf (retryname, "fd/%s", endcomp);
      *retrypt = MACH_PORT_NULL;
      *retrypttype = MACH_MSG_TYPE_COPY_SEND;
      return 0;
    }

  ports_done_with_port (pi);

  /* See S_dir_readdir, which should match the names here. */

  if (N (".") || name[0] == '\0')
    {
      /* Force the user to do the loop; it's easier here  */

      if (endcomp[0] == '\0' && (flags & O_WRITE))
	return EISDIR;
      *doretry = FS_RETRY_NORMAL;
      strcpy (retryname, endcomp);
      *retrypt = ports_get_right (dir_port);
      *retrypttype = MACH_MSG_TYPE_MAKE_SEND;
      return 0;
    }

  if (N (".."))
    {
      *doretry = FS_RETRY_REAUTH;
      strcpy (retryname, endcomp);
      *retrypt = dotdot;
      *retrypttype = MACH_MSG_TYPE_COPY_SEND;
      return 0;
    }

  for (i = 0; i < NDISKS; i++)
    if (disks[i].device != MACH_PORT_NULL && N (disknames[i]))
      {
	if (*endcomp != '\0')
	  return ENOTDIR;
	*doretry = FS_RETRY_NORMAL;
	retryname[0] = '\0';
	*retrypt = ports_get_right (disks[i].port);
	*retrypttype = MACH_MSG_TYPE_MAKE_SEND;
	return 0;
      }

  if (N ("null"))
    {
      if (*endcomp != '\0')
	return ENOTDIR;
      *doretry = FS_RETRY_NORMAL;
      retryname[0] = '\0';
      *retrypt = ports_get_right (null_port);
      *retrypttype = MACH_MSG_TYPE_MAKE_SEND;
      return 0;
    }
  
  if (N ("tty"))
    {
      if (endcomp[0]== '\0' && (flags & O_NOTRANS))
	{
	  *doretry = FS_RETRY_NORMAL;
	  *retrypt = ports_get_right (tty_port);
	  *retryname = '\0';
	  *retrypttype = MACH_MSG_TYPE_MAKE_SEND;
	}
      else
	{
	  *doretry = FS_RETRY_MAGICAL;
	  strcpy (retryname, "tty");
	  strcat (retryname, endcomp);
	  *retrypt = MACH_PORT_NULL;
	  *retrypttype = MACH_MSG_TYPE_COPY_SEND;
	}
      return 0;
    }

  if (N ("stdin"))
    {
      if (*endcomp == '\0' && (flags & (O_NOTRANS|O_NOLINK)))
	{
	  *retryname = '\0';
	  *retrypt = ports_get_right (stdin_port);
	}
      else
	{
	  strcpy (stpcpy (retryname, "fd/0"), endcomp);
	  *retrypt = ports_get_right (dir_port);
	}
  
      *doretry = FS_RETRY_NORMAL;
      *retrypttype = MACH_MSG_TYPE_MAKE_SEND;
      return 0;
    }
  if (N ("stdout"))
    {
      if (*endcomp == '\0' && (flags & (O_NOTRANS|O_NOLINK)))
	{
	  *retryname = '\0';
	  *retrypt = ports_get_right (stdout_port);
	}
      else
	{
	  strcpy (stpcpy (retryname, "fd/1"), endcomp);
	  *retrypt = ports_get_right (dir_port);
	}
      *doretry = FS_RETRY_NORMAL;
      *retrypttype = MACH_MSG_TYPE_MAKE_SEND;
      return 0;
    }
  if (N ("stderr"))
    {
      if (*endcomp == '\0' && (flags & (O_NOTRANS|O_NOLINK)))
	{
	  *retryname = '\0';
	  *retrypt = ports_get_right (stderr_port);
	}
      else
	{
	  strcpy (stpcpy (retryname, "fd/2"), endcomp);
	  *retrypt = ports_get_right (dir_port);
	}
      *doretry = FS_RETRY_NORMAL;
      *retrypttype = MACH_MSG_TYPE_MAKE_SEND;
      return 0;
    }

  if (N ("fd"))
    {
      *doretry = FS_RETRY_NORMAL;
      strcpy (retryname, endcomp);
      *retrypt = ports_get_right (fddir_port);
      *retrypttype = MACH_MSG_TYPE_MAKE_SEND;
      return 0;
    }

  if (N ("console"))
    {
      mach_port_t dirname;

      if (flags & O_NOTRANS)
	{
	  if (*endcomp != '\0')
	    return ENOTDIR;
	  else if (flags & (O_READ|O_WRITE|O_EXEC))
	    return EACCES;
	  else
	    {
	      *doretry = FS_RETRY_NORMAL;
	      *retryname = '\0';
	      *retrypt = ports_get_right (console_port);
	      *retrypttype = MACH_MSG_TYPE_MAKE_SEND;
	      return 0;
	    }
	}

    retry_trans:

      dirname = ports_get_right (dir_port);
      mach_port_insert_right (mach_task_self (), dirname, dirname,
			      MACH_MSG_TYPE_MAKE_SEND);

      mutex_lock (&console_link.lock);
      if (console_link.control != MACH_PORT_NULL)
	{
	  mach_port_t control = console_link.control;
	  mach_port_mod_refs (mach_task_self (), control,
			      MACH_PORT_RIGHT_SEND, 1);
	  mutex_unlock (&console_link.lock);
	  
	  err = fsys_getroot (control, dirname, MACH_MSG_TYPE_COPY_SEND,
			      0, 0, 0, 0, 
			      *endcomp ? 0 : flags, doretry, retryname,
			      retrypt);

	  if (err == MACH_SEND_INVALID_DEST)
	    {
	      mutex_lock (&console_link.lock);
	      if (console_link.control == control)
		fshelp_translator_drop (&console_link);
	      mutex_unlock (&console_link.lock);
	      mach_port_deallocate (mach_task_self (), control);
	      
	      goto retry_trans;
	    }
	  
	  mach_port_deallocate (mach_task_self (), control);
	  mach_port_deallocate (mach_task_self (), dirname);
	  if (!err)
	    strcat (retryname, endcomp);
	  *retrypttype = MACH_MSG_TYPE_MOVE_SEND;
	  return err;
	}
      else
	{
	  mach_port_t filename;
	  
	  /* Start passive translator. */
	  mutex_unlock (&console_link.lock);

	  filename = ports_get_right (console_port);
	  mach_port_insert_right (mach_task_self (), filename, filename,
				  MACH_MSG_TYPE_MAKE_SEND);
	  err = fshelp_start_translator (&console_link, console_trans,
					 sizeof (console_trans), dirname,
					 filename, 0, 0);
	  goto retry_trans;
	}
    }

  
  /* Not one of the ones we know about */
  return flags & O_CREAT ? EROFS : ENOENT;
}

kern_return_t
S_dir_readdir (file_t dir,
	       char **bufp,
	       u_int *buflen,
	       int entry,
	       int nentries,
	       vm_size_t maxread,
	       int *amount)
{
  struct port_info *pi;
  
  static struct entry
    {
      char *name;
      int number;
      int type;
    }
  dirlist[] =
    {
      {".", PT_DIR, DT_DIR},
      {"..", PT_DIR, DT_DIR},
      {"null", PT_NULL, DT_CHR},
      {"tty", PT_TTY, DT_CHR},
      {"stdin", PT_STDIN, DT_LNK},
      {"stdout", PT_STDOUT, DT_LNK},
      {"stderr", PT_STDERR, DT_LNK},
      {"fd", PT_FDDIR, DT_DIR},
      {"console", PT_CONSOLE, DT_REG},
    };
  static int ndirlist = sizeof dirlist / sizeof (struct entry);
  int i;
  struct dirent *ep;

  /* This is hacked, but it at least lets ls work. */

  if (entry != 0)
    {
      /* Return EOF condition */
      *buflen = 0;
      *amount = 0;
      return 0;
    }

  pi = ports_check_port_type (dir, PT_DIR);
  if (!pi)
    return EOPNOTSUPP;

  /* XXX Ignore MAXREAD */
  /* XXX Always pass out-of-band */

  vm_allocate (mach_task_self (), (vm_address_t *)bufp, vm_page_size, 1);
  *buflen = vm_page_size;
  ep = (struct dirent *)*bufp;
  
  for (i = 0; i < ndirlist; i++)
    {
      int len = strlen (dirlist[i].name);
      ep->d_fileno = dirlist[i].number;
      ep->d_reclen = sizeof (struct dirent) + len + 1;
      ep->d_namlen = len;
      bcopy (dirlist[i].name, ep->d_name, len + 1);
      ep->d_type = dirlist[i].type;
      
      ep = (struct dirent *) ((vm_address_t)ep + ep->d_reclen);
    }

  for (i = 0; i < NDISKS; i++)
    {
      int len;
      
      if (disks[i].device == MACH_PORT_NULL)
	continue;
      
      len = strlen (disknames[i]);
      ep->d_fileno = PT_DISKMIN + i;
      ep->d_reclen = sizeof (struct dirent) + len + 1;
      ep->d_namlen = len;
      bcopy (disknames[i], ep->d_name, len + 1);
      ep->d_type = DT_CHR;
      
      ep = (struct dirent *) ((vm_address_t)ep + ep->d_reclen);
    }

  *buflen = (vm_address_t) ep - (vm_address_t) *bufp;
  *amount = ndirlist;
  return 0;
}

kern_return_t
S_dir_mkdir (file_t dir,
	     char *name,
	     mode_t mode)
{
  return EROFS;
}

kern_return_t
S_dir_rmdir (file_t dir,
	     char *name)
{
  return EROFS;
}

kern_return_t
S_dir_unlink (file_t dir,
	      char *name)
{
  return EROFS;
}

kern_return_t
S_dir_link (file_t dir,
	    file_t file,
	    char *name)
{
  return EROFS;
}

kern_return_t
S_dir_rename (file_t dir1,
	      char *name1,
	      file_t dir2,
	      char *name2)
{
  return EROFS;
}

kern_return_t
S_dir_mkfile (file_t dir,
	     int flags,
	     mode_t mode,
	     mach_port_t *pt,
	     mach_msg_type_name_t *pttype)
{
  return EROFS;
}

kern_return_t
S_dir_notice_changes (file_t dir,
		      mach_port_t notify)
{
  return EOPNOTSUPP;
}

kern_return_t
S_file_set_translator (file_t node,
		       int passiveflags,
		       int activeflags,
		       int oldtrans_flags,
		       char *passive,
		       u_int passivelen,
		       mach_port_t active)
{
  struct port_info *pi = ports_check_port_type (node, PT_CONSOLE);

  if (!pi)
    return EOPNOTSUPP;

  if (passiveflags & FS_TRANS_SET)
    return EROFS;
  
  if (!(activeflags & FS_TRANS_SET))
    return 0;
  
  mutex_lock (&console_link.lock);

  if ((activeflags & FS_TRANS_EXCL) && console_link.control != MACH_PORT_NULL)
    {
      mutex_unlock (&console_link.lock);
      return EBUSY;
    }
  
  if (console_link.control != MACH_PORT_NULL)
    fsys_goaway (console_link.control, oldtrans_flags);
  
  fshelp_set_control (&console_link, active);
  
  mutex_unlock (&console_link.lock);
  return 0;
}

kern_return_t
S_file_get_translator (mach_port_t file,
		       char **transp,
		       u_int *translen)
{
  struct port_info *pi = ports_get_port (file);
  char *name;

  if (!pi)
    return EOPNOTSUPP;

  vm_allocate (mach_task_self (), (vm_address_t *)transp, 
	       vm_page_size, 1);
  switch (pi->type)
    {
    case PT_CONSOLE:
      *translen = sizeof (console_trans);
      bcopy (console_trans, *transp, sizeof (console_trans));
      break;
      
    case PT_STDIN:
      name = "fd/0";
      goto symlink;
      
    case PT_STDOUT:
      name = "fd/1";
      goto symlink;
      
    case PT_STDERR:
      name = "fd/2";

    symlink:
      *translen = strlen (_HURD_SYMLINK) + strlen (name) + 2;
      sprintf (*transp, "%s%c%s%c", _HURD_SYMLINK, '\0', name, '\0');
      break;
  
    default:
      vm_deallocate (mach_task_self (), (vm_address_t) *transp, vm_page_size);
      ports_done_with_port (pi);
      return ENOENT;
  }
  
  ports_done_with_port (pi);
  return 0;
}

kern_return_t
S_file_get_translator_cntl (mach_port_t file,
			    mach_port_t *ctl,
			    mach_msg_type_name_t *ctltype)
{
  struct port_info *pi = ports_check_port_type (file, PT_CONSOLE);
  if (!pi)
    return ENOENT;
  ports_done_with_port (pi);
  mutex_lock (&console_link.lock);
  *ctl = console_link.control;
  *ctltype = MACH_MSG_TYPE_COPY_SEND;
  mutex_unlock (&console_link.lock);
  return 0;
}

/* Bogosity */
kern_return_t
S_file_invoke_translator (mach_port_t port,
			  int flags,
			  retry_type *ret,
			  char *retnam,
			  mach_port_t *retport,
			  mach_msg_type_name_t *retporttype)
{
  return EOPNOTSUPP;
}


kern_return_t
S_io_write (mach_port_t port,
	    char *data,
	    mach_msg_type_number_t datalen,
	    off_t offset,
	    mach_msg_type_number_t *amount)
{
  struct port_info *pi = ports_get_port (port);
  error_t err;
  int idx;

  if (!pi)
    return EOPNOTSUPP;
  
  switch (pi->type)
    {
    case PT_NULL:
      *amount = datalen;
      err = 0;
      break;

    default:
      if (pi->type < PT_DISKMIN)
	err = EOPNOTSUPP;
      else
	{
	  idx = pi->type - PT_DISKMIN;
	  if (idx >= NDISKS || disks[idx].device == MACH_PORT_NULL)
	    err = EIEIO;
	  else
	    {
	      err = raw_disk_write (idx, data, datalen, 
				    offset == -1 ? disks[idx].offset : offset, 
				    amount);
	      if (offset == -1 && err == 0)
		disks[idx].offset += *amount;
	    }
	}
      break;
    }
  
  ports_done_with_port (pi);

  return err;
}

kern_return_t
S_io_read (mach_port_t port,
	   char **datap,
	   mach_msg_type_number_t *datalen,
	   off_t offset,
	   mach_msg_type_number_t amount)
{
  struct port_info *pi = ports_get_port (port);
  error_t err;
  int idx;

  if (!pi)
    return EOPNOTSUPP;

  switch (pi->type)
    {
    case PT_NULL:
      *datalen = 0;
      err = 0;
      break;
      
    default:
      if (pi->type < PT_DISKMIN)
	err = EOPNOTSUPP;
      else
	{
	  idx = pi->type - PT_DISKMIN;
	  if (idx >= NDISKS || disks[idx].device == MACH_PORT_NULL)
	    err = EIEIO;
	  else
	    {
	      err = raw_disk_read (idx, datap, datalen,
				   offset == -1 ? disks[idx].offset : offset,
				   amount);
	      if (offset == -1 && err == 0)
		disks[idx].offset += *datalen;
	    }
	}
      break;
    }
      
  ports_done_with_port (pi);
  return err;
}

kern_return_t
S_io_seek (mach_port_t port,
	   off_t offset,
	   int whence,
	   off_t *newoffset)
{
  struct port_info *pi = ports_get_port (port);
  error_t err;
  int idx;
  
  if (!pi || (pi->type < PT_DISKMIN || pi->type >= PT_DISKMIN + NDISKS))
    {
      if (pi)
	ports_done_with_port (pi);
      return ESPIPE;
    }

  idx = (pi->type - PT_DISKMIN);
  if (disks[idx].device == MACH_PORT_NULL)
    err = EIEIO;
  else
    switch (whence)
      {
      case SEEK_SET:
	disks[idx].offset = offset;
	err = 0;
	break;
      case SEEK_CUR:
	disks[idx].offset += offset;
	err = 0;
	break;
      case SEEK_END:
	disks[idx].offset = disks[idx].size + offset;
	err = 0;
	break;
      default:
	err = EINVAL;
	break;
      }

  if (!err)
    *newoffset = disks[idx].offset;
  ports_done_with_port (pi);
  return err;;
}

kern_return_t
S_io_readable (mach_port_t port,
	       mach_msg_type_number_t *amount)
{
  struct port_info *pi = ports_check_port_type (port, PT_NULL);
  if (!pi)
    return EBADF;
  ports_done_with_port (pi);
  *amount = 0;
  return 0;
}

kern_return_t
S_io_set_all_openmodes (mach_port_t port,
			int modes)
{
  return EOPNOTSUPP;
}

kern_return_t
S_io_get_openmodes (mach_port_t port,
		    int *modes)
{
  struct port_info *pi = ports_get_port (port);
  error_t err = 0;
  
  if (!pi)
    return EOPNOTSUPP;

  switch (pi->type)
    {
    case PT_CTL:
    case PT_TRANSBOOT:
      err = EOPNOTSUPP;
      break;
      
    default:
      *modes = O_READ|O_WRITE;
      break;
      
    case PT_CONSOLE:
      *modes = 0;
      break;
      
    case PT_DIR:
      *modes = O_READ|O_EXEC;
      break;
    }
  
  ports_done_with_port (pi);
  return err;
}

kern_return_t
S_io_set_some_openmodes (file_t file,
			 int bits)
{
  return EOPNOTSUPP;
}

kern_return_t
S_io_clear_some_openmodes (file_t file,
			   int bits)
{
  return EOPNOTSUPP;
}

kern_return_t
S_io_async (file_t file,
	    mach_port_t notify,
	    mach_port_t *id,
	    mach_msg_type_name_t *idtype)
{
  return EOPNOTSUPP;
}

kern_return_t
S_io_mod_owner (file_t file,
		pid_t owner)
{
  return EOPNOTSUPP;
}

kern_return_t
S_io_get_owner (file_t file,
		pid_t *owner)
{
  return EOPNOTSUPP;
}

kern_return_t
S_io_get_icky_async_id (file_t file,
			mach_port_t *id,
			mach_msg_type_name_t *idtype)
{
  return EOPNOTSUPP;
}

kern_return_t
S_io_select (file_t file,
	     int type,
	     mach_port_t return_port,
	     mach_msg_type_name_t returnporttype,
	     int id,
	     int *result)
{
  *result = type;
  mach_port_deallocate (mach_task_self (), return_port);
  return 0;
}

kern_return_t
S_io_stat (file_t file,
	   struct stat *st)
{
  struct port_info *pi = ports_get_port (file);
  if (!pi)
    return EOPNOTSUPP;
  
  if (pi->type == PT_CTL || pi->type == PT_TRANSBOOT)
    {
      ports_done_with_port (pi);
      return EOPNOTSUPP;
    }
  
  bzero (st, sizeof (struct stat));

  if (pi->type == PT_DIR)
    io_stat (underlying, st);

  st->st_fstype = FSTYPE_DEV;
  st->st_ino = pi->type;
  if (pi->type == PT_DIR)
    st->st_nlink = 3;
  else if (pi->type == PT_FDDIR)
    st->st_nlink = 2;
  else
    st->st_nlink = 1;
  st->st_blksize = vm_page_size;
  switch (pi->type)
    {
    case PT_DIR:
      st->st_mode = 0555 | S_IFDIR;
      break;
      
    case PT_CONSOLE:
      st->st_mode = 0666 | S_IFREG;
      break;
      
    default:
      st->st_size = disks[pi->type - PT_DISKMIN].size;
      /* Fall through. */

    case PT_NULL:
    case PT_TTY:
      st->st_mode = 0666 | S_IFCHR;
      break;

    case PT_FDDIR:
      st->st_mode = 0555 | S_IFDIR;
      break;
      
    case PT_STDIN:
    case PT_STDOUT:
    case PT_STDERR:
      st->st_mode = 0777 | S_IFLNK;
      break;
    }
  
  return 0;
}

kern_return_t
S_io_reauthenticate (mach_port_t port,
		     mach_port_t rend2)
{
  struct port_info *pi = ports_get_port (port);
  uid_t gubuf[20], ggbuf[20], aubuf[20], agbuf[20];
  uid_t *gen_uids, *gen_gids, *aux_uids, *aux_gids;
  u_int genuidlen, gengidlen, auxuidlen, auxgidlen;

  genuidlen = gengidlen = auxuidlen = auxgidlen = 20;
  gen_uids = gubuf;
  gen_gids = ggbuf;
  aux_uids = aubuf;
  aux_gids = agbuf;

  if (!pi)
    return EOPNOTSUPP;
  
  auth_server_authenticate (authserver, ports_get_right (pi),
			    MACH_MSG_TYPE_MAKE_SEND, rend2, 
			    MACH_MSG_TYPE_MOVE_SEND,
			    ports_get_right (pi), MACH_MSG_TYPE_MAKE_SEND,
			    &gen_uids, &genuidlen, 
			    &aux_uids, &auxuidlen,
			    &gen_gids, &gengidlen,
			    &aux_gids, &auxgidlen); 
			    
  if (gubuf != gen_uids)
    vm_deallocate (mach_task_self (), (u_int) gen_uids,
		   genuidlen * sizeof (uid_t));
  if (ggbuf != gen_gids)
    vm_deallocate (mach_task_self (), (u_int) gen_gids,
		   gengidlen * sizeof (uid_t));
  if (aubuf != aux_uids)
    vm_deallocate (mach_task_self (), (u_int) aux_uids,
		   auxuidlen * sizeof (uid_t));
  if (agbuf != aux_gids)
    vm_deallocate (mach_task_self (), (u_int) aux_gids,
		   auxgidlen * sizeof (uid_t));

  ports_done_with_port (pi);

  return 0;
}

kern_return_t
S_io_restrict_auth (mach_port_t file,
		    mach_port_t *newfile,
		    mach_msg_type_name_t *newfiletype,
		    uid_t *uids,
		    u_int nuids,
		    gid_t *gids,
		    u_int ngids)
{
  struct port_info *pi = ports_get_port (file);
  if (!pi)
    return EOPNOTSUPP;
  *newfile = ports_get_right (pi);
  *newfiletype = MACH_MSG_TYPE_MAKE_SEND;
  ports_done_with_port (pi);
  return 0;
}

kern_return_t
S_io_duplicate (mach_port_t file,
		mach_port_t *newfile,
		mach_msg_type_name_t *newfiletype)
{
  struct port_info *pi = ports_get_port (file);
  if (!pi)
    return EOPNOTSUPP;
  *newfile = ports_get_right (pi);
  *newfiletype = MACH_MSG_TYPE_MAKE_SEND;
  ports_done_with_port (pi);
  return 0;
}

kern_return_t
S_io_server_version (mach_port_t file,
		     char *name,
		     int *major,
		     int *minor,
		     int *edit)
{
  return EOPNOTSUPP;
}

kern_return_t
S_io_map (file_t port,
	  mach_port_t *rdobj,
	  mach_msg_type_name_t *rdobjtype,
	  mach_port_t *wrobj,
	  mach_msg_type_name_t *wrobjtype)
{
  return EOPNOTSUPP;
}


kern_return_t
S_io_map_cntl (file_t port,
		      mach_port_t *obj,
		      mach_msg_type_name_t *objtype)
{
  return EOPNOTSUPP;
}

kern_return_t
S_io_get_conch (file_t port)
{
  return EOPNOTSUPP;
}

kern_return_t
S_io_release_conch (file_t port)
{
  return EOPNOTSUPP;
}

kern_return_t 
S_io_eofnotify (file_t port)
{
  return EOPNOTSUPP;
}

kern_return_t
S_io_prenotify (file_t port,
		       vm_offset_t start, 
		       vm_offset_t end)
{
  return EOPNOTSUPP;
}

kern_return_t
S_io_postnotify (file_t port, 
			vm_offset_t start, 
			vm_offset_t end)
{
  return EOPNOTSUPP;
}

kern_return_t
S_io_readsleep (file_t port)
{
  return EOPNOTSUPP;
}

kern_return_t
S_io_sigio (file_t port)
{
  return EOPNOTSUPP;
}

kern_return_t
S_io_readnotify (file_t port)
{
  return EOPNOTSUPP;
}

  
kern_return_t
do_mach_notify_port_deleted (mach_port_t notify,
				    mach_port_t name)
{
  return 0;
}

kern_return_t
do_mach_notify_msg_accepted (mach_port_t notify,
				    mach_port_t name)
{
  return 0;
}

kern_return_t
do_mach_notify_port_destroyed (mach_port_t notify,
				      mach_port_t name)
{
  return 0;
}

kern_return_t
do_mach_notify_send_once (mach_port_t notify)
{
  return 0;
}

kern_return_t
do_mach_notify_dead_name (mach_port_t notify,
				 mach_port_t name)
{
  return 0;
}
