/*
 * Copyright 2000, International Business Machines Corporation and others.
 * All Rights Reserved.
 *
 * This software has been released under the terms of the IBM Public
 * License.  For details, see the LICENSE file in the top-level source
 * directory or online at http://www.openafs.org/dl/license10.html
 */

#include <afsconfig.h>
#include <afs/param.h>

#include <roken.h>
#include <afs/opr.h>

#if defined(AFS_AIX_ENV)
#include <sys/tape.h>
#include <sys/statfs.h>
#else
#ifdef AFS_DARWIN_ENV
#include <sys/ioccom.h>
#endif
#if defined(AFS_DUX40_ENV) || defined(AFS_OBSD_ENV) || defined(AFS_NBSD_ENV) || (defined(AFS_DARWIN_ENV) && !defined(AFS_DARWIN100_ENV))
#include <sys/ioctl.h>
#endif
#ifndef AFS_DARWIN100_ENV
#include <sys/mtio.h>
#endif
#endif /* AFS_AIX_ENV */

#include "usd.h"

#ifdef O_LARGEFILE
typedef off64_t osi_lloff_t;
#define osi_llseek      lseek64
#else /* O_LARGEFILE */
#ifdef AFS_HAVE_LLSEEK
typedef offset_t osi_lloff_t;
#define osi_llseek      llseek
#else /* AFS_HAVE_LLSEEK */
typedef off_t osi_lloff_t;
#define osi_llseek      lseek
#endif /* AFS_HAVE_LLSEEK */
#endif /* O_LARGEFILE */

/*
 * This macro should be used inside assertions wherever offset/hyper
 * conversion occur.  A good compiler in a 64 bit environment will
 * elide the entire statement if the offset type is 64 bits wide.
 */
#define osi_hFitsInOff(val) \
    ((sizeof(osi_lloff_t) == 4) ? (((val) & 0xffffffff00000000LL) == 0) : 1)

/************ End of osi wrappers ***********************************/

/* Implementation of user space device I/O for regular POSIX files. */

static int
usd_FileRead(usd_handle_t usd, char *buf, afs_uint32 nbytes,
	     afs_uint32 * xferdP)
{
    int fd = (intptr_t)(usd->handle);
    int got;

    got = read(fd, buf, nbytes);
    if (got == -1) {
	if (xferdP)
	    *xferdP = 0;
	return errno;
    }
    if (xferdP)
	*xferdP = got;
    return 0;
}

static int
usd_FileWrite(usd_handle_t usd, char *buf, afs_uint32 nbytes,
	      afs_uint32 * xferdP)
{
    int fd = (intptr_t)(usd->handle);
    int sent;

    sent = write(fd, buf, nbytes);
    if (sent == -1) {
	if (xferdP)
	    *xferdP = 0;
	return errno;
    }
    if (xferdP)
	*xferdP = sent;
    return 0;
}

extern osi_lloff_t osi_llseek(int, osi_lloff_t, int);

static int
usd_FileSeek(usd_handle_t usd, afs_int64 reqOff, int whence,
	     afs_int64 * curOffP)
{
    int fd = (intptr_t)(usd->handle);
    osi_lloff_t lloff;

    if (!osi_hFitsInOff(reqOff))
	return EINVAL;

    lloff = osi_llseek(fd, reqOff, whence);
    if (lloff == (((osi_lloff_t) 0) - 1))
	return errno;
    if (curOffP)
	*curOffP = lloff;

    return 0;
}

static int
usd_FileIoctl(usd_handle_t usd, int req, void *arg)
{
    int fd = (intptr_t)(usd->handle);
#ifdef O_LARGEFILE
    struct stat64 info;
#else /* O_LARGEFILE */
    struct stat info;
#endif /* O_LARGEFILE */
#ifdef AFS_AIX_ENV
    struct statfs fsinfo;	/* AIX stat structure doesn't have st_blksize */
#endif /* AFS_AIX_ENV */
    afs_int64 size;
    int code = 0;

    switch (req) {
    case USD_IOCTL_GETBLKSIZE:
#ifdef AFS_AIX_ENV
	code = fstatfs(fd, &fsinfo);
	if (code) {
	    *((long *)arg) = (long)4096;
	    return 0;
	}
	break;
#endif /* AFS_AIX_ENV */
    case USD_IOCTL_GETTYPE:
    case USD_IOCTL_GETDEV:
    case USD_IOCTL_GETSIZE:
#ifdef O_LARGEFILE
	code = fstat64(fd, &info);
#else /* O_LARGEFILE */
	code = fstat(fd, &info);
#endif /* O_LARGEFILE */
	if (code)
	    return errno;
	break;
    }

    switch (req) {
    case USD_IOCTL_GETTYPE:
	*(int *)arg = info.st_mode & S_IFMT;
	break;
    case USD_IOCTL_GETDEV:
	if (!(S_ISCHR(info.st_mode) || S_ISBLK(info.st_mode)))
	    return ENODEV;	/* not a device */
	*(dev_t *) arg = info.st_rdev;
	break;
    case USD_IOCTL_GETSIZE:
	if (S_ISCHR(info.st_mode) || S_ISBLK(info.st_mode))
	    return ENOTTY;	/* shouldn't be a device */
	*(afs_int64 *)arg = info.st_size;
	break;
    case USD_IOCTL_GETFULLNAME:
	*(char **)arg = usd->fullPathName;
	break;

    case USD_IOCTL_SETSIZE:

	/* We could just use ftruncate in all cases.  (This even works on AIX;
	 * I tried it). -blake 931118 */

	/* However, I'm pretty sure this doesn't work on Ultrix so I am
	 * unsure about OSF/1 and HP/UX. 931118 */

	size = *(afs_int64 *) arg;
	if (!osi_hFitsInOff(size))
	    return EFBIG;
#ifdef O_LARGEFILE
	code = ftruncate64(fd, size);
#else /* O_LARGEFILE */
	code = ftruncate(fd, size);
#endif /* O_LARGEFILE */
	if (code == -1)
	    code = errno;
	return code;

    case USD_IOCTL_TAPEOPERATION:
	{
#ifdef AFS_DARWIN100_ENV
	    code = EOPNOTSUPP;
#else
	    usd_tapeop_t *tapeOpp = (usd_tapeop_t *) arg;
#if defined(AFS_AIX_ENV)
	    struct stop os_tapeop;

	    if (tapeOpp->tp_op == USDTAPE_WEOF) {
		os_tapeop.st_op = STWEOF;
	    } else if (tapeOpp->tp_op == USDTAPE_REW) {
		os_tapeop.st_op = STREW;
	    } else if (tapeOpp->tp_op == USDTAPE_FSF) {
		os_tapeop.st_op = STFSF;
	    } else if (tapeOpp->tp_op == USDTAPE_BSF) {
		os_tapeop.st_op = STRSF;
	    } else if (tapeOpp->tp_op == USDTAPE_PREPARE) {
		return 0;
	    } else if (tapeOpp->tp_op == USDTAPE_SHUTDOWN) {
		return 0;
	    } else {
		/* unsupported tape operation */
		return EINVAL;
	    }
	    os_tapeop.st_count = tapeOpp->tp_count;

	    code = ioctl(fd, STIOCTOP, &os_tapeop);
#else
	    struct mtop os_tapeop;

	    if (tapeOpp->tp_op == USDTAPE_WEOF) {
		os_tapeop.mt_op = MTWEOF;
	    } else if (tapeOpp->tp_op == USDTAPE_REW) {
		os_tapeop.mt_op = MTREW;
	    } else if (tapeOpp->tp_op == USDTAPE_FSF) {
		os_tapeop.mt_op = MTFSF;
	    } else if (tapeOpp->tp_op == USDTAPE_BSF) {
		os_tapeop.mt_op = MTBSF;
	    } else if (tapeOpp->tp_op == USDTAPE_PREPARE) {
		return 0;
	    } else if (tapeOpp->tp_op == USDTAPE_SHUTDOWN) {
		return 0;
	    } else {
		/* unsupported tape operation */
		return EINVAL;
	    }
	    os_tapeop.mt_count = tapeOpp->tp_count;

	    code = ioctl(fd, MTIOCTOP, &os_tapeop);
#endif /* AFS_AIX_ENV */
#endif
	    if (code == -1) {
		code = errno;
	    } else {
		code = 0;
	    }
	    return code;
	}

    case USD_IOCTL_GETBLKSIZE:
	if (S_ISCHR(info.st_mode) || S_ISBLK(info.st_mode)) {
	    *((long *)arg) = (long)4096;
	    return 0;
	}
#ifdef AFS_AIX_ENV
	*((long *)arg) = (long)fsinfo.f_bsize;
#else /* AFS_AIX_ENV */
	*((long *)arg) = (long)info.st_blksize;
#endif /* AFS_AIX_ENV */
	break;

    default:
	return EINVAL;
    }
    return code;
}

static int
usd_FileClose(usd_handle_t usd)
{
    int fd = (intptr_t)(usd->handle);
    int code = 0;
    int ccode;

    /* I can't really believe this is necessary.  On the one hand the user
     * space code always uses character devices, which aren't supposed to do
     * any buffering.  On the other, I very much doubt fsyncing a regular file
     * being salvaged is ever necessary.  But the salvager used to do this
     * before returning, so... */

    if (usd->openFlags & (O_WRONLY | O_RDWR)) {
	int mode;
	code = usd_FileIoctl(usd, USD_IOCTL_GETTYPE, &mode);
	if (code == 0 && S_ISBLK(mode)) {
	    if (fsync(fd) < 0)
		code = errno;
	}
    }

    ccode = close(fd);
    if (!code && ccode)
	code = errno;

    if (usd->fullPathName)
	free(usd->fullPathName);
    free(usd);

    return code;
}

static int
usd_FileOpen(const char *path, int flags, int mode, usd_handle_t * usdP)
{
    int fd;
    int oflags;
    usd_handle_t usd;
    int code;

    if (usdP)
	*usdP = NULL;

    oflags = (flags & USD_OPEN_RDWR) ? O_RDWR : O_RDONLY;

#ifdef O_SYNC			/* AFS_DARWIN_ENV XXX */
    if (flags & USD_OPEN_SYNC)
	oflags |= O_SYNC;
#endif

    if (flags & USD_OPEN_CREATE)
	oflags |= O_CREAT;

#ifdef O_LARGEFILE
    fd = open64(path, oflags | O_LARGEFILE, mode);
#else /* O_LARGEFILE */
    fd = open(path, oflags, mode);
#endif /* O_LARGEFILE */
    if (fd == -1)
	return errno;

    usd = (usd_handle_t) malloc(sizeof(*usd));
    memset(usd, 0, sizeof(*usd));
    usd->handle = (void *)(intptr_t)fd;
    usd->read = usd_FileRead;
    usd->write = usd_FileWrite;
    usd->seek = usd_FileSeek;
    usd->ioctl = usd_FileIoctl;
    usd->close = usd_FileClose;
    usd->fullPathName = (char *)malloc(strlen(path) + 1);
    strcpy(usd->fullPathName, path);
    usd->openFlags = flags;

    code = 0;
    if (flags & (USD_OPEN_RLOCK | USD_OPEN_WLOCK)) {
#ifdef O_LARGEFILE
	struct flock64 fl;
#else /* O_LARGEFILE */
	struct flock fl;
#endif /* O_LARGEFILE */

	/* make sure both lock bits aren't set */
	opr_Assert(~flags & (USD_OPEN_RLOCK | USD_OPEN_WLOCK));

	fl.l_type = ((flags & USD_OPEN_RLOCK) ? F_RDLCK : F_WRLCK);
	fl.l_whence = SEEK_SET;
	fl.l_start = (osi_lloff_t) 0;
	fl.l_len = (osi_lloff_t) 0;	/* whole file */
#ifdef O_LARGEFILE
	code = fcntl(fd, F_SETLK64, &fl);
#else /* O_LARGEFILE */
	code = fcntl(fd, F_SETLK, &fl);
#endif /* O_LARGEFILE */
	if (code == -1)
	    code = errno;

	/* If we're trying to obtain a write lock on a real disk, then the
	 * aggregate must not be attached by the kernel.  If so, unlock it
	 * and fail.
	 * WARNING: The code to check for the above has been removed when this
	 * file was ported from DFS src. It should be put back if
	 * this library is used to access hard disks
	 */
    }

    if (code == 0 && usdP)
	*usdP = usd;
    else
	usd_FileClose(usd);
    return code;
}

static int
usd_FileDummyClose(usd_handle_t usd)
{
    free(usd);
    return 0;
}

int
usd_Open(const char *path, int oflag, int mode, usd_handle_t * usdP)
{
    return usd_FileOpen(path, oflag, mode, usdP);
}

static int
usd_FileStandardInput(usd_handle_t * usdP)
{
    usd_handle_t usd;

    if (usdP)
	*usdP = NULL;

    usd = (usd_handle_t) malloc(sizeof(*usd));
    memset(usd, 0, sizeof(*usd));
    usd->handle = (void *)((unsigned long)0);
    usd->read = usd_FileRead;
    usd->write = usd_FileWrite;
    usd->seek = usd_FileSeek;
    usd->ioctl = usd_FileIoctl;
    usd->close = usd_FileDummyClose;
    usd->fullPathName = "STDIN";
    usd->openFlags = 0;
    *usdP = usd;

    return 0;
}

int
usd_StandardInput(usd_handle_t * usdP)
{
    return usd_FileStandardInput(usdP);
}

static int
usd_FileStandardOutput(usd_handle_t * usdP)
{
    usd_handle_t usd;

    if (usdP)
	*usdP = NULL;

    usd = (usd_handle_t) malloc(sizeof(*usd));
    memset(usd, 0, sizeof(*usd));
    usd->handle = (void *)((unsigned long)1);
    usd->read = usd_FileRead;
    usd->write = usd_FileWrite;
    usd->seek = usd_FileSeek;
    usd->ioctl = usd_FileIoctl;
    usd->close = usd_FileDummyClose;
    usd->fullPathName = "STDOUT";
    usd->openFlags = 0;
    *usdP = usd;

    return 0;
}

int
usd_StandardOutput(usd_handle_t * usdP)
{
    return usd_FileStandardOutput(usdP);
}
