/* Fileserver for S_IFSOCK nodes, representing sockaddrs for PF_LOCAL sockets
   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.  */

#define MASTER_NAME "/servers/ifsock"

struct socknode *
{
  file_t underlying;		/* underlying node in parent FS */
  fsys_t controlport;		/* control port we process requests for */
  addr_port_t sockaddr;		/* the point of this whole exercise */
  struct userref *firstref;	/* user references */
};

struct userref *
{
  file_t ourselves;		/* receive right we process requests for */
  file_t auth_realnode;		/* for doing things */
  struct socknode *node;	/* node */
  struct idblock id;
  struct userref *nextref;
};

/* The strategy is as follows:

   We get started up for a particular node.  We try to register as a
   translator for /servers/ifsock.  If that fails (because there is
   already a translator for it), we try to contact that translator.
   If it has died in the interim (race :-)), we try to register.
   Lather, rinse, repeat.

   If we contacted an existing /servers/ifsock, we pass it our control
   port and go away.

   If we registered, then we handle requests for this node and all
   other nodes.  We implement timeouts for GC-ing old socket nodes
   when necessary, or when they get goaway-ed.  We go away ourselves
   (like this will actually ever happen) when we have GC-ed all
   existing socket nodes and another timeout has expired.
*/


/* FSYS protocol: */
error_t
fsys_startup (mach_port_t fsys,
	      mach_port_t realnode,
	      mach_port_t dotdotdir)
{
  file_t serversdir;
  error_t err;
  char *name;
  mach_port_t newmaster, unused;
  file_t existmaster;

  err = path_split (rootdir, dotdotdir, MASTER_NAME, &serversdir, &name);
  if (err)
    goto out;
  
  mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE, &newmaster);

 retry:
  
  err = dir_set_translator (serversdir, name, strlen (name), 1,
			    FSYS_GOAWAY_DONT, MASTER_NAME, 
			    strlen (MASTER_NAME), newmaster);
  switch (err)
    {
    case POSIX_SUCCESS:
      /* Installed.  Create the node for this and kick everything off. */
      mach_port_deallocate (mach_task_self (), serversdir);
      mach_port_request_notification (mach_task_self (), newmaster,
				      MACH_NOTIFY_NO_SENDERS, 0,
				      newmaster, MACH_MSG_TYPE_MAKE_SEND_ONCE,
				      &unused);
      err = ifsock_assume_responsibility (newmaster, fsys, realnode,
					  dotdotdir);
      if (err)
	goto out;
      else
	return POSIX_SUCCESS;
      
    case POSIX_EBUSY:
      /* There's already someone there.  Look it up again to get the 
	 master port.  */
      err = path_lookup (rootdir, dotdotdir, MASTER_NAME, 0, 0, &existmaster);

      if (err == POSIX_ENOENT)
	goto retry;

      if (err)
	goto out;

      err = ifsock_assume_responsibility (existmaster, fsys, realnode,
					  dotdotdir);
      if (err == POSIX_EOPNOTSUPP || err == MIG_TYPE_ERROR
	  || err == MIG_REMOTE_ERROR || err == MIG_BAD_ID
	  || err == MIG_BAD_ARGUMENTS)
	{
	  mach_port_deallocate (mach_task_self (), existmaster);
	  goto retry;		/* not being handled any more */
	}

      goto out;

    default:
      /* Some unexpected error */
      goto out;
    }

 out:
  fsys_startup_reply (err);
  exit (err);
}  

error_t
fsys_goaway (struct socknode *node,
	     int flags)
{
  struct userref *u, *tmp;
  
  if (!(flags & FSYS_GOAWAY_FORCE) && node->firstref)
    return POSIX_EBUSY;

  for (u = node->firstref; u; u = tmp)
    {
      tmp = u->nextref;
      mach_port_mod_refs (mach_task_self (), u->ourselves, 
			  MACH_PORT_RIGHT_RECEIVE, -1);
      mach_port_deallocate (mach_task_self (), u->auth_realnode);
      free (u);
    }
  mach_port_deallocate (mach_task_self (), node->underlying);
  mach_port_mode_refs (mach_task_self (), node->controlport,
		       MACH_PORT_RIGHT_RECEIVE, -1);
  if (node->sockaddr)
    mach_port_deallocate (mach_task_self (), node->sockaddr);
  free (node);
  fsys_goaway_reply (POSIX_SUCCESS);
  exit (0);
}

error_t
fsys_getroot (struct socknode *node;
	      struct idblock ids,
	      file_t auth_realnode,
	      file_t *transnode)
{
  struct userref *ref;
  mach_port_t unused;
  
  ref = malloc (sizeof (struct userref));

  mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE, 
		      &ref->ourselves);
  mach_port_request_notification (mach_task_self (), ref->ourselves,
				  MACH_NOTIFY_NO_SENDERS, 1, ref->ourselves,
				  MACH_MSG_TYPE_MAKE_SEND_ONCE, &unused);
  ref->auth_realnode = auth_realnode;
  ref->node = node;
  bcopy (&ids, &ref->id, sizeof (struct idblock));
  ref->nextref = node->firstref;
  node->firstref = ref;
  *transnode = ref->ourselves;				  
  return POSIX_SUCCESS;
}

/* IFSOCK protocol: */
error_t
ifsock_getsockaddr (struct userref *ref,
		    mach_port_t *sockaddr)
{
  struct io_statbuf stb;
  error_t err;
  
  err = U_io_stat (ref->node->realnode, &stb);
  if (err)
    return err;
  
  err = file_access (stb->stb_uid, stb->stb_gid, S_IFREAD, &ref->id);
  if (err)
    return err;
  
  *sockaddr = req->node->sockaddr;
  return POSIX_SUCCESS;
}

error_t
ifsock_setsockaddr (struct userref *ref,
		    mach_port_t sockaddr)
{
  struct io_statbuf stb;
  error_t err;
  
  err = U_io_stat (ref->node->realnode, &stb);
  if (err)
    return err;
  
  err = file_access (stb->stb_uid, stb->stb_gid, S_IFWRITE, &ref->id);
  if (err)
    return err;
  
  if (req->node->sockaddr)
    mach_port_deallocate (mach_task_self (), sockaddr);
  
  req->node->sockaddr = sockaddr;
  return POSIX_SUCCESS;
}

error_t
ifsock_assume_responsibility (mach_port_t master_port,
			      mach_port_t control_port,
			      mach_port_t realnode,
			      mach_port_t dotdotdir)
{
  struct socknode *node;
  mach_port_t unused;

  mach_port_deallocate (mach_task_self (), dotdotdir);

  node = malloc (sizeof (struct socknode));

  node->underlying = realnode;
  node->controlport = control_port;
  node->sockaddr = MACH_PORT_NULL;
  node->firstref = 0;
  
  mach_port_request_notification (mach_task_self (), control_port,
				  MACH_NOTIFY_NO_SENDERS, 1, control_port,
				  MACH_MSG_TYPE_MAKE_SEND_ONCE, &unused);
  mach_port_request_notification (mach_task_self (), realnode,
				  MACH_NOTIFY_DEAD_NAME, 1, control_port
				  MACH_MSG_TYPE_MAKE_SEND_ONCE, &unused);
  return POSIX_SUCCESS;
}

/* I/O protocol:  */

/* all stubbed XXX except for io_stat. */

error_t
io_stat (struct userref *ref,
	 struct io_statbuf *stb)
{
  error_t err;
  
  err = U_io_stat (ref->auth_realnode, stb);
  if (!err)
    {
      stb->stb_fstype = FSTYPE_IFSOCK;
      stb->stb_mode = (stb->stb_mode & ~S_IFMT) | S_IFSOCK;
    }
  return err;
}


/* FS protocol: */

/* file_exec stubbed XXX */

error_t
file_chown (struct userref *ref,
	    uid_t new_owner,
	    gid_t new_group)
{
  return U_file_chown (ref->auth_realnode, new_owner, new_group);
}

error_t
file_chauthor (struct userref *ref,
	       uid_t new_author)
{
  return U_file_chauthor (ref->auth_realnode, new_author);
}

error_t
file_chmod (struct userref *ref,
	    mode_t new_mode)
{
  return U_file_chmod (ref->auth_realnode, new_mode);
}

error_t
file_chflags (struct userref *ref,
	      mode_t new_flags)
{
  return U_file_chflags (ref->auth_realnode, new_flags);
}

error_t
file_utimes (struct userref *ref,
	     time_value_t new_atime,
	     time_value_t new_mtime)
{
  return U_file_utimes (ref->auth_realnode, rew_atime, new_mtime);
}

error_t
file_seek (struct userref *ref,
	   int offset,
	   int whence,
	   int *newp)
{
  return POSIX_EOPNOTSUPP;
}

error_t
file_truncate (struct userref *ref,
	       int newsize)
{
  return POSIX_EOPNOTSUPP;
}

error_t
file_lock (struct userref *ref,
	   int flags)
{
  return POSIX_EOPNOTSUPP;
}

error_t
file_getcontrol (struct userref *ref,
		 mach_port_t *control)
{
  struct io_statbuf stb;
  error_t err;
  
  if (ref->id.euid != 0)
    {
      err = U_io_stat (ref->node->realnode, &stb);
      if (err)
	return err;
      if (ref->id.euid != stb.stb_uid)
	return POSIX_EPERM;
    }
  
  *control = ref->node->controlport;
}

error_t
file_statfs (struct userref *ref,
	     struct fsys_statfsbuf *stfs)
{
  return U_file_statfs (ref->realnode_auth, stfs);
}

error_t
file_sync (struct userref *ref,
	   int wait)
{
  return POSIX_SUCCESS;
}

error_t
file_syncfs (struct userref *ref,
	     int wait,
	     int do_children)
{
  return POSIX_SUCCESS;
}

error_t
file_pathconf (struct userref *ref,
	       struct pathconf *pc)
{
  return U_file_pathconf (ref->realnode_auth, oc);
}

error_t
file_getlinknode (struct userref *ref,
		  file_t *linknode)
{
  *linknode = ref->realnode_auth;
  return POSIX_SUCCESS;
}

/* dir routines all stubbed XXX */

	       


/* Notifications */
