#define MODULE_ID	5040

/***
Copyright (c) 1991, 1992 by Frank J. Edwards
See the file COPYRIGHT in this distribution for copyright details.
***/

#ident "$Id: util.c,v 1.19 1992/12/22 23:38:24 root Exp root $";

#include <sys/types.h>
#include <time.h>
#include <sys/cred.h>
#include <sys/errno.h>
#include <sys/inline.h>		/* For strcpy() */
#include <sys/kmem.h>		/* For freechn() */
#include <sys/stat.h>
#include <sys/systm.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <sys/vfs.h>
#include <sys/vnode.h>

#include <sys/buf.h>		/* For bread() and friends */
#include <sys/file.h>		/* For FREAD */
#include "ados.h"
#include "ados_date.h"
#include "ados_udir.h"
#include "ados_file.h"
#include "missing.h"

#define MINS	60
#define DAYS	(24 * 60 * MINS)
#define DAYLIGHT 1		/* Dynamic ??? */

/*
 * Convert an AmigaDOS DateStamp into a Unix timestruc_t
 */
timestruc_t *date2unix(DateStamp * ds)
{
    static timestruc_t timex;

    /* days * DAYS + mins * MINS + 8years + 5hrs - (60 * MINS) (dst!?) */

    timex.tv_nsec = 0;
    timex.tv_sec = (ds->ds_Days + 8 * 365 + 2) * DAYS +
	(ds->ds_Minute + (DAYLIGHT ? 240 : 300)) * MINS;
    return (&timex);
}

/*
 * Convert a Unix timestruc_t into an AmigaDOS DateStamp
 */
DateStamp *time2ados(time_t timex)
{
    static DateStamp ds;

    struct tm *tm;

#if 0
    tm = gmtime(&timex);
    tm->tm_year -= 8;		/* AmigaDOS is from 1978, not 1970 */

    ds.ds_Days = tm->tm_yday + (tm->tm_year * 365) + (tm->tm_year / 4);
    ds.ds_Minute = tm->tm_hour * 60 * 60 + tm->tm_min * 60;
    ds.ds_Tick = tm->tm_sec * 50 / 60;
#endif
    return (&ds);
}

/*
 * Convert AmigaDOS protection bits to Unix protection bits. AmigaDOS
 * protection bits are "SPARWED", bit6 to bit0. Delete permission within
 * AmigaDOS is handled with a bit which applies to a particular file.  Under
 * Unix, this is done by removing write permission to the parent directory.
 * We simulate the delete bit by turning on the "sticky" bit. Otherwise, the
 * Read, Write, and Execute bits are mapped to the same permissions under
 * Unix.
 */

mode_t modes2unix(LONG type, LONG prot)
{
    mode_t m;

    /* The bit is on (1) if the permission is DENIED!  */
    m = ((~prot >> 1) & 0x7) * 0111;
    if (prot & 1)
	m |= S_ISVTX;

    switch (type) {
    case ST_LINKDIR:
    case ST_USERDIR:
    case ST_ROOT:
	m |= S_IFDIR;
	break;

    case ST_LINKFILE:
    case ST_FILE:
	m |= S_IFREG;
	break;

    case ST_SOFTLINK:
	m |= S_IFLNK;
	break;
    }
    DBG("from %x to %x", prot, (int) m, 0);
    return (m);
}

/*
 * Read the anode header block from disk and initialize all of the pertinent
 * entries in (struct anode).
 */
int ados_read_anode(register anode_t * ap, LONG key, struct vfs * vfsp)
{
    register vnode_t *vp = ATOV(ap);
    register struct buf *bp;
    register struct a_udir *ud;
    int error;

    bp = bread(vfsp->vfs_dev, key, vfsp->vfs_bsize);
    if (error = geterror(bp)) {
	brelse(bp);
	return error;
    }
    ud = (struct a_udir *) bp->b_un.b_addr;

IN_FUNC(ados_read_anode);
    vp->v_vfsmountedhere = 0;
    vp->v_flag    = (ud->ud_type2 == ST_ROOT) ? VROOT : 0;
    vp->v_count   = 1;
    vp->v_op      = &ados_vnops;
    vp->v_vfsp    = vfsp;
    vp->v_stream  = NULL;
    vp->v_pages   = NULL;
    vp->v_rdev    = vfsp->vfs_dev;
    vp->v_data    = (caddr_t) ap;
    vp->v_filocks = NULL;

IN_FUNC(ados_read_anode);
    ap->a_mode = modes2unix(ud->ud_type2, ud->ud_prot);
    ap->a_mtime = *date2unix(&ud->ud_dirstmp);
    ap->a_uid = 0;
    ap->a_gid = 0;
    ap->a_keyblk = key;
    ud->ud_name[ ud->ud_name[0]+1 ] = '\0';
    strcpy(ap->a_name, (char *) ud->ud_name+1);

    ap->a_idx = -1;		/* getblk() will fix this at the first call */
    if (ud->ud_type2 == ST_ROOT || ud->ud_type2 == ST_USERDIR) {
	vp->v_type   = VDIR;
	ap->a_size   = 1;
#if 0
	error        = ados_cache_dir(ADOS_VFS(vfsp)->vfs_devvp, ap);
	ap->a_idx    = 0;
#endif
    } else {
	vp->v_type   = VREG;
	ap->a_nlink  = 1;
	ap->a_size   = AFILE(ud)->uf_size;
	ap->a_blocks = (ap->a_size + vfsp->vfs_bsize - 1) / vfsp->vfs_bsize;
    }
    DBG("ap @ %x, error %d", ap, error, 0);

    brelse(bp);
    return error;
}

/*
 * Search a directory for an entry named nm. Return anode pointer if found,
 * NULL otherwise.
 */
anode_t *ados_searchdir(vnode_t * dvp, char *nm, struct cred * cr)
{
    struct ados *afsp = ADOS_VFS(dvp->v_vfsp);
    anode_t *np;
    int error;

IN_FUNC(ados_searchdir);
    error = ados_dotsearch(afsp, VTOA(dvp), nm, cr, &np, (off_t *) 0);
IN_FUNC(ados_searchdir);
    if (error)
	return 0;
    return np;
}

/*
 * Search a directory for an entry. If found, return dir offset of the entry
 * (in offp) and pointer to the anode (in npp).  If not found, return dir
 * offset of first empty slot (in offp) or 0.
 */

/* ARGSUSED */
int ados_dotsearch(struct ados * afsp, anode_t * dnp, char *nm,
		       struct cred * cr, anode_t ** npp, off_t * offp)
{
    struct uio iod;
    struct iovec iov;
    struct a_udir udir;
    register int error = 0, block, bsize = 1 << afsp->vfs_bufbits;

IN_FUNC(ados_dotsearch);
    iod.uio_segflg = UIO_SYSSPACE;
    iod.uio_fmode = FREAD;
    iod.uio_limit = -1;
    iod.uio_iov = &iov;
    iod.uio_iovcnt = 1;
    for (block = 0; !error && block < dnp->a_size; block += bsize) {
	iod.uio_resid = sizeof(udir);
	iod.uio_offset = block;
	iov.iov_base = (caddr_t) &udir;
	iov.iov_len = sizeof(udir);

	if ((error = reada(dnp, &iod, 0)) || iod.uio_resid == bsize)
	    break;
	if (iod.uio_resid == 0) {
	    udir.ud_name[ (int)udir.ud_name[0]+1 ] = '\0';
	    DBG("checking %s", (char *)udir.ud_name+1, 0, 0);
	    if (strcmp((char *) udir.ud_name+1, nm) == 0) {
		error = aget(afsp->vfs_vfsp, udir.ud_hkey, npp);
		if (offp)
		    *offp = block;
		return error;
	    }
	} else if (iod.uio_resid != sizeof(udir))
	    error = EIO;
    }
IN_FUNC(ados_dotsearch);
    return error ? error : ENOENT;
}

int ados_naccess(anode_t * ap, int mode, struct cred * cr)
{
IN_FUNC(ados_naccess);
    if ((mode & VWRITE) && (ATOV(ap)->v_vfsp->vfs_flag & VFS_RDONLY))
	return EROFS;

    if (suser(cr))
	return 0;

    if (cr->cr_uid != ap->a_uid) {
	mode >>= 3;
	if (!groupmember(ap->a_gid, cr))
	    mode >>= 3;
    }
    if (((mode_t) ap->a_mode & mode) == mode)
	return 0;

    return EACCES;
}
