/*
 * Copyright (c) 1998 Kungliga Tekniska Högskolan
 * (Royal Institute of Technology, Stockholm, Sweden).
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the Kungliga Tekniska
 *      Högskolan and its contributors.
 * 
 * 4. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */


/* $Id: ufsmedia.c,v 1.5 1998/10/15 13:28:46 lha Exp $ */

#include "milko_locl.h"

#ifndef FILESYSTEM_UFS

RCSID("$Id: ufsmedia.c,v 1.5 1998/10/15 13:28:46 lha Exp $ - Not compiled in");	/* make sure the file isn't empty */

#else /* FILESYSTEM_UFS */

RCSID("$Id: ufsmedia.c,v 1.5 1998/10/15 13:28:46 lha Exp $");

#define UFS_MAGICCOOKIE 0x1147

typedef struct ufsmedia {
    int magic;
    char *path;
    xfs_cache_handle rootfh;
    fsid_t fsid;
} ufsmedia;


static int
ufs_init(char *path, void **data)
{
    int error ;
    struct stat sb;
    ufsmedia *um;

    if (path == NULL)
	return EINVAL;

    error = stat(path, &sb);
    if (error)
	return errno;

    if (!S_ISDIR(sb.st_mode))
	return ENOTDIR;

    um = calloc(1, sizeof(*um));
    if (!um)
	return ENOMEM;

    um->path = strdup(path);
    if (!um->path) {
	free(um);
	return ENOMEM;
    }

    um->magic = UFS_MAGICCOOKIE;
    error = fhget(path, &um->rootfh);
    if (error) {
	free(um->path);
	free(um);
	return errno;
    }
    fhtonum(&um->rootfh, &um->fsid, NULL, NULL);
    *data = um;

    return 0;
}

static int
ufs_close(void *data, int reson)
{
    ufsmedia *um = (ufsmedia *) data;

    assert(um && um->magic == UFS_MAGICCOOKIE);

    free(um->path);
    free(um);

    return 0;
}

/*
 * Convert unix rights to afsrights for 
 */

static int
unix2afs_rights(const MilkoCred *cred, const mode_t mode, const uid_t uid,
		const gid_t gid, u_int32_t *rcaller, u_int32_t *ranon)
{
    *ranon = *rcaller = AREAD | ALIST | AWRITE | AINSERT | AADMIN;

    return 0;
}

/*
 * Convert unix stat(2) to AFSFetchStatus struct
 */

static int
unixstat2afsstatus(const MilkoCred *cred, const struct stat *sb,
		   AFSFetchStatus *status)
{
/* Same order as in rxdef/common.h */
    
    status->InterfaceVersion	= 1; /* XXX */
    status->FileType		= 0; /* set below */
    status->LinkCount		= sb->st_nlink;
    status->Length		= sb->st_size;
    status->DataVersion		= sb->st_gen;
    status->Author 		= sb->st_uid;
    status->Owner 		= sb->st_uid;
    status->CallerAccess	= 0; /* set below */
    status->AnonymousAccess	= 0; /* set below */
    status->UnixModeBits	= sb->st_mode;
    status->ParentVnode		= 11; /* XXX */
    status->ParentUnique	= 12; /* XXX */
    status->SegSize		= sb->st_blksize;
    status->ClientModTime	= sb->st_mtime;
    status->ServerModTime	= sb->st_mtime;
    status->Group		= sb->st_gid;
    status->SyncCount		= 0; /* XXX */

    if (S_ISREG(sb->st_mode)) 
	status->FileType = TYPE_FILE;
    else if (S_ISDIR(sb->st_mode))
	status->FileType = TYPE_DIR;
    else if (S_ISLNK(sb->st_mode))
	status->FileType = TYPE_LINK;
    else
	return EBADF;

    return unix2afs_rights(NULL, sb->st_mode, sb->st_uid, sb->st_gid,
			    &status->CallerAccess,
			    &status->AnonymousAccess);

}



/*
 * Bake a afsdirectory from a unix directory
 */

static int
bake_afsdir(int fd, mbuf **the_mbuf, AFSFid dot, AFSFid *rdotdot)
{
    struct dirent *dp;
    long basep;
    struct stat sb;
    int count, error, len;
    char str[2048];
    AFSFid dotdot, tmp;
    
    error = mbuf_create(the_mbuf, -1, 0, 0, MBUF_ANON);
    if (error)
	return error;

    dotdot.Volume = dot.Volume;
    if (dot.Vnode == 1)
	dotdot.Vnode = 1;
    else {
/*	error = istatpath(fd, "..", &sb); */
	dotdot.Vnode = sb.st_ino;
    }
    dotdot.Unique = 17;

    if (rdotdot)
	*rdotdot = dotdot;

    error = mdir_mkdir(*the_mbuf, dot, dotdot);
    if (error)
	goto clean;

    
    if (lseek(fd, 0, SEEK_SET) == -1) {
	error = errno;
	goto clean;
    }

    tmp.Volume = dot.Volume;
    
    while((len = getdirentries(fd, str, sizeof(str), &basep)) > 0) {
	dp = (struct dirent *) str;
	
	count = 0;
	
	while (len > count) {

	    /* . or .. */
	    if ((dp->d_namlen == 1 && dp->d_name[0] == '.') ||
		(dp->d_namlen == 2 && dp->d_name[0] == '.' && 
		 dp->d_name[1] == '.'))
		goto contloop;

	    abort();

#if 0
	    findpath() && stat();
#endif
	    error = mdir_creat(*the_mbuf, dp->d_name, tmp);
	    if (error)
		goto clean;

	contloop:
	    count += dp->d_reclen;
	    ((char *)dp) += dp->d_reclen;
	}
    }
    if (len == -1) 
	goto clean;
    
    return 0;

 clean:
    mbuf_end(*the_mbuf);
    close(fd);
    return error;
}

/*
 * Open inode and stat()
 */

static int
ufsi_iopenstat(const MilkoCred *cred, const ufsmedia *um, int32_t vnode,
	       int32_t unique, struct stat *sb, int *fd)
{
    int saved_errno;
    int error;

    assert(um && um->magic == UFS_MAGICCOOKIE && fd);

    if (vnode == 1)
	*fd = fhopen(&((struct ufsmedia *) um)->rootfh, O_RDWR);
    else {
	struct xfs_cache_handle handle;
	numtofh(um->fsid, vnode, unique, &handle);
	*fd = fhopen(&handle, O_RDWR);
    }
    if (*fd == -1)
	return errno;
 
    /* We need to fstat() to check the rights */
    error = fstat(*fd, sb);
    if (error) {
	saved_errno = errno;
	close(*fd);
	return saved_errno;
    }

    /* XXX check access right */

    return 0;
}

/*
 * Internal getstatus
 *  Return fd to file is there is no errors and rfd is != NULL.
 */

static int
ufsi_getstatus(const MilkoCred *cred, ufsmedia *um, const AFSFid *fid,
	       AFSFetchStatus *status, int *rfd)
{
    int fd;
    int error;
    struct stat sb;

    assert(status);

    error = ufsi_iopenstat(cred, um, fid->Vnode, fid->Unique, &sb, &fd);
    if (error)
	return error;

    error = unixstat2afsstatus(cred, &sb, status);
    if (error) 
	goto out;

    if (status->FileType == TYPE_DIR) {
	AFSFid parent;
	mbuf *buf;

	error = bake_afsdir(fd, &buf, *fid, &parent);
	if (error)
	    goto out;

	if (fid->Vnode == 1) {
	    status->ParentVnode = 1;
	    status->ParentUnique = 1;
	} else {
	    status->ParentVnode = parent.Vnode;
	    status->ParentUnique = parent.Unique;
	}

	status->Length = buf->len;

	bufcache_add(*fid, buf);
	mbuf_end(buf);
    }

 out:

    if (rfd == NULL || error != 0)
	close(fd);
    else
	*rfd = fd;
    
    return error;
}

/*
 * Getstatus for a FID
 */

static int 
ufs_getstatus(const MilkoCred *cred, void *data, const AFSFid *fid, 
	      AFSFetchStatus *status)
{
    return ufsi_getstatus(cred, (ufsmedia *) data, fid, status, NULL);
}

static int
ufs_setstatus(const MilkoCred *cred, void *data, 
	      const AFSFid *fid, const AFSStoreStatus *sstatus,
	      AFSFetchStatus *status)
{
    int fd = -1 , error;
    struct stat sb;
    ufsmedia *um = (ufsmedia *) data;
    
    assert(um && um->magic == UFS_MAGICCOOKIE);

    printf("ufs_setstatus: fid: %d.%d.%d sstatus->Mask: 0x%x\n",
	   fid->Volume, fid->Vnode, fid->Unique, sstatus->Mask);

    /*
     * You might think that this is not nececary, but it is since
     * we need to check rights (and that is done i iopenstat()).
     * To check the rights we need to stat(2). Now that we get back
     * a (unsable) struct stat is a extra bonus.
     */
    error = ufsi_iopenstat(cred, um, fid->Vnode, fid->Unique, &sb, &fd);
    if (error)
	return error;

    /* XXX: This is currently done for simplicity, not speed */

    if (sstatus->Mask & SS_MODTIME) {
	/* XXX build a new opcode ? futimes ? */
	error = EINVAL;
	goto errorexit;
    }
    if (sstatus->Mask & SS_OWNER) {
	error = fchown(fd, sstatus->Owner, -1);
	if (error) {
	    error = errno;
	    goto errorexit;
	}
    }
    if (sstatus->Mask & SS_GROUP) {
	error = fchown(fd, -1, sstatus->Group);
	if (error) {
	    error = errno;
	    goto errorexit;
	}
    }
    if (sstatus->Mask & SS_MODEBITS) {
	error = fchmod(fd, sstatus->UnixModeBits);
	if (error)
	    goto errorexit;
    }
#if 0
    if (sstatus->Mask & SS_SEGSIZE) {
	/* XXX ? */
    }
#endif

    error = ufsi_getstatus(cred, um, fid, status, NULL);
 errorexit:
    if (fd >= 0)
	close(fd);
    return error;

}

/*
 * Fetch the file and status.
 *  If its a directory convert it to afs-native format.
 */

static int 
ufs_getdata(const MilkoCred *cred, void *data, struct rx_call *rxcall,
	    const AFSFid *fid, const int32_t offset, const int32_t len,
	    AFSFetchStatus *status)
{
    int error;
    int fd;
    int32_t nlen;

    error = ufsi_getstatus(cred, (ufsmedia *) data, fid, status, &fd);
    if (error)
	return error;

    
    if (status->FileType == TYPE_DIR) {
	mbuf *buf;

	error = bake_afsdir(fd, &buf, *fid, NULL);
	if (error)
	    return error;

	/* XXX check ranges */
	
	nlen = htonl(buf->len);
	if (rx_Write(rxcall, &nlen, sizeof(nlen)) != sizeof(nlen))
	    return EINVAL;

	error =  mcopybuf2rx(rxcall, buf, offset, len) ;
	mbuf_end(buf);

    } else {

	/* XXX check ranges */

	nlen = htonl(len);
	if (rx_Write(rxcall, &nlen, sizeof(nlen)) != sizeof(nlen))
	    return EINVAL;
	error = mcopyfd2rx(fd, rxcall, offset, len);
	if (error) {
	    close(fd);
	    return error;
	}
    }

    return error;
}

int ufs_putdata(const MilkoCred *cred, void *data, struct rx_call *rxcall,
		const AFSFid *fid, const AFSStoreStatus *sstatus, 
		const int32_t offset, const int32_t len, const int32_t filelen,
		AFSFetchStatus *status)
{
    int error, fd;
    ufsmedia *um = (ufsmedia *) data;
    struct stat sb;

    error = ufsi_iopenstat(cred, um, fid->Vnode, fid->Unique, &sb, &fd);
    if (error)
	return error;

    if (S_ISDIR(sb.st_mode)) {
	close(fd);
	return EISDIR;
    }
    if (S_ISLNK(sb.st_mode)) {
	close(fd);
	return EPERM;
    }

    error = ftruncate(fd, filelen);
    if (error) {
	close(fd);
	return errno;
    }
	
    error = mcopyrx2fd(rxcall, fd, offset, len);
    if (error) {
	close(fd);
	return error;
    }

    return ufsi_getstatus(cred, um, fid, status, NULL);
}
/*
 * The definition of the UFS filesystem
 */

struct filesystem ufs_filesystem = {
    "ufs",
    "ufs - filesystem $Revision: 1.5 $",
    ufs_init,		/* fs_init */
    ufs_close,		/* fs_close */

    ufs_getstatus,	/* fs_getstatus */
    ufs_setstatus,	/* fs_setstatus */

    ufs_getdata,	/* fs_getdata */
    ufs_putdata,	/* fs_putdata */

    NULL,		/* fs_makedir */
    NULL,		/* fs_removedir */

    NULL,		/* fs_createfile */
    NULL,		/* fs_rename */
    NULL,		/* fs_removefile */

    NULL,		/* fs_symlink */
    NULL,		/* fs_symlink */

    NULL,		/* fs_fetchacl */
    NULL,		/* fs_storeacl */
    NULL		/* fs_giveupcallback */
} ;

#endif /* FILESYSTEM_READONLY */
