/* Filesystem and I/O operations on devices
   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 "dev.h"

#include "fs_S.h"
#include "io_S.h"

spin_lock_t refcnt_lock = SPIN_LOCK_INITIALIZER;

kern_return_t
S_file_exec (struct protid *cred,
	     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 (struct protid *cred,
	      uid_t owner,
	      gid_t group)
{
  if (!cred)
    return EOPNOTSUPP;

  if (cred->type == DEVDIR)
    return EROFS;

  if (!isowner (cred->po->dev, cred))
    return EPERM;
  
  mutex_lock (&config_lock);
  cred->po->dev->st.st_uid = owner;
  cred->po->dev->st.st_gid = group;
  set_ctime (&cred->po->dev->st);
  mutex_unlock (&config_lock);

  return 0;
}

S_file_chauthor (struct protid *cred,
		 uid_t author)
{
  if (!cred)
    return EOPNOTSUPP;
  if (cred->type == DEVDIR)
    return EROFS;
  if (!isowner (cred->po->dev, cred))
    return EPERM;
  
  mutex_lock (&config_lock);
  cred->po->dev->st.st_author = author;
  set_ctime (&cred->po->dev->st);
  mutex_unlock (&config_lock);
  
  return 0;
}

kern_return_t
S_file_chmod (struct protid *cred,
	      mode_t mode)
{
  struct stat *st;
  
  if (!cred)
    return EOPNOTSUPP;
  if (cred->type == DEVDIR)
    return EROFS;
  if (!isowner (cred->po->dev, cred))
    return EPERM;
  
  mode &= ~(S_IFMT | S_ISPARE);
  
  mutex_lock (&config_lock);
  
  st = &cred->po->dev->st;
  
  if (!isuid (0, cred))
    {
      if (!S_ISDIR (st->st_mode))
	mode &= ~S_ISVTX;
      if (!groupmember (st->st_gid, cred))
	mode &= ~S_ISGID;
      if (!isuid (st->st_uid, cred))
	mode &= ~S_ISUID;
    }
  mode |= st->st_mode & (S_IFMT | S_ISPARE);
  st->st_mode = mode;
  set_ctime (st);
  mutex_unlock (&config_lock);

  return 0;
}

kern_return_t
S_file_chflags (struct protid *cred,
		int flags)
{
  if (!cred)
    return EOPNOTSUPP;
  if (cred->type == DEVDIR)
    return EROFS;
  if (!isowner (cred->po->dev, cred))
    return EPERM;
  
  mutex_lock (&config_lock);
  cred->po->dev->st.st_flags = flags;
  set_ctime (&cred->po->dev->st);
  mutex_unlock (&config_lock);
  return 0;
}

kern_return_t
S_file_utimes (struct protid *cred,
	       time_value_t atime,
	       time_value_t mtime)
{
  if (!cred)
    return EOPNOTSUPP;
  if (cred->type == DEVDIR)
    return EROFS;
  if (!isowner (cred->po->dev, cred))
    return EPERM;
  
  mutex_lock (&config_lock);
  cred->po->dev->st.st_atime = atime.seconds;
  cred->po->dev->st.st_atime_usec = atime.microseconds;
  cred->po->dev->st.st_mtime = mtime.seconds;
  cred->po->dev->st.st_mtime_usec = mtime.microseconds;
  set_ctime (&cred->po->dev->st);
  mutex_unlock (&config_lock);
  
  return 0;
}

kern_return_t
S_file_truncate (struct protid *cred,
		 off_t size)
{
  if (!cred)
    return EOPNOTSUPP;
  
  if (!(cred->po->openflags & O_WRITE))
    return EBADF;
  
  /* Truncation of devices always succeeds, doing nothing. */

  return 0;
}

kern_return_t
S_file_lock (struct protid *cred,
	     int flags)
{
  error_t err;
  
  if (!cred)
    return EOPNOTSUPP;
  
  mutex_lock (&config_lock);
  err = fshelp_acquire_lock (&cred->po->dev->flock, &cred->po->flock_stat,
			     &config_lock, flags);
  mutex_unlock (&config_lock);
  return err;
}

kern_return_t
S_file_lock_stat (struct protid *cred,
		  int *stat,
		  int *otherstat)
{
  if (!cred)
    return EOPNOTSUPP;
  
  *stat = cred->po->flock_stat;
  *otherstat = cred->po->dev->flock.type;
  return 0;
}

kern_return_t
S_file_check_access (struct protid *cred,
		     int *allowed)
{
  if (!cred)
    return EOPNOTSUPP;
  
  *allowed = 0;
  
  mutex_lock (&config_lock);
  
  if (check_access (cred->po->dev, S_IWRITE, cred) == 0)
    *allowed |= O_WRITE;
  if (check_access (cred->po->dev, S_IREAD, cred) == 0)
    *allowed |= O_READ;
  if (check_access (cred->po->dev, S_IEXEC, cred) == 0)
    *allowed |= O_EXEC;
  
  mutex_unlock (&config_lock);
  return 0;
}

kern_return_t
S_file_notice_changes (struct protid *cred,
		       mach_port_t notify)
{
  return EOPNOTSUPP;
}

kern_return_t
S_file_getcontrol (struct protid *cred,
		   mach_port_t *cntl,
		   mach_msg_type_name_t *cntltype)
{
  if (!cred)
    return EOPNOTSUPP;
  
  if (!isuid (0, cred))
    return EPERM;
  
  *cntl = ports_get_right (fscontrol);
  *cntltype = MACH_MSG_TYPE_MAKE_SEND;
  return 0;
}

kern_return_t
S_file_sync (struct protid *cred,
	     int wait)
{
  if (!cred)
    return EOPNOTSUPP;
  
  /* I don't think this is actually quite right... XXX */
  sync_config_file ();
  return 0;
}

kern_return_t
S_file_syncfs (struct protid *cred,
	       int wait,
	       int dochildren)
{
  void sync_trans (struct trans_link *trans, void *arg)
    {
      error_t err;
      file_t root;
      int wait = (int) arg;
      retry_type retry;
      string_t retryname;

      err = fsys_getroot (trans->control, 0, 0, 0, 0, O_NOTRANS,
			  &retry, retryname, &root);
      if (!err && retry != FS_RETRY_NONE)
	err = EIEIO;
      if (!err)
	{
	  file_syncfs (root, wait, 1);
	  mach_port_deallocate (mach_task_self (), root);
	}
    }

  if (!cred)
    return EOPNOTSUPP;
  
  if (dochildren)
    fshelp_translator_iterate (sync1, (void *)wait);
  
  sync_config_file ();
  
  return 0;
}

kern_return_t
S_dir_pathtrans (struct protid *dircred,
		 char *name,
		 int flags,
		 mode_t mode,
		 retry_type *retrytype,
		 char *retryname,
		 mach_port_t *retrypt,
		 mach_msg_type_name_t *retrypttype)
{
  int namelen = strlen (name);
  char *nextname;
  int i;
  int mustbedir = 0;

  if (!dircred)
    return EOPNOTSUPP;
  if (dircred->type == DEVICE)
    return ENOTDIR;
  
  if (name[0] == '/')
    return EINVAL;

  /* If this is /dev/fd/NNN then return the appropriate magic
     retry value. */
  if (dircred->type == FDDIR)
    {
      char *c;

    open_fd_dev:
      for (c = name, *c, c++)
	{
	  if (!isdigit (*c) && c != '/')
	    return ENOENT;
	  if (*c == '/')
	    return ENOTDIR;
	}
      strcpy (retryname, "fd/");
      strcat (retryname, name);
      *retrytype = FS_RETRY_MAGICAL;
      *retrypt = MACH_PORT_NULL;
      *retrypttype = MACH_MSG_TYPE_COPY_SEND;
      return 0;
    }
  
  assert (dircred->type == DEVDIR);
  
  /* If this is not looking up a single component, null terminate
     the first component and set NEXTNAME to everything after that. */
  nextname = index ('/', name);
  if (nextname)
    {
      *nextname = '\0';
      while (*nextname == '/')
	nextname++;
      if (*nextname == '\0')
	{
	  nextname = 0;
	  mustbedir = 1;
	}
    }

  for (i = 0; i < ndevices; i++)
    {
      if (strcmp (devices[i].name, name))
	continue;
      
      if (nextname)
	{
	  if (devices[i].type == DEV_TYPE_FDDIR)
	    {
	      name = nextname;
	      goto open_fd_dev;
	    }
	  else
	    return ENOTDIR;
	}

      /* Return the actual device. */

      /* Check permissions */
      if (mustbedir && !S_ISDIR (devices[i].st.st_mode))
	return ENOTDIR;
      
      err = 0;
      if (flags & O_READ)
	err = check_access (&devices[i], S_IREAD, dircred);
      if (!err && (flags & O_EXEC))
	err = check_access (&devices[i], S_IEXEC, dircred);
      if (!err && (flags & O_WRITE))
	{
	  if (S_ISDIR (devices[i].st.st_mode))
	    err = EINDIR;
	  else
	    err = check_access (&devices[i], S_IWRITE, dircred);
	}

      flags &= (O_READ | O_WRITE | O_EXEC);
      
      /* Allocate a new reference for the device; if this
	 is the first open of the device, then call the
	 open routine. */
      spin_lock (&refcnt_lock);
      if (!devices[i].refcnt++)
	{
	  err = (*devsw[devices[i].type])(&devices[i], flags & O_NONBLOCK);
	  if (err)
	    {
	      spin_unlock (&refcnt_lock);
	      return err;
	    }
	}

      

      spin_unlock (&refcnt_lock);
	  
      
      
