/* stat.c
 *
 * COPYRIGHT (c) 2019 The Fellowship of SML/NJ (http://www.smlnj.org)
 * All rights reserved.
 */

#include "ml-unixdep.h"
#include <sys/types.h>
#include <sys/stat.h>
#include "ml-base.h"
#include "ml-values.h"
#include "ml-objects.h"
#include "ml-c.h"
#include "cfun-proto-list.h"

#define MODE_BITS (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID)

#if defined(STAT_HAS_TIMESPEC)
/* convert struct timespec to nanoseconds */
STATIC_INLINE Unsigned64_t timespec_to_ns (struct timespec *ts)
{
    return NS_PER_SEC * (Unsigned64_t)ts->tv_sec + (Unsigned64_t)ts->tv_nsec;
}
#endif

/* mkStatRep:
 *
 * This makes a representation of the struct stat to be returned
 * to the SML side. It is a tuple with the following fields:
 *
 *    file_type : int
 *    mode      : SysWord.word
 *    ino       : SysWord.word		-- should be Word64.word
 *    dev       : SysWord.word
 *    nlink     : SysWord.word
 *    uid       : SysWord.word
 *    gid       : SysWord.word
 *    size      : Position.int (aka Int64.int)
 *    atime     : Word64.word
 *    mtime     : Word64.word
 *    ctime     : Word64.word
 */
PVT ml_val_t mkStatRep (ml_state_t *msp, struct stat *buf)
{
    int			ftype;
    Unsigned64_t	aTim, mTim, cTim;
    ml_val_t		mode, ino, dev, uid, gid, nlink, sr, atime, mtime, ctime, size;

#if ((S_IFDIR != 0x4000) || (S_IFCHR != 0x2000) || (S_IFBLK != 0x6000) || (S_IFREG != 0x8000) || (S_IFIFO != 0x1000) || (S_IFLNK != 0xA000) || (S_IFSOCK != 0xC000))
    if (S_ISDIR(buf->st_mode)) ftype = 0x4000;
    else if (S_ISCHR(buf->st_mode)) ftype = 0x2000;
    else if (S_ISBLK(buf->st_mode)) ftype = 0x6000;
    else if (S_ISREG(buf->st_mode)) ftype = 0x8000;
    else if (S_ISFIFO(buf->st_mode)) ftype = 0x1000;
#ifdef S_ISLNK
    else if (S_ISLNK(buf->st_mode)) ftype = 0xA000;
#endif
#ifdef S_ISSOCK
    else if (S_ISSOCK(buf->st_mode)) ftype = 0xC000;
#endif
    else ftype = 0;
#else
    ftype = buf->st_mode & 0xF000;
#endif

    SYSWORD_ALLOC (msp, mode, (Word_t)((buf->st_mode) & MODE_BITS));
/*
    WORD64_ALLOC (msp, ino, buf->st_ino);
*/
    SYSWORD_ALLOC (msp, ino, (Word_t)(buf->st_ino));
    SYSWORD_ALLOC (msp, dev, (Word_t)(buf->st_dev));
    SYSWORD_ALLOC (msp, nlink, (Word_t)(buf->st_nlink));
    SYSWORD_ALLOC (msp, uid, (Word_t)(buf->st_uid));
    SYSWORD_ALLOC (msp, gid, (Word_t)(buf->st_gid));
    INT64_ALLOC (msp, size, buf->st_size);

#if !defined(STAT_HAS_TIMESPEC)
  /* the old API with second-level granularity */
    aTim = NS_PER_SEC * (Unsigned64_t)buf->st_atime;
    mTim = NS_PER_SEC * (Unsigned64_t)buf->st_mtime;
    cTim = NS_PER_SEC * (Unsigned64_t)buf->st_ctime;
#elif defined(OPSYS_DARWIN)
  /* macOS uses non-standard names for the fields */
    aTim = timespec_to_ns (&buf->st_atimespec);
    mTim = timespec_to_ns (&buf->st_mtimespec);
    cTim = timespec_to_ns (&buf->st_ctimespec);
#else
    aTim = timespec_to_ns (&buf->st_atim);
    mTim = timespec_to_ns (&buf->st_mtim);
    cTim = timespec_to_ns (&buf->st_ctim);
#endif
    WORD64_ALLOC (msp, atime, aTim);
    WORD64_ALLOC (msp, mtime, mTim);
    WORD64_ALLOC (msp, ctime, cTim);

  /* allocate the stat record */
    ML_AllocWrite(msp,  0, MAKE_DESC(11, DTAG_record));
    ML_AllocWrite(msp,  1, INT_CtoML(ftype));
    ML_AllocWrite(msp,  2, mode);
    ML_AllocWrite(msp,  3, ino);
    ML_AllocWrite(msp,  4, dev);
    ML_AllocWrite(msp,  5, nlink);
    ML_AllocWrite(msp,  6, uid);
    ML_AllocWrite(msp,  7, gid);
    ML_AllocWrite(msp,  8, size);
    ML_AllocWrite(msp,  9, atime);
    ML_AllocWrite(msp, 10, mtime);
    ML_AllocWrite(msp, 11, ctime);
    sr = ML_Alloc(msp, 11);

    return sr;

} /* end of mkStatRep */

/* _ml_P_FileSys_stat : string -> statrep
 *
 * Query file status given file name.
 */
ml_val_t _ml_P_FileSys_stat (ml_state_t *msp, ml_val_t arg)
{
    char            *path = STR_MLtoC(arg);
    int		    sts;
    struct stat     buf;

    sts = stat(path, &buf);

    if (sts < 0) {
	return RAISE_SYSERR(msp, sts);
    }

    return (mkStatRep(msp, &buf));

} /* end of _ml_P_FileSys_stat */

/* _ml_P_FileSys_fstat : int -> statrep
 *
 * Query file status given file descriptor.
 */
ml_val_t _ml_P_FileSys_fstat (ml_state_t *msp, ml_val_t arg)
{
    int             fd = INT_MLtoC(arg);
    int		    sts;
    struct stat     buf;

    sts = fstat(fd, &buf);

    if (sts < 0) {
	return RAISE_SYSERR(msp, sts);
    }
    else {
	return mkStatRep(msp, &buf);
    }

} /* end of _ml_P_FileSys_fstat */

/* _ml_P_FileSys_lstat : string -> statrep
 *
 * Query file status given file name, but do not follow
 * symbolic links.
 */
ml_val_t _ml_P_FileSys_lstat (ml_state_t *msp, ml_val_t arg)
{
    char            *path = STR_MLtoC(arg);
    int		    sts;
    struct stat     buf;

    sts = lstat(path, &buf);

    if (sts < 0) {
	return RAISE_SYSERR(msp, sts);
    }
    else {
	return mkStatRep(msp, &buf);
    }

} /* end of _ml_P_FileSys_lstat */
