/* Copyright (C) 1992 Free Software Foundation, Inc.
This file is part of the GNU C Library.

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

The GNU C Library 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
Library General Public License for more details.

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

#ifndef	_HURD_H

#define	_HURD_H	1

#define	_GNU_SOURCE
#include <features.h>

#include <mach.h>
#include <hurd/hurd_types.h>
#include <hurd/process.h>
#include <hurd/fs.h>
#include <hurd/io.h>
#include <errno.h>

#define	__hurd_fail(err)	(errno = (err), -1)

/* Lightweight user references for ports.  */

/* Structure describing a cell containing a port.
   With the lock held, a user extracts PORT, and sets USER_DEALLOC to point
   to a word in his local storage.  PORT can then safely be used.  When
   PORT is no longer needed, with the lock held, the user examines
   USER_DEALLOC.  If it is the same address that user stored there, it
   extracts *USER_DEALLOC, clears USER_DEALLOC to NULL, and releases the
   lock.  If *USER_DEALLOC is set, the user deallocates the port he used.  */
struct _hurd_port
  {
    spin_lock_t lock;		/* Locks rest.  */
    mach_port_t port;		/* Port. */
    int *user_dealloc;		/* If not NULL, points to user's flag word.  */
  };

/* Evaluate EXPR with the variable `port' bound to the port in PORTCELL.  */
#define	_HURD_PORT_USE(portcell, expr)					      \
  ({ struct _hurd_port *const __p = &(portcell);			      \
     int __dealloc;							      \
     const mach_port_t port = _hurd_port_get (__p, &__dealloc);		      \
     __typeof(expr) __result = (expr);					      \
     _hurd_port_free (__p, &__dealloc);					      \
     __result; })

/* Initialize *PORT to INIT.  */
static inline void
_hurd_port_init (struct _hurd_port *port, mach_port_t init)
{
  __spin_lock_init (&port->lock);
  port->port = init;
  port->user_dealloc = NULL;
}

/* Get a reference to *PORT, which is locked.
   Pass return value and MYFLAG to _hurd_port_free when done.  */
static inline mach_port_t
_hurd_port_locked_get (struct _hurd_port *port, int *myflag)
{
  mach_port_t result;
  result = port->port;
  if (result != MACH_PORT_NULL)
    {
      port->user_dealloc = myflag;
      *myflag = 0;
    }
  __spin_unlock (&port->lock);
  return result;
}

/* Same, but locks PORT first.  */
static inline mach_port_t
_hurd_port_get (struct _hurd_port *port, int *myflag)
{
  __spin_lock (&port->lock);
  return _hurd_port_locked_get (port, myflag);
}

/* Free a reference gotten with
   `USED_PORT = _hurd_port_get (PORT, MYFLAG);' */
static inline void
_hurd_port_free (struct _hurd_port *port,
		 int *myflag, mach_port_t used_port)
{
  __spin_lock (&port->lock);
  if (port->user_dealloc == myflag)
    port->user_dealloc = NULL;
  __spin_unlock (&port->lock);
  if (*myflag)
    __mach_port_deallocate (__mach_task_self (), used_port);
}

/* Set *PORT's port to NEWPORT.  PORT is locked.  */
static inline void
_hurd_port_locked_set (struct _hurd_port *port, mach_port_t newport)
{
  mach_port_t old;
  if (port->user_dealloc == NULL)
    old = port->port;
  else
    {
      old = MACH_PORT_NULL;
      *port->user_dealloc = 1;
    }
  port->port = newport;
  __spin_unlock (&port->lock);
  if (old != MACH_PORT_NULL)
    __mach_port_deallocate (__mach_task_self (), old);
}

/* Same, but locks PORT first.  */
static inline void
_hurd_port_set (struct _hurd_port *port, mach_port_t newport)
{
  __spin_lock (&port->lock);
  return _hurd_port_locked_set (port, newport);
}

/* Basic ports and info, initialized by startup.  */
extern struct _hurd_port _hurd_proc, _hurd_auth;
extern struct _hurd_port _hurd_ccdir, _hurd_cwdir, _hurd_crdir;
extern volatile mode_t _hurd_umask;

extern struct mutex _hurd_ctty_lock;
extern int _hurd_ctty_fstype;
extern fsid_t _hurd_ctty_fsid;
extern ino_t _hurd_ctty_fileid;

extern vm_address_t _hurd_stack_low, _hurd_stack_high; /* Not locked.  */

extern thread_t _hurd_msgport_thread;
extern mach_port_t _hurd_msgport; /* Locked by _hurd_siglock.  */

/* Not locked.  If we are using a real dtable,
   these are turned into that and then cleared at startup.
   If not, these are never changed after startup.  */
extern mach_port_t *_hurd_init_dtable;
extern size_t _hurd_init_dtablesize;

/* File descriptor table.  */
struct _hurd_fd
  {
    struct _hurd_port port;
    int flags;			/* fcntl flags; locked by port.lock.  */

    /* Normal port to the ctty.  Also locked by port.lock.
       (The ctty.lock is only ever used when the port.lock is held.)  */
    struct _hurd_port ctty;
  };

struct _hurd_dtable
  {
    int size;			/* Number of elts in `d' array.  */

    /* Uses of individual descriptors are not locked.  It is up to the user
       to synchronize descriptor operations on a single descriptor.  */

    struct _hurd_fd *d;
  };
extern struct mutex _hurd_dtable_lock; /* Locks next two.  */
extern struct _hurd_dtable _hurd_dtable;
extern int _hurd_dtable_rlimit;

/* If not NULL, pointed-to word is set when _hurd_dtable.d changes.
   User who set `user_dealloc' should free the _hurd_dtable.d value
   he used if his word is set when he is finished.
   If NULL, the old value of _hurd_dtable.d is freed by the setter.  */
int *_hurd_dtable_user_dealloc;

static inline struct _hurd_dtable
_hurd_dtable_use (int *dealloc)
{
  struct _hurd_dtable dtable;
  __mutex_lock (&_hurd_dtable_lock);
  _hurd_dtable_user_dealloc = dealloc;
  dtable = _hurd_dtable;
  __mutex_unlock (&_hurd_dtable_lock);
  return dtable;
}

struct _hurd_dtable_resizes
  {
    size_t n;
    void (*free) (void *);
    void *terminator;
  };
extern const struct _hurd_dtable_resizes _hurd_dtable_resizes;

static inline void
_hurd_dtable_done (struct _hurd_dtable dtable, int *dealloc)
{
  __mutex_lock (&_hurd_dtable_lock);
  if (_hurd_dtable_user_dealloc == dealloc)
    _hurd_dtable_user_dealloc = NULL;
  __mutex_unlock (&_hurd_dtable_lock);
  if (*dealloc)
    /* _hurd_dtable_resizes is a symbol set.
       setdtablesize.c gives it one element: free.
       If setdtablesize is not linked in, *DEALLOC
       will never get set, so we will never get here.
       This hair avoids linking in free if we don't need it.  */
    (*_hurd_dtable_resizes.free) (dtable);
}


/* Allocate a new file descriptor and set it to PORT.
   If the table is full, deallocate PORT, set errno, and return -1.  */
static inline int
_hurd_dalloc (io_t port, io_t ctty, int flags)
{
  int i;
  __mutex_lock (&hurd_dtable_lock);
  for (i = 0; i < _hurd_dtable.size; ++i)
    {
      struct _hurd_fd *d = &_hurd_dtable.d[i];
      __spin_lock (&d->port.lock);
      if (d->port.port == MACH_PORT_NULL)
	{
	  d->port.port = port;
	  d->port.user_dealloc = NULL;
	  d->ctty.port = ctty;
	  d->ctty.user_dealloc = NULL;
	  d->flags = flags;
	  __spin_unlock (&d->port.lock);
	  __mutex_unlock (&hurd_dtable_lock);
	  return i;
	}
      __spin_unlock (&d->port.lock);
    }
  __mutex_unlock (&hurd_dtable_lock);
  __mach_port_deallocate (__mach_task_self (), port);
  __mach_port_deallocate (__mach_task_self (), ctty);
  errno = EMFILE;
  return -1;
}

/* Returns the descriptor cell for FD in DTABLE, locked.  */
static inline struct _hurd_fd *
_hurd_dtable_fd (int fd, struct _hurd_dtable dtable)
{
  if (fd < 0 || fd >= dtable.size)
    return NULL;
  else
    {
      struct _hurd_fd *cell = &dtable.d[fd];
      __spin_lock (&cell->port.lock);
      if (cell->port.port == MACH_PORT_NULL)
	{
	  __spin_unlock (&cell->port.lock);
	  return NULL;
	}
      return cell;
    }
}

struct _hurd_fd_user
  {
    struct _hurd_dtable dtable;
    struct _hurd_fd *d;
  };

/* Returns the descriptor cell for FD, locked.  */
static inline struct _hurd_fd_user
_hurd_fd (int fd, int *dealloc)
{
  struct _hurd_fd_user d;
  d.dtable = _hurd_dtable_use (dealloc);
  d.d = _hurd_dtable_fd (fd, dtable);
  if (d.d == NULL)
    _hurd_dtable_done (d.dtable, dealloc);
  return d;
}

static inline void
_hurd_fd_done (struct _hurd_fd_user d, int *dealloc)
{
  _hurd_dtable_done (d->dtable, dealloc);n
}

/* Evaluate EXPR with the variable `port' bound to the port to FD,
   and `ctty' bound to the ctty port.  */
   
#define	_HURD_DPORT_USE(fd, expr)					      \
  ({ int __dealloc_dt;							      \
     struct _hurd_fd_user __d = _hurd_fd (fd, &__dealloc_dt);		      \
     if (__cell.d == NULL)						      \
       EBADF;								      \
     else								      \
       {								      \
	 int __dealloc = 0, __dealloc_ctty = 0;				      \
	 io_t port = _hurd_port_locked_get (&__d.d->port, &__dealloc);	      \
	 io_t ctty = _hurd_port_locked_get (&__d.d->ctty, &__dealloc_ctty);   \
	 __typeof (expr) __result;					      \
	 __result = (expr);						      \
	 _hurd_port_free (&__d.d->port, port, &__dealloc);		      \
	 if (ctty != MACH_PORT_NULL)					      \
	   _hurd_port_free (&__d.d->ctty, ctty, &__dealloc_ctty);	      \
	 _hurd_fd_done (__d, &__dealloc_dt);				      \
	 __result;							      \
       }								      \
   })									      \

static inline int
_hurd_dfail (int fd, error_t err)
{
  int signo;
  switch (err)
    {
    case MACH_SEND_INVALID_DEST: /* The server has disappeared!  */
      _hurd_raise_signal (NULL, SIGLOST, fd);
      break;
    case EPIPE:
      _hurd_raise_signal (NULL, SIGPIPE, fd);
      break;
    }
  return __hurd_fail (err);
}

/* Return the socket server for sockaddr domain DOMAIN.  */
extern socket_t _hurd_socket_server (int domain);

/* Return a receive right which will not be sent to.  */
extern mach_port_t _hurd_dead_recv (void);


/* Current process IDs.  */
extern pid_t _hurd_pid, _hurd_ppid, _hurd_pgrp;
extern int _hurd_orphaned;


/* User and group IDs.  */
extern mutex_t _hurd_idlock;
extern int _hurd_id_valid;	/* Nonzero if _hurd_id is valid.  */
extern idblock_t _hurd_id;
extern auth_t _hurd_rid_auth;	/* Cache used by access.  */


/* Unix `data break', for brk and sbrk.
   If brk and sbrk are not used, this info will not be initialized or used.  */
extern vm_address_t _hurd_brk;	/* Data break.  */
extern vm_address_t _hurd_data_end; /* End of allocated space.  */
extern struct mutex _hurd_brk_lock; /* Locks brk and data_end.  */
extern int _hurd_set_data_limit (const struct rlimit *);

/* Set the data break; the brk lock must
   be held, and is released on return.  */
extern int _hurd_set_brk (vm_address_t newbrk);

/* Resource limit on core file size.  Enforced by hurdsig.c.  */
extern int _hurd_core_limit;

#include <signal.h>

/* Per-thread signal state.  */
struct _hurd_sigstate
  {
    thread_t thread;
    struct _hurd_sigstate *next; /* Linked-list of thread sigstates.  */

    struct mutex lock;		/* Locks the rest of this structure.  */
    sigset_t blocked;
    sigset_t pending;
    struct sigaction actions[NSIG];
    struct sigaltstack sigaltstack;
    int sigcodes[NSIG];		/* Codes for pending signals.  */

    int suspended;		/* If nonzero, sig_post signals `arrived'.  */
    struct condition arrived;

    int vforked;		/* Nonzero if this thread is a vfork child.  */
    struct
      {
	process_t proc;
	file_t ccdir, cwdir, crdir, auth;
	mode_t umask;
	int ctty_fstype;
	fsid_t ctty_fsid;
	ino_t ctty_fileid;
	struct _hurd_dtable *dtable;
	jmp_buf continuation;
      } *vfork_saved;

    /* Not locked.  Used only by this thread,
       or by signal thread with this thread suspended.  */
    mach_port_t intr_port;	/* Port an interruptible RPC was sent on.  */
    int intr_restart;		/* If nonzero, restart interrupted RPC.  */
  };
/* Linked list of states of all threads
   whose state has been inquired about.  */
extern struct _hurd_sigstate *_hurd_sigstates;
extern struct mutex _hurd_siglock; /* Locks _hurd_sigstates.  */
/* Get the sigstate of a given thread, taking its lock.  */
extern struct _hurd_sigstate *_hurd_thread_sigstate (thread_t);

/* Thread to receive process-global signals.  */
extern thread_t _hurd_sigthread;

/* Called by the machine-dependent exception handler.  */
extern void _hurd_exc_post_signal (thread_t, int sig, int code);

/* SS->lock is held on entry, and released before return.  */
extern void _hurd_internal_post_signal (struct _hurd_sigstate *ss,
					int signo, int sigcode,
					sigset_t *restore_blocked);

/* Function run by the signal thread to receive from the signal port.  */
extern void _hurd_msgport_receive (void);


/* Perform interruptible RPC CALL on PORT.
   The args in CALL should be constant or local variable refs.
   They may be evaluated many times, and must not change.
   PORT must not be deallocated before this RPC is finished.  */
#define	_HURD_EINTR_RPC(port, call) \
  ({
    error_t __err;
    struct _hurd_sigstate *__ss
      = _hurd_thread_sigstate (__mach_thread_self ());
    __mutex_unlock (&__ss->lock); /* Lock not needed.  */
    /* If we get a signal and should return EINTR, the signal thread will
       clear this.  The RPC might return EINTR when some other thread gets
       a signal, in which case we want to restart our call.  */
    __ss->intr_restart = 1;
    /* This one needs to be last.  A signal can arrive before here,
       and if intr_port were set before intr_restart are
       initialized, the signal thread would get confused.  */
    __ss->intr_port = (port);
    /* A signal may arrive here, after intr_port is set,
       but before the mach_msg system call.  The signal handler might do an
       interruptible RPC, and clobber intr_port; then it would not be set
       properly when we actually did send the RPC, and a later signal
       wouldn't interrupt that RPC.  So, _hurd_run_sighandler saves
       intr_port in the sigcontext, and sigreturn restores them.  */
  __do_call:
    switch (__err = (call))
      {
      case EINTR:		/* RPC went out and was interrupted.  */
      case MACH_SEND_INTERRUPTED: /* RPC didn't get out.  */
	if (__ss->intr_restart)
	  /* Restart the interrupted call.  */
	  goto __do_call;
	/* Return EINTR.  */
	__err = EINTR;
	break;
      case MACH_RCV_PORT_DIED:
	/* Server didn't respond to interrupt_operation,
	   so the signal thread destroyed the reply port.  */
	__err = EINTR;
	break;
      }
    __ss->intr_port = MACH_PORT_NULL;
    __err;
  })

/* Calls to get and set basic ports.  */
extern process_t getproc (void);
extern file_t getccdir (void), getcwdir (void), getcrdir (void);
extern auth_t getauth (void);
extern int setproc (process_t);
extern int setccdir (file_t), setcwdir (file_t), setcrdir (file_t);

/* Does reauth with the proc server and fd io servers.  */
extern int __setauth (auth_t), setauth (auth_t);
#define	setauth	__setauth


extern error_t __hurd_path_split (file_t crdir, file_t cwdir,
				  const char *file,
				  file_t *dir, const char **name);
#define	hurd_path_split	__hurd_path_split
extern error_t __hurd_path_lookup (file_t crdir, file_t cwdir,
				   const char *file,
				   int flags, mode_t mode,
				   file_t *file);
#define	hurd_path_lookup __hurd_path_lookup

/* Returns a port to the directory, and sets *NAME to the file name.  */
extern file_t __path_split (const char *file, const char **name);
#define	path_split	__path_split

/* Looks up FILE with the given FLAGS and MODE (as for dir_pathtrans).  */
extern file_t __path_lookup (const char *file, int flags, mode_t mode);
#define path_lookup __path_lookup

/* Open a file descriptor on a port.  */
extern int openport (io_t port);


#endif	/* hurd.h */
