/*
|| This file is part of Pike. For copyright information see COPYRIGHT.
|| Pike is distributed under GPL, LGPL and MPL. See the file COPYING
|| for more information.
*/

#include "global.h"
#include "fdlib.h"
#include "interpret.h"
#include "svalue.h"
#include "bignum.h"
#include "mapping.h"
#include "object.h"
#include "builtin_functions.h"
#include "operators.h"
#include "program_id.h"
#include "file_machine.h"
#include "pike_types.h"

#include <sys/stat.h>
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif /* HAVE_SYS_PARAM_H */

#define sp Pike_sp
#define fp Pike_fp

/*! @module Stdio
 */
/*! @class Stat
 *!
 *!   This object is used to represent file status information
 *!   from e.g. @[file_stat()].
 *!
 *!   It contains the following items usually found in a C @tt{struct
 *!   stat@}:
 *!   @dl
 *!     @item mode
 *!       File mode (see @tt{mknod(2)@}).
 *!     @item size
 *!       File size in bytes.
 *!     @item uid
 *!       User ID of the file's owner.
 *!     @item gid
 *!       Group ID of the file's owner.
 *!     @item atime
 *!       Time of last access in seconds since 00:00:00 UTC, 1970-01-01.
 *!     @item mtime
 *!       Time of last data modification.
 *!     @item ctime
 *!       Time of last file status change.
 *!     @item atime_nsec
 *!       Time of last access in nanoseconds, added to atime to get
 *!       sub-second time
 *!     @item mtime_nsec
 *!       Time of last modification in nanoseconds, added to mtime to get
 *!       sub-second time
 *!     @item ctime_nsec
 *!       Time of last file status change in nanoseconds, added to ctime to
 *!       get sub-second time
 *!     @item ino
 *!       Inode number.
 *!     @item nlink
 *!       Number of links.
 *!     @item dev
 *!       ID of the device containing a directory entry for this file.
 *!     @item rdev
 *!       ID of the device.
 *!   @enddl
 *!
 *!   It also contains some items that correspond to the C @tt{IS*@} macros:
 *!   @dl
 *!     @item isreg
 *!       Set if the file is a regular file.
 *!     @item isdir
 *!       Set if the file is a directory.
 *!     @item islnk
 *!       Set if the file is a symbolic link. Note that symbolic links
 *!       are normally followed by the stat functions, so this might
 *!       only be set if you turn that off, e.g. by giving a nonzero
 *!       second argument to @[file_stat()].
 *!     @item isfifo
 *!       Set if the file is a FIFO (aka named pipe).
 *!     @item issock
 *!       Set if the file is a socket.
 *!     @item ischr
 *!       Set if the file is a character device.
 *!     @item isblk
 *!       Set if the file is a block device.
 *!   @enddl
 *!
 *!   There are also some items that provide alternative representations
 *!   of the above:
 *!   @dl
 *!     @item type
 *!       The type as a string, can be any of @expr{"reg"@},
 *!       @expr{"dir"@}, @expr{"lnk"@}, @expr{"fifo"@}, @expr{"sock"@},
 *!       @expr{"chr"@}, @expr{"blk"@}, and @expr{"unknown"@}.
 *!     @item mode_string
 *!       The file mode encoded as a string in @tt{ls -l@} style, e.g.
 *!       @expr{"drwxr-xr-x"@}.
 *!   @enddl
 *!
 *!   Note that some items might not exist or have meaningful values
 *!   on some platforms.
 *!
 *!   Additionally, the object may be initialized from or casted to an
 *!   @expr{array@} on the form of a 'traditional' LPC stat-array, and
 *!   it's also possible to index the object directly with integers as
 *!   if it were such an array. The stat-array has this format:
 *!
 *!   @array
 *!     @elem int 0
 *!       File mode, same as @tt{mode@}.
 *!     @elem int 1
 *!       If zero or greater, the file is a regular file and this is
 *!       its size in bytes. If less than zero it gives the type:
 *!       -2=directory, -3=symlink and -4=device.
 *!     @elem int 2
 *!       Time of last access, same as @tt{atime@}.
 *!     @elem int 3
 *!       Time of last data modification, same as @tt{mtime@}.
 *!     @elem int 4
 *!       Time of last file status change, same as @tt{ctime@}.
 *!     @elem int 5
 *!       User ID of the file's owner, same as @tt{uid@}.
 *!     @elem int 6
 *!       Group ID of the file's owner, same as @tt{gid@}.
 *!   @endarray
 *!
 *!   It's possible to modify the stat struct by assigning values to
 *!   the items. They essentially work as variables, although some of
 *!   them affect others, e.g. setting @expr{isdir@} clears @expr{isreg@}
 *!   and setting @expr{mode_string@} changes many of the other items.
 */

/* Let's define these mode flags if they don't exist, so reading and
 * writing the Stat structure will behave identically regardless of
 * OS. */

#ifndef S_IFMT
#define S_IFMT	0xf000
#endif /* !S_IFMT */
#ifndef S_IFREG
#define S_IFREG	0x8000
#endif /* !S_IFREG */
#ifndef S_IFLNK
#define S_IFLNK	0xA000
#endif /* !S_IFLNK */
#ifndef S_IFDIR
#define S_IFDIR	0x4000
#endif /* !S_IFDIR */
#ifndef S_IFCHR
#define S_IFCHR	0x2000
#endif /* !S_IFCHR */
#ifndef S_IFBLK
#define S_IFBLK	0x6000
#endif /* !S_IFBLK */
#ifndef S_IFIFO
#define S_IFIFO	0x1000
#endif /* !S_IFIFO */
#ifndef S_IFSOCK
#define S_IFSOCK 0xC000
#endif /* !S_IFSOCK */

#ifndef S_IRUSR
#define S_IRUSR 0400
#endif /* !S_IRUSR */
#ifndef S_IWUSR
#define S_IWUSR 0200
#endif /* !S_IWUSR */
#ifndef S_IXUSR
#define S_IXUSR 0100
#endif /* !S_IXUSR */
#ifndef S_IRGRP
#define S_IRGRP 040
#endif /* !S_IRGRP */
#ifndef S_IWGRP
#define S_IWGRP 020
#endif /* !S_IWGRP */
#ifndef S_IXGRP
#define S_IXGRP 010
#endif /* !S_IXGRP */
#ifndef S_IROTH
#define S_IROTH 04
#endif /* !S_IROTH */
#ifndef S_IWOTH
#define S_IWOTH 02
#endif /* !S_IWOTH */
#ifndef S_IXOTH
#define S_IXOTH 01
#endif /* !S_IXOTH */

#ifndef S_ISUID
#define S_ISUID 0x800
#endif /* !S_ISUID */
#ifndef S_ISGID
#define S_ISGID 0x400
#endif /* !S_ISGID */
#ifndef S_ISVTX
#define S_ISVTX 0x200
#endif /* !S_ISVTX */

PMOD_EXPORT struct program *stat_program=NULL;

struct stat_storage
{
   /* Note: stat_create assumes there are no refs in here. */
   PIKE_STAT_T s;
};

static struct mapping *stat_map=NULL;

enum stat_query
{STAT_DEV=1, STAT_INO, STAT_MODE, STAT_NLINK, STAT_UID, STAT_GID, STAT_RDEV, 
 STAT_SIZE,
#ifdef HAVE_STRUCT_STAT_BLOCKS
 STAT_BLKSIZE, STAT_BLOCKS,
#endif
 STAT_ATIME, STAT_MTIME, STAT_CTIME,
 STAT_ATIME_NSEC,STAT_MTIME_NSEC, STAT_CTIME_NSEC,
/* is... */
 STAT_ISLNK, STAT_ISREG, STAT_ISDIR, STAT_ISCHR, 
 STAT_ISBLK, STAT_ISFIFO, STAT_ISSOCK,
/* special */
 STAT_TYPE, STAT_MODE_STRING,
/* end marker */
 STAT_ENUM_END};

static struct pike_string *stat_index_strs[STAT_ENUM_END];

static struct pike_string *str_type_reg, *str_type_dir, *str_type_lnk,
  *str_type_chr, *str_type_blk, *str_type_fifo, *str_type_sock, *str_type_unknown;

#define THIS_STAT ((struct stat_storage*)(Pike_fp->current_storage))

static void stat_index_set (INT32 args);
static void _stat_index_set (INT_TYPE pos, struct svalue *val, int is_int, INT64 intval);

static int stat_compat_set (INT_TYPE pos, INT64 val)
{
  if (pos < 0) pos += 7;
  switch (pos) {
    case 0: DO_NOT_WARN(THIS_STAT->s.st_mode = (int) val); break;
    case 1:
      if (val >= 0) {
	THIS_STAT->s.st_mode = (THIS_STAT->s.st_mode & ~S_IFMT) | S_IFREG;
	THIS_STAT->s.st_size = DO_NOT_WARN ((off_t) val);
      }
      else {
	THIS_STAT->s.st_size = 0;
	if (val == -2)
	  THIS_STAT->s.st_mode = (THIS_STAT->s.st_mode & ~S_IFMT) | S_IFDIR;
	else if (val == -3)
	  THIS_STAT->s.st_mode = (THIS_STAT->s.st_mode & ~S_IFMT) | S_IFLNK;
	else
	  THIS_STAT->s.st_mode = THIS_STAT->s.st_mode & ~S_IFMT;
      }
      break;
    case 2: THIS_STAT->s.st_atime = DO_NOT_WARN ((time_t) val); break;
    case 3: THIS_STAT->s.st_mtime = DO_NOT_WARN ((time_t) val); break;
    case 4: THIS_STAT->s.st_ctime = DO_NOT_WARN ((time_t) val); break;
    case 5: DO_NOT_WARN(THIS_STAT->s.st_uid = (int) val); break;
    case 6: DO_NOT_WARN(THIS_STAT->s.st_gid = (int) val); break;
    default: return 0;
  }
  return 1;
}

static void stat_push_compat(INT_TYPE n)
{
   if (n < 0) n += 7;
   switch (n)
   {
      case 0: push_int(THIS_STAT->s.st_mode); break;
      case 1:
	 switch(S_IFMT & THIS_STAT->s.st_mode)
	 {
	    case S_IFREG:
	       push_int64(THIS_STAT->s.st_size);
	       break;

	    case S_IFDIR: push_int(-2); break;
	    case S_IFLNK: push_int(-3); break;
	    default:
#ifdef DEBUG_FILE
	       fprintf(stderr, "encode_stat(): mode:%ld\n",
		       (long)S_IFMT & THIS_STAT->s.st_mode);
#endif /* DEBUG_FILE */
	       push_int(-4);
	       break;
	 }
	 break;
      case 2: push_int64(THIS_STAT->s.st_atime); break;
      case 3: push_int64(THIS_STAT->s.st_mtime); break;
      case 4: push_int64(THIS_STAT->s.st_ctime); break;
      case 5: push_int(THIS_STAT->s.st_uid); break;
      case 6: push_int(THIS_STAT->s.st_gid); break;
      default:
      {
	 INT32 args=1;
	 SIMPLE_BAD_ARG_ERROR("`[]",1,"int(0..6)");
      }
   }
}

static void stat_init (struct object *UNUSED(o))
{
  memset (&THIS_STAT->s, 0, sizeof (THIS_STAT->s));
}

/*! @decl void create (void|object|array stat);
 *!
 *! A new @[Stdio.Stat] object can be initialized in two ways:
 *!
 *! @ul
 *!   @item
 *!     @[stat] is an object, typically another @[Stdio.Stat]. The
 *!     stat info is copied from the object by getting the values of
 *!     @expr{mode@}, @expr{size@}, @expr{atime@}, @expr{mtime@},
 *!     @expr{ctime@}, @expr{uid@}, @expr{gid@}, @expr{dev@}, @expr{ino@},
 *!     @expr{nlink@}, and @expr{rdev@}.
 *!   @item
 *!     @[stat] is a seven element array on the 'traditional' LPC
 *!     stat-array form (see the class doc).
 *! @endul
 */
void f_min(INT32 args);
void f_max(INT32 args);

static void _stat_index(INT_TYPE code)
{
  if (!code) {
    /* Fall back to a normal index on this object, in case
     * someone inherited us. */
    struct svalue res;
    object_index_no_free2 (&res, fp->current_object, 0, sp-1);
    pop_stack();
    *sp++ = res;
    return;
  }

  switch (code)
  {
    case STAT_DEV: push_int(THIS_STAT->s.st_dev); break;
    case STAT_INO: push_int(THIS_STAT->s.st_ino); break;
    case STAT_MODE: push_int(THIS_STAT->s.st_mode); break;
    case STAT_NLINK: push_int(THIS_STAT->s.st_nlink); break;
    case STAT_UID: push_int(THIS_STAT->s.st_uid); break;
    case STAT_GID: push_int(THIS_STAT->s.st_gid); break;
    case STAT_RDEV: push_int(THIS_STAT->s.st_rdev); break;
    case STAT_SIZE: push_int64(THIS_STAT->s.st_size); break;
#ifdef HAVE_STRUCT_STAT_BLOCKS
    case STAT_BLKSIZE: push_int(THIS_STAT->s.st_blksize); break;
    case STAT_BLOCKS: push_int(THIS_STAT->s.st_blocks); break;
#endif
    case STAT_ATIME: push_int64(THIS_STAT->s.st_atime); break;
    case STAT_MTIME: push_int64(THIS_STAT->s.st_mtime); break;
    case STAT_CTIME: push_int64(THIS_STAT->s.st_ctime); break;
#ifdef HAVE_STRUCT_STAT_NSEC
    case STAT_ATIME_NSEC: push_int64(THIS_STAT->s.st_atim.tv_nsec); break;
    case STAT_MTIME_NSEC: push_int64(THIS_STAT->s.st_mtim.tv_nsec); break;
    case STAT_CTIME_NSEC: push_int64(THIS_STAT->s.st_ctim.tv_nsec); break;
#else
    case STAT_ATIME_NSEC: case STAT_MTIME_NSEC:    case STAT_CTIME_NSEC:
      push_int(0);
      break;
#endif
    case STAT_ISREG:
      push_int((THIS_STAT->s.st_mode & S_IFMT) == S_IFREG); break;
    case STAT_ISLNK:
      push_int((THIS_STAT->s.st_mode & S_IFMT) == S_IFLNK); break;
    case STAT_ISDIR:
      push_int((THIS_STAT->s.st_mode & S_IFMT) == S_IFDIR); break;
    case STAT_ISCHR:
      push_int((THIS_STAT->s.st_mode & S_IFMT) == S_IFCHR); break;
    case STAT_ISBLK:
      push_int((THIS_STAT->s.st_mode & S_IFMT) == S_IFBLK); break;
    case STAT_ISFIFO:
      push_int((THIS_STAT->s.st_mode & S_IFMT) == S_IFIFO); break;
    case STAT_ISSOCK:
      push_int((THIS_STAT->s.st_mode & S_IFMT) == S_IFSOCK); break;

    case STAT_TYPE:
      switch (THIS_STAT->s.st_mode & S_IFMT)
      {
        case S_IFREG: ref_push_string(str_type_reg); break;
        case S_IFDIR: ref_push_string(str_type_dir); break;
        case S_IFLNK: ref_push_string(str_type_lnk); break;
        case S_IFCHR: ref_push_string(str_type_chr); break;
        case S_IFBLK: ref_push_string(str_type_blk); break;
        case S_IFIFO: ref_push_string(str_type_fifo); break;
        case S_IFSOCK: ref_push_string(str_type_sock); break;
        default: ref_push_string(str_type_unknown); break;
      }
      break;

    case STAT_MODE_STRING:
      switch (THIS_STAT->s.st_mode & S_IFMT)
      {
        case S_IFREG:
          push_text("-");
          break;
        case S_IFDIR:
          push_text("d");
          break;
        case S_IFLNK:
          push_text("l");
          break;
        case S_IFCHR:
          push_text("c");
          break;
        case S_IFBLK:
          push_text("b");
          break;
        case S_IFIFO:
          push_text("f");
          break;
        case S_IFSOCK:
          push_text("s");
          break;
        default:
          push_text("?");
          break;
      }

      if ( (THIS_STAT->s.st_mode & S_IRUSR) )
        push_text("r");
      else
        push_text("-");

      if ( (THIS_STAT->s.st_mode & S_IWUSR) )
        push_text("w");
      else
        push_text("-");

      if ( (THIS_STAT->s.st_mode & S_ISUID) )
        if ( (THIS_STAT->s.st_mode & S_IXUSR) )
          push_text("s");
        else
          push_text("S");
      else
        if ( (THIS_STAT->s.st_mode & S_IXUSR) )
          push_text("x");
        else
          push_text("-");

      if ( (THIS_STAT->s.st_mode & S_IRGRP) )
        push_text("r");
      else
        push_text("-");

      if ( (THIS_STAT->s.st_mode & S_IWGRP) )
        push_text("w");
      else
        push_text("-");

      if ( (THIS_STAT->s.st_mode & S_ISGID) )
        if ( (THIS_STAT->s.st_mode & S_IXGRP) )
          push_text("s");
        else
          push_text("S");
      else
        if ( (THIS_STAT->s.st_mode & S_IXGRP) )
          push_text("x");
        else
          push_text("-");

      if ( (THIS_STAT->s.st_mode & S_IROTH) )
        push_text("r");
      else
        push_text("-");

      if ( (THIS_STAT->s.st_mode & S_IWOTH) )
        push_text("w");
      else
        push_text("-");

      if ( (THIS_STAT->s.st_mode & S_ISVTX) )
        if ( (THIS_STAT->s.st_mode & S_IXOTH) )
          push_text("t");
        else
          push_text("T");
      else
        if ( (THIS_STAT->s.st_mode & S_IXOTH) )
          push_text("x");
        else
          push_text("-");

      f_add(10);
      break;
#ifdef PIKE_DEBUG
    default:
      Pike_fatal ("stat_index is not kept up-to-date with stat_map.\n");
#endif
  }
}

static void stat_index(INT32 args)
{
  if( !args )
    SIMPLE_TOO_FEW_ARGS_ERROR("Stat `[]",1);
  else if( args == 1 )
  {
    if (TYPEOF(sp[-1]) == T_INT)
    {
      int index = sp[-1].u.integer;
      pop_stack();
      stat_push_compat(index);
    }
    else if( TYPEOF(sp[-1]) == T_STRING )
    {
      struct svalue *tmp;
      tmp = low_mapping_string_lookup( stat_map, sp[-1].u.string );
      _stat_index( tmp ? tmp->u.integer : 0 );
    }
    else
      SIMPLE_BAD_ARG_ERROR("`[]",1,"int(0..6)|string");
  }
  else if (args>=2) /* range */
  {
    INT_TYPE from, to, n=0;

    if (args > 2) {
      pop_n_elems(args - 2);
      args = 2;
    }

    if (TYPEOF(sp[-2]) != T_INT &&
        !(TYPEOF(sp[-2]) == T_OBJECT && is_bignum_object (sp[-2].u.object)))
      SIMPLE_BAD_ARG_ERROR("`[..]",1,"int");

    if (TYPEOF(sp[-1]) != T_INT &&
        !(TYPEOF(sp[-1]) == T_OBJECT && is_bignum_object (sp[-1].u.object)))
      SIMPLE_BAD_ARG_ERROR("`[..]",2,"int");

    /* make in range 0..6 */
    push_int(6);
    f_min(2);
    stack_swap();
    push_int(0);
    f_max(2);
    stack_swap();

    from = sp[-2].u.integer;
    to = sp[-1].u.integer;

    pop_n_elems(args);

    while (from<=to)
    {
      stat_push_compat(from++);
      n++;
    }
    f_aggregate(n);
  }
}


static void _stat_index_set (INT_TYPE code, struct svalue *val, int got_int_val, INT64 int_val )
{
#define BAD_ARG_2(X) bad_arg_error("`[]=", Pike_sp-2,2,2,X,val,msg_bad_arg,2,"`[]=",X)

  if( got_int_val == -1 )
  {
    if (TYPEOF(*val) == T_INT)
      int_val = val->u.integer, got_int_val = 1;
    else if (TYPEOF(*val) == T_OBJECT && is_bignum_object (val->u.object))
    {
      if (!int64_from_bignum (&int_val, val->u.object))
        Pike_error ("`[]=: Too big integer as value.\n");
      else
        got_int_val = 1;
    }
  }

  switch (code) {
    case STAT_MODE_STRING:
      if (TYPEOF(*val) != T_STRING)
        BAD_ARG_2("string");

      /* FIXME: Handle modes on the form u+rw, perhaps? */

      if (val->u.string->len != 10)
        BAD_ARG_2("mode string with 10 chars");

      {
        PCHARP str = MKPCHARP_STR (val->u.string);
        int mode = THIS_STAT->s.st_mode;

        switch (INDEX_PCHARP (str, 0)) {
          case '-': mode = (mode & ~S_IFMT) | S_IFREG; break;
          case 'd': mode = (mode & ~S_IFMT) | S_IFDIR; break;
          case 'l': mode = (mode & ~S_IFMT) | S_IFLNK; break;
          case 'c': mode = (mode & ~S_IFMT) | S_IFCHR; break;
          case 'b': mode = (mode & ~S_IFMT) | S_IFBLK; break;
          case 'f': mode = (mode & ~S_IFMT) | S_IFIFO; break;
          case 's': mode = (mode & ~S_IFMT) | S_IFSOCK; break;
        }

        switch (INDEX_PCHARP (str, 1)) {
          case '-': mode &= ~S_IRUSR; break;
          case 'r': mode |= S_IRUSR; break;
        }
        switch (INDEX_PCHARP (str, 2)) {
          case '-': mode &= ~S_IWUSR; break;
          case 'w': mode |= S_IWUSR; break;
        }
        switch (INDEX_PCHARP (str, 3)) {
          case '-': mode &= ~(S_IXUSR | S_ISUID); break;
          case 'x': mode = (mode & ~S_ISUID) | S_IXUSR; break;
          case 'S': mode = (mode & ~S_IXUSR) | S_ISUID; break;
          case 's': mode |= S_IXUSR | S_ISUID; break;
        }

        switch (INDEX_PCHARP (str, 4)) {
          case '-': mode &= ~S_IRGRP; break;
          case 'r': mode |= S_IRGRP; break;
        }
        switch (INDEX_PCHARP (str, 5)) {
          case '-': mode &= ~S_IWGRP; break;
          case 'w': mode |= S_IWGRP; break;
        }
        switch (INDEX_PCHARP (str, 6)) {
          case '-': mode &= ~(S_IXGRP | S_ISGID); break;
          case 'x': mode = (mode & ~S_ISGID) | S_IXGRP; break;
          case 'S': mode = (mode & ~S_IXGRP) | S_ISGID; break;
          case 's': mode |= S_IXGRP | S_ISGID; break;
        }

        switch (INDEX_PCHARP (str, 7)) {
          case '-': mode &= ~S_IROTH; break;
          case 'r': mode |= S_IROTH; break;
        }
        switch (INDEX_PCHARP (str, 8)) {
          case '-': mode &= ~S_IWOTH; break;
          case 'w': mode |= S_IWOTH; break;
        }
        switch (INDEX_PCHARP (str, 9)) {
          case '-': mode &= ~(S_IXOTH | S_ISVTX); break;
          case 'x': mode = (mode & ~S_ISVTX) | S_IXOTH; break;
          case 'T': mode = (mode & ~S_IXOTH) | S_ISVTX; break;
          case 't': mode |= S_IXOTH | S_ISVTX; break;
        }

        THIS_STAT->s.st_mode = mode;
      }
      break;

    case STAT_TYPE:
      if (TYPEOF(*val) != T_STRING)
        BAD_ARG_2("string");

      if (val->u.string == str_type_reg)
        THIS_STAT->s.st_mode = (THIS_STAT->s.st_mode & ~S_IFMT) | S_IFREG;
      else if (val->u.string == str_type_dir)
        THIS_STAT->s.st_mode = (THIS_STAT->s.st_mode & ~S_IFMT) | S_IFDIR;
      else if (val->u.string == str_type_lnk)
        THIS_STAT->s.st_mode = (THIS_STAT->s.st_mode & ~S_IFMT) | S_IFLNK;
      else if (val->u.string == str_type_chr)
        THIS_STAT->s.st_mode = (THIS_STAT->s.st_mode & ~S_IFMT) | S_IFCHR;
      else if (val->u.string == str_type_blk)
        THIS_STAT->s.st_mode = (THIS_STAT->s.st_mode & ~S_IFMT) | S_IFBLK;
      else if (val->u.string == str_type_fifo)
        THIS_STAT->s.st_mode = (THIS_STAT->s.st_mode & ~S_IFMT) | S_IFIFO;
      else if (val->u.string == str_type_sock)
        THIS_STAT->s.st_mode = (THIS_STAT->s.st_mode & ~S_IFMT) | S_IFSOCK;
      else if (val->u.string == str_type_unknown)
        THIS_STAT->s.st_mode = THIS_STAT->s.st_mode & ~S_IFMT;
      else
        BAD_ARG_2("valid type string");
      break;

    case STAT_ISREG:
      THIS_STAT->s.st_mode &= ~S_IFMT;
      if (!UNSAFE_IS_ZERO (val)) THIS_STAT->s.st_mode |= S_IFREG;
      break;
    case STAT_ISDIR:
      THIS_STAT->s.st_mode &= ~S_IFMT;
      if (!UNSAFE_IS_ZERO (val)) THIS_STAT->s.st_mode |= S_IFDIR;
      break;
    case STAT_ISLNK:
      THIS_STAT->s.st_mode &= ~S_IFMT;
      if (!UNSAFE_IS_ZERO (val)) THIS_STAT->s.st_mode |= S_IFLNK;
      break;
    case STAT_ISCHR:
      THIS_STAT->s.st_mode &= ~S_IFMT;
      if (!UNSAFE_IS_ZERO (val)) THIS_STAT->s.st_mode |= S_IFCHR;
      break;
    case STAT_ISBLK:
      THIS_STAT->s.st_mode &= ~S_IFMT;
      if (!UNSAFE_IS_ZERO (val)) THIS_STAT->s.st_mode |= S_IFBLK;
      break;
    case STAT_ISFIFO:
      THIS_STAT->s.st_mode &= ~S_IFMT;
      if (!UNSAFE_IS_ZERO (val)) THIS_STAT->s.st_mode |= S_IFIFO;
      break;
    case STAT_ISSOCK:
      THIS_STAT->s.st_mode &= ~S_IFMT;
      if (!UNSAFE_IS_ZERO (val)) THIS_STAT->s.st_mode |= S_IFSOCK;
      break;

    default:
      if (!got_int_val)
        BAD_ARG_2("integer");

      switch (code) {
        case STAT_DEV: DO_NOT_WARN(THIS_STAT->s.st_dev = (int) int_val); break;
        case STAT_INO: DO_NOT_WARN(THIS_STAT->s.st_ino = (int) int_val); break;
        case STAT_MODE: DO_NOT_WARN(THIS_STAT->s.st_mode = (int) int_val); break;
        case STAT_NLINK: DO_NOT_WARN(THIS_STAT->s.st_nlink = (int) int_val); break;
        case STAT_UID: DO_NOT_WARN(THIS_STAT->s.st_uid = (int) int_val); break;
        case STAT_GID: DO_NOT_WARN(THIS_STAT->s.st_gid = (int) int_val); break;
        case STAT_RDEV: DO_NOT_WARN(THIS_STAT->s.st_rdev = (int) int_val); break;
        case STAT_SIZE: THIS_STAT->s.st_size = DO_NOT_WARN ((off_t) int_val); break;
#ifdef HAVE_STRUCT_STAT_BLOCKS
        case STAT_BLKSIZE: DO_NOT_WARN(THIS_STAT->s.st_blksize = int_val); break;
        case STAT_BLOCKS: DO_NOT_WARN(THIS_STAT->s.st_blocks = int_val); break;
#endif
        case STAT_ATIME: THIS_STAT->s.st_atime = DO_NOT_WARN ((time_t) int_val); break;
        case STAT_MTIME: THIS_STAT->s.st_mtime = DO_NOT_WARN ((time_t) int_val); break;
        case STAT_CTIME: THIS_STAT->s.st_ctime = DO_NOT_WARN ((time_t) int_val); break;

#ifdef HAVE_STRUCT_STAT_NSEC
        case STAT_ATIME_NSEC: THIS_STAT->s.st_atim.tv_nsec = DO_NOT_WARN ((time_t) int_val); break;
        case STAT_MTIME_NSEC: THIS_STAT->s.st_mtim.tv_nsec = DO_NOT_WARN ((time_t) int_val); break;
        case STAT_CTIME_NSEC: THIS_STAT->s.st_ctim.tv_nsec = DO_NOT_WARN ((time_t) int_val); break;
#endif
      }
  }
}

static void stat_index_set (INT32 args)
{
  int got_int_val = 0;
  INT64 int_val;

  if (args < 2)
    SIMPLE_TOO_FEW_ARGS_ERROR ("Stat `[]=", 2);

  if (args > 2) {
    pop_n_elems (args - 2);
    args = 2;
  }

  if (TYPEOF(sp[-1]) == T_INT)
    int_val = sp[-1].u.integer, got_int_val = 1;
  else if (TYPEOF(sp[-1]) == T_OBJECT && is_bignum_object (sp[-1].u.object))
  {
    if (!int64_from_bignum (&int_val, sp[-1].u.object))
      Pike_error ("Stat `[]=: Too big integer as value.\n");
    else
      got_int_val = 1;
  }

  /* shouldn't there be an else clause here ? */
  /* No, the second argument is checked further below depending on
   * what the first is. /mast */

  if (TYPEOF(sp[-2]) == T_INT) {
    if (!got_int_val)
      SIMPLE_BAD_ARG_ERROR ("`[]=", 2,
			    "integer when the first argument is an integer");
    if (!stat_compat_set (sp[-2].u.integer, int_val))
      SIMPLE_BAD_ARG_ERROR ("`[]=", 1, "int(0..6)|string");
  }
  else if (TYPEOF(sp[-2]) == T_STRING) {
    INT_TYPE code;
    struct svalue *tmp;
    tmp = low_mapping_string_lookup( stat_map, sp[-2].u.string );
    if (!tmp)
    {
      /* Fall back to a normal index set on this object, in case
       * someone inherited us. */
      object_set_index2 (fp->current_object, 0, sp-2, sp-1);
      stack_swap();
      pop_stack();
      return;
    }
    _stat_index_set( tmp->u.integer, sp-1, got_int_val, int_val);
  }

  else SIMPLE_BAD_ARG_ERROR ("`[]=", 1, "int(0..6)|string");

  stack_swap();
  pop_stack();
}

static void stat_indices(INT32 args);
static void stat_values(INT32 args);

/*! @decl mapping(string:int)|array cast (string to);
 *!
 *! Convert the stat object to a mapping or array.
 */
static void stat_cast(INT32 args)
{
  struct pike_string *type;

  if (!args)
    SIMPLE_TOO_FEW_ARGS_ERROR("Stat cast",1);
  if (TYPEOF(sp[-args]) != T_STRING)
    SIMPLE_BAD_ARG_ERROR("cast",1,"string");

  type = Pike_sp[-args].u.string;
  pop_stack(); /* type have at least one more reference. */

  if (type == literal_array_string)
  {
    push_int(0);
    push_int(6);
    stat_index(2);
  }
  else if (type == literal_mapping_string)
  {
    stat_indices(0);
    stat_values(0);
    push_mapping(mkmapping(Pike_sp[-2].u.array, Pike_sp[-1].u.array));
    stack_pop_n_elems_keep_top(2);
  }
  else
    push_undefined();
}

static void stat__sprintf(INT32 args)
{
   int x;

   if (args<1)
      SIMPLE_TOO_FEW_ARGS_ERROR("_sprintf",2);

   if (TYPEOF(sp[-args]) != T_INT)
      SIMPLE_BAD_ARG_ERROR("_sprintf",0,"integer");

   x=sp[-args].u.integer;
   pop_n_elems(args);
   switch (x)
   {
      case 'O':
        push_text("Stat(%s %db)");
        _stat_index(STAT_MODE_STRING);
        _stat_index(STAT_SIZE);
        f_sprintf(3);
        return;
      default:
	 push_int(0);
	 return;
   }
}

static void stat_indices(INT32 args)
{
   pop_n_elems(args);
   ref_push_mapping(stat_map);
   f_indices(1);
}

static void stat_values(INT32 args)
{
   int i,z;
   struct array *a;

   pop_n_elems(args);
   ref_push_mapping(stat_map);
   f_indices(1);
   z=(a=sp[-1].u.array)->size;
   for (i=0; i<z; i++)
   {
      ref_push_object(fp->current_object);
      push_svalue(a->item+i);
      f_index(2);
   }
   f_aggregate(z);
   stack_pop_keep_top();
}

#ifdef __GNUC__
/* Without this gcc inlines all the function calls to _index_set etc below.

   It's really rather pointless. And we really need a better way to
   avoid this than adding a lot of -Os around the code. :)
*/
#pragma GCC optimize "-Os"
#endif

static void stat_create (INT32 args)
{
  if (args >= 1 && !UNSAFE_IS_ZERO (Pike_sp - 1)) {
    pop_n_elems (args - 1);
    args = 1;

    if (TYPEOF(sp[-1]) == T_OBJECT)
      if (sp[-1].u.object->prog == stat_program) {
	*THIS_STAT = *(struct stat_storage *) sp[-1].u.object->storage;
	pop_stack();
	return;
      }

    if ((1 << TYPEOF(sp[-1])) & (BIT_PROGRAM|BIT_OBJECT|BIT_MAPPING))
    {
      struct keypair *k;
      int e;
      NEW_MAPPING_LOOP( stat_map->data )
      {
        push_svalue(&k->ind);
        SET_SVAL_SUBTYPE(sp[-1],1);
        o_index();
        if(!IS_UNDEFINED(sp-1)) {
          _stat_index_set( k->val.u.integer, sp-1, -1,0 );
        }
        pop_stack();
      }
    }
    else if (TYPEOF(sp[-1]) == T_ARRAY)
    {
      struct array *a = sp[-1].u.array;
      int i;
      if (a->size != 7)
	SIMPLE_BAD_ARG_ERROR ("create", 1, "stat array with 7 elements");
      for (i = 0; i < 7; i++) {
	INT64 val;
	if (TYPEOF(ITEM(a)[i]) == T_INT)
	  val = ITEM(a)[i].u.integer;
	else if (TYPEOF(ITEM(a)[i]) == T_OBJECT &&
		 is_bignum_object (ITEM(a)[i].u.object)) {
	  if (!int64_from_bignum (&val, ITEM(a)[i].u.object))
	    Pike_error ("create: Too big integer in stat array.\n");
	}
	else
	  SIMPLE_BAD_ARG_ERROR ("create", 1, "array(int)");
	stat_compat_set (i, val);
      }
    }

    else
      SIMPLE_BAD_ARG_ERROR ("create", 1, "void|Stdio.Stat|array(int)");
  }

  pop_n_elems (args);
}
#undef THIS_STAT

void push_stat(PIKE_STAT_T *s)
{
   struct object *o;
   struct stat_storage *stor;
   o=clone_object(stat_program,0);
   stor=get_storage(o,stat_program);
   stor->s=*s;
   push_object(o);
}


/*! @endclass
 */
/*! @endmodule
 */

/* ---------------------------------------------------------------- */

void init_stdio_stat(void)
{
   unsigned int n=0;

   static const struct {
     const char *name;
     const INT_TYPE id;
   } __indices[] = {
     {"dev",STAT_DEV},
     {"ino",STAT_INO},
     {"mode",STAT_MODE},
     {"nlink",STAT_NLINK},
     {"uid",STAT_UID},
     {"gid",STAT_GID},
     {"rdev",STAT_RDEV},
     {"size",STAT_SIZE},
#ifdef HAVE_STRUCT_STAT_BLOCKS
     {"blksize",STAT_BLKSIZE},
     {"blocks",STAT_BLOCKS},
#endif
     {"atime",STAT_ATIME},
     {"mtime",STAT_MTIME},
     {"ctime",STAT_CTIME},

     {"atime_nsec",STAT_ATIME_NSEC},
     {"mtime_nsec",STAT_MTIME_NSEC},
     {"ctime_nsec",STAT_CTIME_NSEC},

     {"islnk",STAT_ISLNK},
     {"isreg",STAT_ISREG},
     {"isdir",STAT_ISDIR},
     {"ischr",STAT_ISCHR},
     {"isblk",STAT_ISBLK},
     {"isfifo",STAT_ISFIFO},
     {"issock",STAT_ISSOCK},

     {"type",STAT_TYPE},
     {"mode_string",STAT_MODE_STRING}
   };

   str_type_reg = make_shared_string("reg");
   str_type_dir = make_shared_string("dir");
   str_type_lnk = make_shared_string("lnk");
   str_type_chr = make_shared_string("chr");
   str_type_blk = make_shared_string("blk");
   str_type_fifo = make_shared_string("fifo");
   str_type_sock = make_shared_string( "sock");
   str_type_unknown = make_shared_string( "unknown");

   stat_map=allocate_mapping(1);
   push_int(0);
   for( n=0; n<sizeof(__indices)/sizeof(__indices[0]); n++ )
   {
     struct pike_string *s = make_shared_string(__indices[n].name);
     stat_index_strs[__indices[n].id]=s;
     sp[-1].u.integer = __indices[n].id;
     mapping_string_insert( stat_map, s, sp-1);
   }
   sp--;
   dmalloc_touch_svalue(sp);

   START_NEW_PROGRAM_ID(STDIO_STAT);
   ADD_STORAGE(struct stat_storage);

   ADD_FUNCTION ("create", stat_create,
		 tFunc(tOr5(tVoid,tObjImpl_STDIO_STAT,tPrg(tObj),tMapping,tArr(tInt)),
		       tVoid), ID_PROTECTED);

   ADD_FUNCTION("`[]",stat_index,
		tOr(tFunc(tOr(tStr,tInt06),tOr3(tStr,tInt,tFunction)),
		    tFunc(tInt tInt,tArr(tInt))),0);
   ADD_FUNCTION("`->",stat_index,
		tOr(tFunc(tOr(tStr,tInt06),tOr3(tStr,tInt,tFunction)),
		    tFunc(tInt tInt,tArr(tInt))),0);

   ADD_FUNCTION ("`[]=", stat_index_set,
		 tOr(tFunc(tInt06 tSetvar(0,tInt),tVar(0)),
		     tFunc(tString tSetvar(1,tOr(tInt,tString)),tVar(1))), 0);
   ADD_FUNCTION ("`->=", stat_index_set,
		 tOr(tFunc(tInt06 tSetvar(0,tInt),tVar(0)),
		     tFunc(tString tSetvar(1,tOr(tInt,tString)),tVar(1))), 0);

   ADD_FUNCTION("cast",stat_cast,tFunc(tStr,tOr(tMapping,tArray)),ID_PROTECTED);
   ADD_FUNCTION("_sprintf",stat__sprintf,
		tFunc(tInt tOr(tVoid,tMapping),tString),0);
   ADD_FUNCTION("_indices",stat_indices,
		tFunc(tNone,tArr(tOr(tString,tInt))),0);
   ADD_FUNCTION("_values",stat_values,
		tFunc(tNone,tArr(tOr(tString,tInt))),0);

   set_init_callback (stat_init);

   stat_program=end_program();
   add_program_constant("Stat",stat_program,0);
}

void exit_stdio_stat(void)
{
   size_t i;

   free_program(stat_program);
   free_mapping(stat_map);

   for (i = 1; i < STAT_ENUM_END; i++)
     free_string (stat_index_strs[i]);

   free_string (str_type_reg);
   free_string (str_type_dir);
   free_string (str_type_lnk);
   free_string (str_type_chr);
   free_string (str_type_blk);
   free_string (str_type_fifo);
   free_string (str_type_sock);
   free_string (str_type_unknown);
}
