/* Inode and related data structure manipulation
   Copyright (C) 1991, 1992 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 2, 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 "ufs.h"
#include "inode.h"
#include "dinode.h"
#include "fs.h"
#include <sys/file.h>
#include <stdio.h>
#include <stdlib.h>
#include <mach/notify.h>
#include <string.h>

static void release_inode (struct inode *, int);
static struct inode *inodestruct_alloc (void);
static void inodestruct_free (struct inode *);
static struct protid *protid_alloc (void);
static void protid_free (struct protid *);
static struct peropen *peropen_alloc (void);
static void peropen_free (struct peropen *);

struct inode *inodestructlist;
struct mutex inodestructlock;

struct protid *protidlist;
struct mutex protidlock;

struct peropen *peropenlist;
struct mutex peropenlock;

/* This locks all modifications to refcounts on protids.  */
static struct mutex credreflock;

#define	INOHSZ	512
#if	((INOHSZ&(INOHSZ-1)) == 0)
#define	INOHASH(ino)	((ino)&(INOHSZ-1))
#else
#define	INOHASH(ino)	(((unsigned)(ino))%INOHSZ)
#endif

struct inode *ihead[INOHSZ];

struct mutex ihashlock;

void
inode_init(void)
{
  register int i;

  for (i = 0; i < INOHSZ; i++)
    ihead[i] = 0;
  
  mutex_init (&ihashlock);
  mutex_init (&credreflock);
  mutex_init (&inodestructlock);
  mutex_init (&protidlock);
  mutex_init (&peropenlock);
  inodestructlist = 0;
  protidlist = 0;
  peropenlist = 0;
}

/*
 * Look up an inode by inumber.
 * A pointer to a locked
 * inode structure is returned.
 */
error_t
iget(ino_t ino, 
     struct inode **ipp)
{
  struct inode *volatile ip;
  error_t error;
  
 loop:
  mutex_lock (&ihashlock);
  for (ip = ihead[INOHASH (ino)]; ip; ip = ip->i_next) {
    if (ino != ip->i_number)
      continue;
    mutex_unlock (&ihashlock);

    mutex_lock (&ip->i_toplock);

    /* At this point, the inode might have been freed, since we don't
       have a reference.  Check to make sure this is still the right node. */
    if (ip->i_number != ino || !ip->di)
      {
	mutex_unlock (&ip->i_toplock);
	goto loop;
      }
    ip->i_refcnt++;
    *ipp = ip;
    return 0;;
  }
  /*
   * Allocate a new inode structure.
   */
  ip = inodestruct_alloc ();
  ip->di = &((struct dinode *)dinodes)[ino];
  ip->i_number = ino;
  ip->i_translator = MACH_PORT_NULL;
  ip->i_refcnt = 1;
  ip->i_diroff = 0;
  ip->i_sininfo = 0;
  ip->i_dinloc = 0;
  ip->i_fileinfo = 0;
  ip->i_sinloc = 0;
  ip->i_it = 0;
  ip->i_owner = 0;

  ip->flock_type = LOCK_UN;
  ip->shlock_count = 0;
  ip->needflock = 0;
  
  mutex_init (&ip->i_dinlock);
  mutex_init (&ip->i_sinlock);
  mutex_init (&ip->i_datalock);
  mutex_init (&ip->i_itlock);
  mutex_init (&ip->i_toplock);
  
  condition_init (&ip->i_flockwait);
  condition_init (&ip->i_itwait);
  
  if (error = catch_exception ())
    {
      inodestruct_free (ip);
      return error;
    }

  printf ("ino: %d\t"
	  "mode: 0%o\t"
	  "nlink: %d\t"
	  "size: %d\n",
	  ip->i_number,
	  DI_MODE (ip->di),
	  ip->di->di_nlink,
	  ip->di->di_size);

  if (ip->di->di_size > NDADDR * sblock->fs_bsize)
    ip->i_allocsize = blkroundup (ip->di->di_size);
  else
    ip->i_allocsize = fragstoblks (fragroundup (ip->di->di_size));
  
  mutex_lock (&ip->i_toplock);
  ip->i_next = ihead[INOHASH (ino)];
  if (ip->i_next)
    ip->i_next->i_prev = &ip->i_next;
  ip->i_prev = &ihead[INOHASH (ino)];
  ihead[INOHASH (ino)] = ip;
  mutex_unlock (&ihashlock);
  /*
   * Set up a generation number for this inode if it does not
   * already have one. This should only happen on old filesystems.
   */
  if (ip->di->di_gen == 0) {
    if (++nextgennumber < (u_long)time->seconds)
      nextgennumber = time->seconds;
    if (!readonly)
      {
	ip->di->di_gen = nextgennumber;
	ip->di->di_ctime = time->seconds;
      }
  }
  end_catch_exception ();
  *ipp = ip;
  return 0;
}

/*
 * Look up a possibly locked inode by i number and return it.
 * It must have a reference.
 */
struct inode *
ifind (ino_t ino)
{
  struct inode *ip;
  
  mutex_lock (&ihashlock);
  for (ip = ihead[INOHASH (ino)]; ip; ip = ip->i_next)
    {
      if (ino != ip->i_number)
	continue;
      mutex_unlock (&ihashlock);
      
      if (!ip->i_refcnt)
	panic ("ifind: not used");
      
      return ip;
    }
  
  panic ("ifind: not found");
}
  
/*
 * Unlock and decrement the reference count of an inode structure.
 */
void
iput (struct inode *ip)
{
  release_inode (ip, 0);
  mutex_unlock (&ip->i_toplock);
}

void
irele (struct inode *ip)
{
  release_inode (ip, 1);
}
  
static void
release_inode (struct inode *ip, 
	       int checkuse)
{
  int oldmode;
  
  if (--ip->i_refcnt)
    return;
  
  if (checkuse)
    panic ("release_inode");
  
  if ((short int) ip->di->di_nlink < 0)
    panic ("irele: negative link count");
  
  if (!ip->di->di_nlink && !readonly)
    {
      if (catch_exception ())
	return;
      oldmode = DI_MODE (ip->di);
      inode_truncate (ip, 0);
      ip->di->di_model = 0;
      ip->di->di_modeh = 0;
      ip->di->di_rdev = 0;
      ip->di->di_ctime = ip->di->di_mtime = time->seconds;
      end_catch_exception ();
      ifree (ip->i_number, oldmode);
    }

  /* We should also check allocsize here and free disk storage if
     di->di-size hasn't advanced to cover it.  Also, is a synchronous
     update really necessary?  */
  inode_update (ip, 1);

  /* Remove the inode from its hash chain */
  ip->di = 0;
  ip->i_number = 0;
  mutex_lock (&ihashlock);
  *ip->i_prev = ip->i_next;
  if (ip->i_next)
    ip->i_next->i_prev = ip->i_prev;
  mutex_unlock (&ihashlock);
  
  inodestruct_free (ip);	/* does its own mutex_unloc (&ip->i_toplock) */
}

struct protid *
convert_port_to_protid (mach_port_t port)
{
  struct protid *cred;

  /* This works because the receive right holds a reference, and it
     can't delete the last reference (and thus make the port type wrong)
     without claiming credreflock.  */

  mutex_lock (&credreflock);
  if (*(int *)port != PT_PROTID)
    return 0;

  cred = (struct protid *) port;
  cred->refcnt++;
  mutex_unlock (&credreflock);
  return cred;
}

void
release_protidport (struct protid *cred)
{
  mutex_lock (&credreflock);
  if (--cred->refcnt)
    {
      mutex_unlock (&credreflock);
      return;
    }
  mutex_unlock (&credreflock);
  release_peropen (cred->po);
  protid_free (cred);
}  

void
release_peropen (struct peropen *po)
{
  struct inode *ip = po->ip;
  
  mutex_lock (&ip->i_toplock);
  
  if (--po->refcnt)
    {
      mutex_unlock (&ip->i_toplock);
      return;
    }
  
  iput (ip);
  peropen_free (po);
}

/* Make a protid and a peropen for a locked inode */
struct protid *
make_protid (struct inode *ip,
	     int flags,
	     struct protid *parent)
{
  struct peropen *po = peropen_alloc ();
  struct protid *pi = protid_alloc ();
  /* I actually found a legitimate, non-forced, usage of size_t.  Wow. */
  size_t uidsize, gidsize;

  ip->i_refcnt++;

  po->filepointer = 0;
  po->flock_status = LOCK_UN;
  po->refcnt = 1;
  po->openstat = flags;
  po->state = 0;
  po->ip = ip;
  
  if (parent)
    {
      uidsize = (sizeof (struct idlist) + sizeof (uid_t) * parent->nuids);
      gidsize = (sizeof (struct idlist) + sizeof (gid_t) * parent->ngids);
    }
  else
    uidsize = gidsize = 3 * sizeof (uid_t);

  pi->uids = malloc (uidsize);
  pi->gids = malloc (gidsize);
  pi->nuids = parent ? parent->nuids : 1;
  pi->ngids = parent ? parent->ngids : 1;

  if (parent)
    {
      bcopy (parent->uids, pi->uids, uidsize);
      bcopy (parent->gids, pi->gids, gidsize);
    }
  else
    {
      bzero (pi->uids, uidsize);
      bzero (pi->gids, gidsize);
    }

  pi->po = po;
  pi->shared_object = PORT_NULL;
  pi->mapped = 0;
  pi->refcnt = 0;

  return pi;
}

mach_port_t
convert_protid_to_port (struct protid *cred)
{
  if (cred)
    {
      mutex_lock (&credreflock);
      cred->refcnt++;
      mutex_unlock (&credreflock);
      mach_port_insert_right (mach_task_self (), (mach_port_t) cred,
			      (mach_port_t) cred, MACH_MSG_TYPE_MAKE_SEND);
      mach_port_move_member (mach_task_self (), 
			     (mach_port_t) cred, ufs_portset);
      return (mach_port_t) cred;
    }
  else
    return dotdot_file;		/* special hack.  -mib */
}



/* Allocation and destruction routines */


static struct inode *
inodestruct_alloc (void)
{
  struct inode *ip;
  
  mutex_lock (&inodestructlock);
  
  if (inodestructlist)
    {
      ip = inodestructlist;
      inodestructlist = ip->i_nfree;
      mutex_unlock (&inodestructlock);
    }
  else
    {
      mutex_unlock (&inodestructlock);
      ip = malloc (sizeof (struct inode));
    }
  return ip;
}

static void
inodestruct_free (struct inode *ip)
{
  mutex_lock (&inodestructlock);
  mutex_unlock (&ip->i_toplock);
  ip->i_nfree = inodestructlist;
  inodestructlist = ip;
  mutex_unlock (&inodestructlock);
}

static struct peropen *
peropen_alloc (void)
{
  struct peropen *po;
  
  mutex_lock (&peropenlock);
  
  if (peropenlist)
    {
      po = peropenlist;
      peropenlist = po->nfree;
      mutex_unlock (&peropenlock);
    }
  else
    {
      mutex_unlock (&peropenlock);
      po = malloc (sizeof (struct peropen));
    }
  return po;
}

static void
peropen_free (struct peropen *po)
{
  mutex_lock (&peropenlock);
  po->nfree = peropenlist;
  peropenlist = po;
  mutex_unlock (&peropenlock);
}

static struct protid *
protid_alloc (void)
{
  struct protid *pi;
  int err;
  mach_port_t foo;

  mutex_lock (&protidlock);
  
  if (protidlist)
    {
      pi = protidlist;
      protidlist = pi->nfree;
      mutex_unlock (&protidlock);
      pi->porttype = PT_PROTID;
      mach_port_request_notification (mach_task_self (), (mach_port_t) pi,
				      MACH_NOTIFY_NO_SENDERS, 1,
				      (mach_port_t) pi,
				      MACH_MSG_TYPE_MAKE_SEND_ONCE, &foo);
      return pi;
    }
  else
    {
      mutex_unlock (&protidlock);
      pi = malloc (sizeof (struct protid));
    }
  
  err = mach_port_allocate_name (mach_task_self (), MACH_PORT_RIGHT_RECEIVE,
				 (mach_port_t) pi);
  if (err == KERN_NAME_EXISTS)
    {
      /* Isn't this ugly? */
      struct protid *newpi = protid_alloc ();
      free (pi);
      pi = newpi;
    }

  pi->porttype = PT_PROTID;
  mach_port_request_notification (mach_task_self (), (mach_port_t) pi,
				  MACH_NOTIFY_NO_SENDERS, 1, (mach_port_t) pi,
				  MACH_MSG_TYPE_MAKE_SEND_ONCE, &foo);
  return pi;
}

void
protid_free (struct protid *pi)
{
  mutex_lock (&protidlock);
  pi->nfree = protidlist;
  pi->porttype = PT_NONE;
  protidlist = pi;
  mutex_unlock (&protidlock);
}

