/* reserv.c - reserved and pseudo file descriptors
 *
 ****************************************************************
 * Copyright (C) 1998, 2000 Thomas Lord
 * 
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */



#include "hackerlab/os/errno.h"
#include "hackerlab/os/limits.h"
#include "hackerlab/bugs/panic.h"
#include "hackerlab/arrays/ar.h"
#include "hackerlab/vu/vu-bad-arg.h"
#include "hackerlab/vu/vu.h"
#include "hackerlab/vu/reserv.h"
#include "hackerlab/sort/qsort.h"


/************************************************************************
 *(h0 "Reserved File Descriptors and Pseudo-Descriptors"
 *    :include ("vu/reserv.h"))
 *
 * The functions in this section allow programs to allocate file
 * descriptors and pseudo-descriptors.  A pseudo-descriptor is an
 * integer outside of the range of descriptors used by the kernel
 * (`0..(OPEN_MAX - 1)').  Pseudo-descriptors are densely packed
 * (allocated sequentially beginning with `OPEN_MAX') and are suitable
 * for use with the function `vu_set_fd_handler' (see xref:"A Virtual
 * Unix File-System Interface").
 */



/* A dynamically sized array of available pseudo-descriptors.
 */
static t_converter free_reserved_pseudo  = {0};

/* The lowest-numbered pseudo-descriptor (== OPEN_MAX).
 */
static int first_pseudo;


/* The lowest-numbered pseudo-descriptor that has never been allocated.
 */
static int next_pseudo;


/* VU functions
 */

static void * reserve_pseudo_make_closure (void * closure);
static void reserve_pseudo_free_closure (void * closure);
static int reserve_pseudo_close (int * errn, int fd, void * closure);

/* #define reserve_pseudo_make_closure	(vu_make_closure_fn)vu_bad_arg */
/* #define reserve_pseudo_free_closure	(vu_free_closure_fn)vu_bad_arg */
#define reserve_pseudo_access		(vu_access_fn)vu_bad_arg
#define reserve_pseudo_chdir		(vu_chdir_fn)vu_bad_arg
#define reserve_pseudo_chmod		(vu_chmod_fn)vu_bad_arg
#define reserve_pseudo_chown		(vu_chown_fn)vu_bad_arg
#define reserve_pseudo_chroot		(vu_chroot_fn)vu_bad_arg
/* #define reserve_pseudo_close		(vu_close_fn)vu_bad_arg */
#define reserve_pseudo_closedir		(vu_closedir_fn)vu_bad_arg
#define reserve_pseudo_fchdir		(vu_fchdir_fn)vu_bad_arg
#define reserve_pseudo_fchmod		(vu_fchmod_fn)vu_bad_arg
#define reserve_pseudo_fchown		(vu_fchown_fn)vu_bad_arg
#define reserve_pseudo_fstat		(vu_fstat_fn)vu_bad_arg
#define reserve_pseudo_fsync		(vu_fsync_fn)vu_bad_arg
#define reserve_pseudo_ftruncate	(vu_ftruncate_fn)vu_bad_arg
#define reserve_pseudo_link		(vu_link_fn)vu_bad_arg
#define reserve_pseudo_lseek		(vu_lseek_fn)vu_bad_arg
#define reserve_pseudo_lstat		(vu_lstat_fn)vu_bad_arg
#define reserve_pseudo_mkdir		(vu_mkdir_fn)vu_bad_arg
#define reserve_pseudo_open		(vu_open_fn)vu_bad_arg
#define reserve_pseudo_opendir		(vu_opendir_fn)vu_bad_arg
#define reserve_pseudo_read		(vu_read_fn)vu_bad_arg
#define reserve_pseudo_readdir		(vu_readdir_fn)vu_bad_arg
#define reserve_pseudo_readlink		(vu_readlink_fn)vu_bad_arg
#define reserve_pseudo_rename		(vu_rename_fn)vu_bad_arg
#define reserve_pseudo_rmdir		(vu_rmdir_fn)vu_bad_arg
#define reserve_pseudo_stat		(vu_stat_fn)vu_bad_arg
#define reserve_pseudo_symlink		(vu_symlink_fn)vu_bad_arg
#define reserve_pseudo_truncate		(vu_truncate_fn)vu_bad_arg
#define reserve_pseudo_unlink		(vu_unlink_fn)vu_bad_arg
#define reserve_pseudo_utime		(vu_utime_fn)vu_bad_arg
#define reserve_pseudo_write		(vu_write_fn)vu_bad_arg
#define reserve_pseudo_fcntl		(vu_fcntl_fn)vu_bad_arg
#define reserve_pseudo_dup		(vu_dup_fn)vu_bad_arg
#define reserve_pseudo_dup2		(vu_dup2_fn)vu_bad_arg
#define reserve_pseudo_move_state	(vu_dup2_fn)vu_bad_arg

static struct vu_fs_discipline pseudo_fd_functions \
  = { VU_FS_DISCIPLINE_INITIALIZERS (reserve_pseudo_) };

/* local prototypes */
static void init_reserv(void);


/*(c reserv)
 * int reserv (int * errn, int flags);
 * 
 * Allocate a file descriptor by opening `"/dev/null"' by using
 * `vu_open' in the manner descibed by `flags' which may be one of
 * `O_RDONLY', `O_RDWR' etc.
 * 
 * (See xref:"vu_open".)
 */
int
reserv (int * errn, int flags)
{
  return vu_open (errn, "/dev/null", flags, 0);
}




static void
init_reserv ()
{
  static int initialized = 0;
  
  if (!initialized)
    {
      /* Is there really a guarantee that descriptors
       * are densly packed, starting at 0?  If not, 
       * this is bogus.
       */
      next_pseudo = sysconf (_SC_OPEN_MAX);
      if (next_pseudo < 0)
	panic ("I/O error calling sysconf (_SC_OPEN_MAX);\n");
/*
      next_pseudo = getdtablesize ();
      if (next_pseudo < 0)
	next_pseudo = OPEN_MAX;
*/
      first_pseudo = next_pseudo;
      initialized = 1;
    }
}

/*
 * \brief sort ints
 */
static int 
int_cmp (void * a, void * b, void * closure)
{
    int *left = (int *)a;
    int *right = (int *)b;
    return *left - *right;
}

/*
 * \brief trim the cache of reserved available pseudo fd's
 */
static void
trim_reserv_cache(void)
{
  if (first_pseudo == next_pseudo)
      /* we'll be allocating from the start anyway */
      return;

  /* sort the reserved fds */
  /* TODO: reverse sort so that later allocations will take the lowest available 
   * once we've done the trimming.
   */
  quicksort (free_reserved_pseudo.void_ptr, 
	     ar_size (free_reserved_pseudo.void_ptr),
	     sizeof (int),
	     int_cmp,
	     NULL);

  while (ar_size (free_reserved_pseudo.void_ptr))
    {
      if (*(int *)ar_ref (&free_reserved_pseudo.void_ptr, 
		   ar_size (free_reserved_pseudo.void_ptr) - 1,
		   sizeof (int)) == next_pseudo)
	{
	  --next_pseudo;
	  ar_pop_int(&free_reserved_pseudo.int_ptr);
	}
      else
	{
	  /* not the same, we're done */
	  return;
	}
    }
  ar_free (&free_reserved_pseudo.void_ptr);
}

/*(c reserv_pseudo)
 * int reserv_pseudo (int * errn, int flags);
 * 
 * Reserve a pseudo file descriptor suitable for use with
 * `vu_set_fd_handler'.  A pseudo file descriptor can be used with the
 * vu file system functions and is guaranteed not to be the same as
 * any real file descriptor.  (see xref:"A Virtual Unix File-System
 * Interface")
 */
int
reserv_pseudo (int * errn, int flags)
{
  int answer;

  init_reserv ();

  if (ar_size (free_reserved_pseudo.void_ptr))
    answer = ar_pop_int(&free_reserved_pseudo.int_ptr);
  else
    answer = ++next_pseudo;

  vu_set_fd_handler (answer, &pseudo_fd_functions, 0);
  return answer;
}


/*(c reserv_pseudo_ge_n)
 * int reserv_pseudo_ge_n (int * errn, int n, int flags);
 * 
 * Reserve a pseudo file descriptor greater than or equal to `n'
 * suitable for use with `vu_set_fd_handler'.  A pseudo file
 * descriptor can be used with the vu file system functions and is
 * guaranteed not to be the same as any real file descriptor.  (see
 * xref:"A Virtual Unix File-System Interface")
 *
 * This function is useful for implementing `vu_fcntl' with a `cmd'
 * argument `F_DUPFD'.
 */
int
reserv_pseudo_ge_n (int * errn, int n, int flags)
{
  int s;
  int answer;
  
  init_reserv ();

  if (n < first_pseudo)
    n = first_pseudo;

  answer = -1;
  s = ar_size (free_reserved_pseudo.void_ptr);
  if (s)
    {
      int x;
      for (x = 0; x < s; ++x)
	if (   (free_reserved_pseudo.int_ptr[x] >= n)
	    && (free_reserved_pseudo.int_ptr[x] < free_reserved_pseudo.int_ptr[s - 1]))
	  {
	    int tmp;
	    tmp = free_reserved_pseudo.int_ptr[x];
	    free_reserved_pseudo.int_ptr[x] = free_reserved_pseudo.int_ptr[s - 1];
	    free_reserved_pseudo.int_ptr[s - 1] = tmp;
	  }
    }
  
  if (!s || (free_reserved_pseudo.int_ptr[s - 1] < n))
    {
      while (next_pseudo < n)
	{
	  ar_push_int(&free_reserved_pseudo.int_ptr, next_pseudo);
	  ++next_pseudo;
	}
    }
  return reserv_pseudo (errn, flags);
}


/* static void * reserve_pseudo_make_closure (void * closure);
 * 
 * Do nothing. This is the `make_closure' function in the vtable
 * `pseudo_fd_functions'.
 */
static void * 
reserve_pseudo_make_closure (void * closure)
{
  return closure;
}


/* static void reserve_pseudo_free_closure (void * closure);
 * 
 * Do nothing. This is the `free_closure' function in the vtable
 * `pseudo_fd_functions'.
 */
static void
reserve_pseudo_free_closure (void * closure)
{
}

/* static int reserve_pseudo_close (int * errn, int fd, void * closure);
 * 
 * Close a pseudo-file descriptor.  This is the `close' function in
 * the vtable `pseudo_fd_functions'.
 */
static int
reserve_pseudo_close (int * errn, int fd, void * closure)
{
  ar_push_int (&free_reserved_pseudo.int_ptr, fd);
  trim_reserv_cache();
  return 0;
}


/*(c unreserv_pseudo)
 * void unreserv_pseudo (int fd);
 * 
 * Release a pseudo-descriptor previously allocated by
 * `reserved_pseudo'.
 */
void
unreserv_pseudo (int fd)
{
  ar_push_int (&free_reserved_pseudo.int_ptr, fd);
  trim_reserv_cache();
}


syntax highlighted by Code2HTML, v. 0.9.1