/*
 * Copyright (c) 2000-2001 Silicon Graphics, Inc.; provided copyright in
 * certain portions may be held by third parties as indicated herein.
 * All Rights Reserved.
 *
 * The code in this source file represents an aggregation of work from
 * Georgia Tech, Fred Fish, Jeff Lee, Arnold Robbins and other Silicon
 * Graphics engineers over the period 1985-2000.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it would be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write the Free Software Foundation,
 * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/mtio.h>
#include <sys/param.h>
#include <assert.h>

#include "config.h"
#include "rmtlib.h"
#include "swap.h"


/*
 * uses old_mtget IRIX structure since we don't bother
 * sending the "V" version command.
 */
struct  irix_mtget   {
        short   mt_type;          /* type of magtape device */
        unsigned short  mt_dsreg; /* ``drive status'' register */
        short   mt_erreg;         /* ``error'' register */
        short   mt_resid;         /* residual count */
        int     mt_fileno;        /* file number of current position */
        int     mt_blkno;         /* block number of current position */
};

struct linux32_mtget
{
    int32_t mt_type;           /* Type of magtape device.  */
    int32_t mt_resid;          /* Residual count: */
    /* The following registers are device dependent.  */
    int32_t mt_dsreg;          /* Status register.  */
    int32_t mt_gstat;          /* Generic (device independent) status.  */
    int32_t mt_erreg;          /* Error register.  */
    /* The next two fields are not always used.  */
    int32_t mt_fileno;        /* Number of current file on tape.  */
    int32_t mt_blkno;         /* Current block number.  */
};

struct linux64_mtget
{
    int64_t mt_type;           /* Type of magtape device.  */
    int64_t mt_resid;          /* Residual count. */
    /* The following registers are device dependent.  */
    int64_t mt_dsreg;          /* Status register.  */
    int64_t mt_gstat;          /* Generic (device independent) status.  */
    int64_t mt_erreg;          /* Error register.  */
    /* The next two fields are not always used.  */
    int32_t mt_fileno;        /* Number of current file on tape.  */
    int32_t mt_blkno;         /* Current block number.  */
};


/* IRIX tape device status values */
#define IRIX_MT_EOT          0x01    /* tape is at end of media */
#define IRIX_MT_BOT          0x02    /* tape is at beginning of media */
#define IRIX_MT_WPROT        0x04    /* tape is write-protected */
#define IRIX_MT_EW           0x08    /* hit early warning marker     */
#define IRIX_MT_ONL          0x40    /* drive is online */
#define IRIX_MT_EOD          0x4000  /* tape is at end of data */
#define IRIX_MT_FMK          0x8000  /* tape is at file mark */

/* IRIX mt operations (mt_op values for MTIOCTOP) */
#define IRIX_MTWEOF  0       /* write an end-of-file record */
#define IRIX_MTFSF   1       /* forward space file */
#define IRIX_MTBSF   2       /* backward space file */
#define IRIX_MTFSR   3       /* forward space record */
#define IRIX_MTBSR   4       /* backward space record */
#define IRIX_MTREW   5       /* rewind */
#define IRIX_MTOFFL  6       /* rewind and put the drive offline */
#define IRIX_MTERASE 12      /* erase tape from current position to EOT */
#define IRIX_MTUNLOAD 13     /* unload tape from drive */

/* std (common) mt op codes */
#define STD_MTWEOF  0       /* write an end-of-file record */
#define STD_MTFSF   1       /* forward space file */
#define STD_MTBSF   2       /* backward space file */
#define STD_MTFSR   3       /* forward space record */
#define STD_MTBSR   4       /* backward space record */
#define STD_MTREW   5       /* rewind */
#define STD_MTOFFL  6       /* rewind and put the drive offline */

#define MT_MAX 40 /* encompass potential range of mt_op values */
static int mtop_irixmap[MT_MAX] = {-1};
static int mtop_stdmap[MT_MAX] = {-1};

static void
init_mtop_map(void)
{
    /* set all other values to sentinel (-1) */

    /* only map the ones which xfsdump/restore are interested in */
    mtop_irixmap[MTWEOF]    = IRIX_MTWEOF;
    mtop_irixmap[MTFSF]     = IRIX_MTFSF;
    mtop_irixmap[MTBSF]     = IRIX_MTBSF;
    mtop_irixmap[MTFSR]     = IRIX_MTFSR;
    mtop_irixmap[MTBSR]     = IRIX_MTBSR;
    mtop_irixmap[MTREW]     = IRIX_MTREW;
    mtop_irixmap[MTOFFL]    = IRIX_MTOFFL;
    mtop_irixmap[MTERASE]   = IRIX_MTERASE;
    mtop_irixmap[MTUNLOAD]  = IRIX_MTUNLOAD;

    mtop_stdmap[MTWEOF]    = STD_MTWEOF;
    mtop_stdmap[MTFSF]     = STD_MTFSF;
    mtop_stdmap[MTBSF]     = STD_MTBSF;
    mtop_stdmap[MTFSR]     = STD_MTFSR;
    mtop_stdmap[MTBSR]     = STD_MTBSR;
    mtop_stdmap[MTREW]     = STD_MTREW;
    mtop_stdmap[MTOFFL]    = STD_MTOFFL;
    mtop_stdmap[MTUNLOAD]  = STD_MTOFFL;
}


static int _rmt_ioctl(int, unsigned int, void *);

/*
 *	Do ioctl on file.  Looks just like ioctl(2) to caller.
 */

int
rmtioctl(int fildes, unsigned int request, void *arg)
{
	if (isrmt (fildes)) {
		return (_rmt_ioctl (fildes - REM_BIAS, request, arg));
	}
	else {
		return (ioctl (fildes, request, arg));
	}
}


/*
 *	_rmt_ioctl --- perform raw tape operations remotely
 */

/*
 * WARNING: MTIOCGET code is highly dependent on the format
 *          of mtget on different platforms
 *          We only support Linux 32/ia64 and IRIX 32/64 for this case.
 *          We use the result of uname(1) (in rmtopen()) and
 *          the size of the mtget structure to determine which
 *          architecture it is.
 */

static int
_rmt_ioctl(int fildes, unsigned int op, void *arg)
{
	char buffer[BUFMAGIC];
	int rc, cnt, ssize;
	char *p = NULL, *irixget = NULL, *linux32get = NULL, *linux64get = NULL;
	struct irix_mtget irix_mtget;
	struct linux32_mtget linux32_mtget;
	struct linux64_mtget linux64_mtget;
	int islinux32 = 1; /* is remote machine Linux 32 bit */
	static int onetrip = 0;

	if (!onetrip) {
		onetrip = 1;
		init_mtop_map();
	}

/*
 *	MTIOCTOP is the easy one. nothing is transfered in binary
 */

	if (op == MTIOCTOP) {
		int mt_op = ((struct mtop *) arg)->mt_op;
		int mt_count = ((struct mtop *) arg)->mt_count;

		if (RMTHOST(fildes) == UNAME_UNDEFINED) {
			_rmt_msg(RMTWARN,
		_("rmtioctl: remote host type not supported for MTIOCTOP\n"));
			setoserror(EPROTONOSUPPORT);
			return(-1);
		}

		/* map the linux op code to the irix op code */
		if (RMTHOST(fildes) == UNAME_IRIX) {
			mt_op = mtop_irixmap[mt_op];
			if (mt_op == -1) {
			    setoserror(EINVAL);
			    return(-1);
			}
		}
		else if (RMTHOST(fildes) != UNAME_LINUX) {
		/* map the linux op code to the standard/fallback op code */
			mt_op = mtop_stdmap[mt_op];
			if (mt_op == -1) {
			    setoserror(EINVAL);
			    return(-1);
			}
		}

		sprintf(buffer, "I%d\n%d\n", mt_op, mt_count);
		if (_rmt_command(fildes, buffer) == -1) {
			return(-1);
		}
		return(_rmt_status(fildes));
	}
        else if (op == MTIOCGET) {

		/*
		 *  Grab the status and read it directly into the structure.
		 *  Since the data is binary data, and the other machine might
		 *  be IRIX or Linux of a different byte-order,
		 *  we have to be careful in converting the data.
		 *
		 *  NOTE: the original /etc/rmt did NOT support a newline after
		 *  the S command, and Sun still does not.  Neither does the
		 *  current bsd source, all the way through the tahoe release.
		 *  So do NOT add the \n to this!  The sgi rmt command will
		 *  work either way.  Olson, 4/91
		 */
		if (_rmt_command(fildes, "S") == -1 ||
		    (rc = _rmt_status(fildes)) == -1)
			return(-1);


		/* If undefined then try and define it by looking
		 * and the size of the get structure.
		 * If we know our rmt host, then verify that the
		 * structure is the correct size for the supported ones
		 */
 		switch (RMTHOST(fildes)) {
		    case UNAME_UNDEFINED:
			_rmt_msg(RMTWARN,
		_("rmtioctl: remote host type not supported for MTIOCGET\n"));
			setoserror(EPROTONOSUPPORT);
			return(-1);
		    case UNAME_IRIX:
			if (sizeof(struct irix_mtget) != rc) {
			    _rmt_msg(RMTWARN,
				_("rmtioctl: IRIX mtget structure of wrong size"
				  " - got %d, wanted %d\n"),
				rc, sizeof(struct irix_mtget));
			    setoserror(EPROTONOSUPPORT);
			    return(-1);
			}
			break;
		    case UNAME_LINUX:
			if (sizeof(struct linux32_mtget) == rc) {
			    islinux32 = 1;
			}
			else if (sizeof(struct linux64_mtget) == rc) {
			    islinux32 = 0;
			}
			else {
			    _rmt_msg(RMTWARN,
			_("rmtioctl: Linux mtget structure of wrong size "
			  "- got %d, wanted %d or %d\n"),
				rc, sizeof(struct linux32_mtget),
				sizeof(struct linux64_mtget));
			    setoserror(EPROTONOSUPPORT);
			    return(-1);
			}
			break;
		    default:
			setoserror(EPROTONOSUPPORT);
			return(-1);
		}


		assert(RMTHOST(fildes)==UNAME_LINUX || RMTHOST(fildes)==UNAME_IRIX);


		if (RMTHOST(fildes) == UNAME_IRIX) {
		    p = irixget = (char *)&irix_mtget;
		}
		else if (islinux32) {
		    p = linux32get = (char *)&linux32_mtget;
		}
		else {
		    p = linux64get = (char *)&linux64_mtget;
		}


		/* read in all the data */
		ssize = rc;
		for (; ssize > 0; ssize -= cnt, p += cnt) {
			cnt = read(READ(fildes), p, ssize);
			if (cnt <= 0) {
				_rmt_abort(fildes);
				setoserror(EIO);
				return(-1);
			}
		}


		/*
		 * May need to byteswap
		 */
		if (RMTHOST(fildes) == UNAME_IRIX) {
			struct irix_mtget *irixp = (struct irix_mtget *)irixget;

			if (irixp->mt_type > 0xff) {
			    /* assume that mt_type should fit in 1 byte */

			    irixp->mt_type   = INT_SWAP(irixp->mt_type, irixp->mt_type);
			    irixp->mt_dsreg  = INT_SWAP(irixp->mt_dsreg, irixp->mt_dsreg);
			    irixp->mt_erreg  = INT_SWAP(irixp->mt_erreg, irixp->mt_erreg);
			    irixp->mt_resid  = INT_SWAP(irixp->mt_resid, irixp->mt_resid);
			    irixp->mt_fileno = INT_SWAP(irixp->mt_fileno, irixp->mt_fileno);
			    irixp->mt_blkno  = INT_SWAP(irixp->mt_blkno, irixp->mt_blkno);
			}
		}
		else if (islinux32) {
			struct linux32_mtget *linuxp = (struct linux32_mtget *)linux32get;

			if (linuxp->mt_type > 0xffff) {
			    /* assume that mt_type should fit in 2 bytes */

			    linuxp->mt_type   = INT_SWAP(linuxp->mt_type, linuxp->mt_type);
			    linuxp->mt_dsreg  = INT_SWAP(linuxp->mt_dsreg, linuxp->mt_dsreg);
			    linuxp->mt_erreg  = INT_SWAP(linuxp->mt_erreg, linuxp->mt_erreg);
			    linuxp->mt_resid  = INT_SWAP(linuxp->mt_resid, linuxp->mt_resid);
			    linuxp->mt_fileno = INT_SWAP(linuxp->mt_fileno, linuxp->mt_fileno);
			    linuxp->mt_blkno  = INT_SWAP(linuxp->mt_blkno, linuxp->mt_blkno);
			    linuxp->mt_gstat  = INT_SWAP(linuxp->mt_gstat, linuxp->mt_gstat);

			}
		}
		else {
			struct linux64_mtget *linuxp = (struct linux64_mtget *)linux64get;

			if (linuxp->mt_type > 0xffff) {
			    /* assume that mt_type should fit in 2 bytes */

			    linuxp->mt_type   = INT_SWAP(linuxp->mt_type, linuxp->mt_type);
			    linuxp->mt_dsreg  = INT_SWAP(linuxp->mt_dsreg, linuxp->mt_dsreg);
			    linuxp->mt_erreg  = INT_SWAP(linuxp->mt_erreg, linuxp->mt_erreg);
			    linuxp->mt_resid  = INT_SWAP(linuxp->mt_resid, linuxp->mt_resid);
			    linuxp->mt_fileno = INT_SWAP(linuxp->mt_fileno, linuxp->mt_fileno);
			    linuxp->mt_blkno  = INT_SWAP(linuxp->mt_blkno, linuxp->mt_blkno);
			    linuxp->mt_gstat  = INT_SWAP(linuxp->mt_gstat, linuxp->mt_gstat);

			}
		}

		/*
		 * now mtget has the correct (byte-swapped if needed) data,
                 * so we just need to copy over the fields which are possibly
                 * of different length and different semantics.
		 */
		if (RMTHOST(fildes) == UNAME_IRIX) {
			struct mtget *dstp = (struct mtget *)arg;
			struct irix_mtget *srcp = (struct irix_mtget *)irixget;
			short status = srcp->mt_dsreg;

			dstp->mt_type = srcp->mt_type;
			dstp->mt_erreg = srcp->mt_erreg;
			dstp->mt_resid = srcp->mt_resid;
			dstp->mt_fileno = srcp->mt_fileno;
			dstp->mt_blkno = srcp->mt_blkno;
			dstp->mt_dsreg = srcp->mt_dsreg; /* different semantics */

			/* need to do tape status conversions */
			dstp->mt_gstat = 0;
			if (status & IRIX_MT_EOT)
			    dstp->mt_gstat |= GMT_EOT(0xffffffff);
			if (status & IRIX_MT_BOT)
			    dstp->mt_gstat |= GMT_BOT(0xffffffff);
			if (status & IRIX_MT_WPROT)
			    dstp->mt_gstat |= GMT_WR_PROT(0xffffffff);
			if (status & IRIX_MT_ONL)
			    dstp->mt_gstat |= GMT_ONLINE(0xffffffff);
			if (status & IRIX_MT_EOD)
			    dstp->mt_gstat |= GMT_EOD(0xffffffff);
			if (status & IRIX_MT_FMK)
			    dstp->mt_gstat |= GMT_EOF(0xffffffff);
			if (status & IRIX_MT_EW)
			    ;/* No GMT_ to map it to */
		}
		else if (islinux32) {
			struct mtget *dstp = (struct mtget *)arg;
			struct linux32_mtget *srcp = (struct linux32_mtget *)linux32get;

			dstp->mt_type = srcp->mt_type;
			dstp->mt_erreg = srcp->mt_erreg;
			dstp->mt_resid = srcp->mt_resid;
			dstp->mt_fileno = srcp->mt_fileno;
			dstp->mt_blkno = srcp->mt_blkno;
			dstp->mt_dsreg = srcp->mt_dsreg;
			dstp->mt_gstat = srcp->mt_gstat;
		}
		else {
			struct mtget *dstp = (struct mtget *)arg;
			struct linux64_mtget *srcp = (struct linux64_mtget *)linux64get;

			dstp->mt_type = srcp->mt_type;
			dstp->mt_erreg = srcp->mt_erreg;
			dstp->mt_resid = srcp->mt_resid;
			dstp->mt_fileno = srcp->mt_fileno;
			dstp->mt_blkno = srcp->mt_blkno;
			dstp->mt_dsreg = srcp->mt_dsreg;
			dstp->mt_gstat = srcp->mt_gstat;
		}
		return(0);

	}
        else {
	    setoserror(EINVAL);
	    return(-1);
	}
}
