/*===========================================================================
  Copyright (C) 1995-2011 European Southern Observatory (ESO)
 
  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; either version 2 of 
  the License, or (at your option) any later version.
 
  This program is distributed in the hope that it will 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 to the Free 
  Software Foundation, Inc., 675 Massachusetts Ave, Cambridge, 
  MA 02139, USA.
 
  Correspondence concerning ESO-MIDAS should be addressed as follows:
	Internet e-mail: midas@eso.org
	Postal address: European Southern Observatory
			Data Management Division 
			Karl-Schwarzschild-Strasse 2
			D 85748 Garching bei Muenchen 
			GERMANY
===========================================================================*/

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.TYPE        Module
.NAME        iodevr
.LANGUAGE    C
.AUTHOR      IPG-ESO Garching
.CATEGORY    Host operating system interfaces. Remote tape management.
.ENVIRONMENT UNIX
.COMMENTS    Remote tape management. 

	The functions of this module provides remote connection with a 
	server in a remote host which executes basic i/o on devices like
	magnetic tapes, optical disks on UNIX enviroments.

	This interface is called by "osu" when detecting that i/o device
	is not located in a local but in a remote host. It will try to
	establish connection with a tapeserver in the remote host.

	A new INET service is expected to be defined for both
	client and server in the system file /etc/services: mtape
	Port numbers for private services are typically in the range 1024-2048.

.REMARKS 	The tapeserver must be running in the remote host before 
		trying to stablish connection with remote devices. (See 
		$MIDASHOME/$MIDVERS/system/tapeserv/inmtaped.doc  file
		for more details)

.VERSION 1.1	900124   Implementation     C. Guirao

 110525		last modif

------------------------------------------------------------*/

/*
 * Define _POSIX_SOURCE to indicate
 * that this is a POSIX program
 * Except for SUN & GNU-SUN & HP-UX & OSF & AIX & ULTRIX & SGI
 * because the POSIX definition
 * causes undefines (eg. u_long) in socket includes.
 */

#if !defined(__APPLE__) && !defined(__hpux) && !defined(sun) && !defined(__sun) && !defined(__osf__) && !defined(_AIX) && !defined(ultrix) && !defined(__sgi) && !defined(__linux__) &&!defined(__FreeBSD__)
#define _POSIX_SOURCE 1
#endif


#ifndef Cygwin

/* Cygwin has no XDR structures... */

#define next_iodev	iodevg		/* Next iodev in chain: generic */
static char class_name[] = "remote";	

#include <unistd.h>
#include <sys/types.h>
#include <proto_os.h>		        /* ANSI-C prototyping */
#include <stdio.h>
#include <stdlib.h>
#include <rpc/rpc.h>			/* XDR definitions */
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <errno.h>
#include <oserror.h>
#include <string.h>

#include <osudef.h>
#include <rtape.h>
#include <osparms.h>

#define NULL_PTR(x)     (x *)0
#define RET_ERROR(a,b) {osxclose(skfd); oserror=(a); oserrmsg=(b); return(-1);}
#define TAPE_SERVICE "mtape"	/* must be defined in /etc/services */


#define ERR_SYNC 	msg0
#define ERR_XDR 	msg1
static char msg0[] = "Out of synchronism with tape server.";
static char msg1[] = "XDR library error";
static char errmsg[MAX_MSG];

char *osmsg();
/* FILE *fdopen(); */

static 	int skfd;		/* Socket file descriptor */
FILE *fp_in, *fp_out;

struct info {
	struct osustat stat;
	int  filenum;
	long blkno;
        };

/*
 * XDR routines
 */
static XDR xdrs_in, xdrs_out;
bool_t xdr_command(xdrs, cmd)
XDR *xdrs;
struct command *cmd;
{
        return(xdr_int(xdrs, &cmd->cmd) &&
               xdr_int(xdrs, &cmd->param[0]) &&
               xdr_int(xdrs, &cmd->param[1]));
}

bool_t xdr_osustat(xdrs, stat)
XDR *xdrs;
struct osustat *stat;
{
#if defined(__APPLE__)
int    for_stat_usize;

        return (xdr_long(xdrs, &for_stat_usize) &&
               xdr_int(xdrs, &stat->blocksize) &&
               xdr_int(xdrs, &stat->density) &&
               xdr_int(xdrs, &stat->isda) &&
               xdr_int(xdrs, &stat->istm));
#else
        return(xdr_long(xdrs, &stat->usize) &&
               xdr_int(xdrs, &stat->blocksize) &&
               xdr_int(xdrs, &stat->density) &&
               xdr_int(xdrs, &stat->isda) &&
               xdr_int(xdrs, &stat->istm));
#endif
}

bool_t xdr_result(xdrs, res)
XDR *xdrs;
struct result *res;
{
        return(xdr_int(xdrs, &res->ret) &&
               xdr_int(xdrs, &res->oserror) &&
               xdr_int(xdrs, &res->nobyt));
}


static int send_op(op)
struct command *op;
{
	oserror = 0;

	/* Send the command to the server */
        if (!xdr_command(&xdrs_out, op)) RET_ERROR(-1,ERR_XDR);
	fflush(fp_out);

	return(0);
}

static int get_res(res)
struct result *res;
{
	char *msg;

	oserror = 0;
	msg = errmsg;
	/* Wait and get results from the server */
        if (!xdr_result(&xdrs_in, res)) RET_ERROR(-1,ERR_XDR);

        /* If any error, then get error message */
        oserror = res->oserror;
        if ( res->ret < 0 && res->nobyt > 0) 
        	if (!xdr_string(&xdrs_in, &msg,MAX_MSG)) RET_ERROR(-1,ERR_XDR);
        return(0);
}

static int ioctop(op)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Interface to remote ioctl routine.
	 The structure command is already completed by the caller.
.RETURNS 0 (OK) / -1 (error)
------------------------------------------------------------*/
struct command *op; 		/* IN: Command to be executed */
{
	struct result res;

	/* Send command and get results  */
	if (send_op(op) < 0) return(-1);
	if (get_res(&res) < 0) return(-1);

	/* Returns the remote-return-code */
        return(res.ret);
}

static int ioinfo(fd, s, filenum, blkno)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Retrieve Info concerning an opened device
.RETURNS 0 (success) / -1 (error)
------------------------------------------------------------*/
int	fd;		/* IN: File Descriptor		*/
struct osustat	*s;	/* OUT: The filled components */
int     *filenum;       /* OUT: Where we are    */
#if defined(__APPLE__)
int    *blkno;         /* OUT: Where we are    */
#else
long    *blkno;         /* OUT: Where we are    */
#endif
{
	struct command op;
	struct result res;


	/* Prepare command structure to tapeserver */
	op.cmd = READ_INFO;
	op.param[0] = fd;
	op.param[1] = 0;

	/* Send the command to the server */

	/* Send command and get results  */
	if (send_op(&op) < 0) return(-1);
	if (get_res(&res) < 0) return(-1);

	/* If no error, more data is expected from the server */
	if ( res.ret == 0) {
        	if (!xdr_osustat(&xdrs_in, s))  RET_ERROR(-1,ERR_XDR);
		if (!xdr_int(&xdrs_in, filenum)) RET_ERROR(-1,ERR_XDR);
		if (!xdr_long(&xdrs_in, blkno)) RET_ERROR(-1,ERR_XDR);
		}

	/* It returns the remote return code */
        return(res.ret);
}


static int ioopen(name,mode,den)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Open a tape device
.RETURNS File descriptor / -1 (error)
------------------------------------------------------------*/
char *name;             /* IN:  Physical name of tape device */
int mode;               /* IN:  Open mode */
int den;                /* IN:  Density. Not used */
{
	static char devname[MAX_DEVICE_NAME], remotehost[MAX_HOST_NAME];
	static char *channame[2];
	register char *p, *q;
	int uid;
	struct command op;
	struct result res;
	struct passwd *passwd;
	char null;
        char *pname, *puser;

        oserror = 0;            /* Clear error from getdev      */

	/* Check that name comes in the format <remotehost!device. */
        for (q = remotehost , p = name ; (*p) && (*p != '!') && (*p != ':') ; p++, q++) *q = *p;
        *q = '\0';
 	if (*p == '!' || *p == ':' ) p++;
        else 	{
		oserror = -1;
		oserrmsg = "Name not in <remotehost:device> format";
		return(-1);
		}
        for (q = devname; *p; p++, q++)       *q = *p;
        *q = '\0';

	channame[0] = TAPE_SERVICE;
	channame[1] = remotehost;
	/* Open socket in WRITE mode to establish connection with remote host */
	channame[0] = TAPE_SERVICE;
	if ( (skfd=osxopen(channame,NETW|IPC_WRITE)) < 0 ) {
		(void)strcpy(errmsg,"OPEN SOCKET WRITE_MODE: ");
		strcat(errmsg,osmsg());
		oserrmsg=errmsg;
		oserror = -1;
		return(-1);
		}

	/*
 	 * Empty the input buffer. It could contains error messages from the
	 * the inetd itself (eg. ld.so: warning: /usr/lib/libc.so.1.8 ....
	 * NOTE: the timeout is set to 1 sec. this might need to be increased.
	 */
	while ( osxinfo(skfd, 1, 0) == DATARDY ) osxread(skfd, &null, 1);

	fp_in = fdopen(skfd,"r");
	fp_out = fdopen(skfd,"w");
	xdrstdio_create(&xdrs_in, fp_in, XDR_DECODE);
	xdrstdio_create(&xdrs_out, fp_out, XDR_ENCODE);

	/* 
	 * Prepares command structure for OPEN device.
	 * In this command is indicated length of a second buffer to be
	 * sent to the server with the device name, and the name of local
	 * user.
	 */
	uid=getuid();
	passwd=getpwuid(uid);
	op.cmd = OPEN_DEVICE;
	op.param[0] = mode;
	op.param[1] = 0;

	/* Send OPEN command to server */
	if (send_op(&op) < 0) return(-1);

	/* Prepare a buffer with the second part of OPEN command */
	pname = name;
	puser = passwd->pw_name;

	/* Send devicename and username to server */
	if (!xdr_string(&xdrs_out, &pname, MAX_HOST_NAME+MAX_DEVICE_NAME))
		RET_ERROR(-1,ERR_XDR);
	if (!xdr_string(&xdrs_out, &puser, MAX_USER_NAME))
		RET_ERROR(-1,ERR_XDR);
	fflush(fp_out);

	/* Read result of OPEN command from server */
	if (get_res(&res) < 0) return(-1);

	/* Returns return_code from remote open */
        return(res.ret);
}

static int ioclose(fd/*,option*/)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Close the opened device
.RETURNS 0/-1 if error
------------------------------------------------------------*/
int fd;                 /* IN:  Tape file descriptor */
/*int option;*/		/* IN:	Option OPU_DISMOUNT	*/
{
	struct command op;
	struct result res;

	/* Prepare CLOSE command to be sent to server */
	op.cmd = CLOSE_DEVICE;
	op.param[0] = fd;
	op.param[1] = 0;

	/* Send CLOSE command */
	if (send_op(&op) < 0) return(-1);

	/* Wait for the server to return result */
	if (get_res(&res) < 0) return(-1);

	/* Close socket */
	if ( osxclose(skfd) < 0) return(-1);

	/* Close XDR streams */
	xdr_destroy(&xdrs_in);
	xdr_destroy(&xdrs_out);

	/* Returns the return code from server */
        return(res.ret);
}


static int ioread(fd,buffer,size)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Read a block from a magnetic tape.
.RETURNS Bytes read / -1 if error
.REMARKS 0 Bytes read, means a File Mark was detected.
.REMARKS oserror set to -2 if buffer too small
------------------------------------------------------------*/
int fd;                 /* IN:  Tape device file descriptor */
char *buffer;           /* IN:  Buffer for reading */
int size;               /* IN:  Length of bytes to be read */
{
	int ret;
	struct command op;
	struct result res;

	/* Prepares READ command */
	op.cmd = READ_DATA;
	op.param[0] = fd;
	op.param[1] = size;

	/* Send READ command to server */
	if (send_op(&op) < 0) return(-1);

	/* Wait for results of READ command */
	if (get_res(&res) < 0) return(-1);

        /* READ OK?, then get the data from the server */
	if ( res.ret > 0 ) {
		if (!xdr_bytes(&xdrs_in, &buffer,(u_int *)&ret,(u_int)size)) 
			RET_ERROR(-1,ERR_XDR);
		if ( ret != res.ret ) RET_ERROR(-1,ERR_SYNC);
		}

	/* Returns the return code from server */
        return(res.ret);
}

static int iowrite(fd,buffer,size)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Write a block on a magnetic tape.
.RETURNS Bytes written / -1 (error)
------------------------------------------------------------*/
int fd;                 /* IN:  Tape device file descriptor */
char *buffer;           /* IN:  Buffer for reading */
int size;               /* IN:  Length of bytes to be read */
{
        struct result res;
        struct command op;

	/* Prepares WRITE command */
	op.cmd = WRITE_DATA;
	op.param[0] = fd;
	op.param[1] = size;

	/* Send WRITE command to server */
	if (send_op(&op) < 0 ) return(-1);

	/* Send data to be written in device */
	if (!xdr_bytes(&xdrs_out, &buffer,(u_int *)&size,(u_int)size)) RET_ERROR(-1,ERR_XDR);
	fflush(fp_out);

	/* Wait for results of WRITE command */
	if (get_res(&res) < 0) return(-1);
	
	/* Return code from server */
        return(res.ret);
}

static int ioweof(fd,ntm)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Write end-of-file record (tape_mark) on the tape.
.RETURNS 0 / -1 (error)
------------------------------------------------------------*/
int fd;                 /* IN:  Tape device file descriptor */
int ntm;		/* IN:  Number of tape marks */
{
	struct command op;

	/* Prepare WRITE TAPE MARK command */
	op.cmd = WRITE_EOF;
	op.param[0] = fd;
	op.param[1] = ntm;

	/* Executes command in server and returns the remote-return-code */
	return(ioctop(&op));
}

static int iofsf(fd,ntm)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Skip forward space file on a tape. 
.RETURNS Tape marks skipped/ -1 (error)
------------------------------------------------------------*/
int fd;                 /* IN:  Tape device file descriptor */
int ntm;                /* IN:  Number of tape marks */
{
	struct command op;

	/* Prepare command */
	op.cmd = SKIP_FSF;
	op.param[0] = fd;
	op.param[1] = ntm;

	/* Executes command in server and returns the remote-return-code */
	return(ioctop(&op));
}


static int iobsf(fd,ntm)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Skip backward space file on a tape. 
.RETURNS Tape marks skipped/ -1 (error)
------------------------------------------------------------*/
int fd;                 /* IN:  Tape device file descriptor */
int ntm;                /* IN:  Number of tape marks */
{
	struct command op;

	/* Prepare command */
	op.cmd = SKIP_BSF;
	op.param[0] = fd;
	op.param[1] = ntm;

	/* Executes command in server and returns the remote-return-code */
	return(ioctop(&op));
}


static int iorew(fd)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Rewind tape
.RETURNS 0 / -1 (error)
------------------------------------------------------------*/
int fd;                 /* IN:  Tape device file descriptor */
{
	struct command op;

	/* Prepare command */
	op.cmd = REWIND;
	op.param[0] = fd;
	op.param[1] = 0;

	/* Executes command in server and returns the remote-return-code */
	return(ioctop(&op));
}

static int ioeom(fd)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Move to EOMedia
.RETURNS 0 (OK) / -1 (not done, error)
------------------------------------------------------------*/
int fd;                 /* IN:  Tape device file descriptor */
{
	struct command op;

	/* Prepare command */
	op.cmd = SKIP_EOM;
	op.param[0] = fd;
	op.param[1] = 0;

	/* Executes command in server and returns the remote-return-code */
	return(ioctop(&op));
}

/*=====================================================================
 * 		Definition of the structure returned to osu
 *=====================================================================*/
#ifdef next_iodev
struct iolist *next_iodev();
#else
#define next_iodev      (IODEV)0
#endif

static OPITEM list_of_functions[] = {
	{ U_INFO,	ioinfo},
	{ U_OPEN,	ioopen},
	{ U_CLOSE,  	ioclose},
	{ U_READ,	ioread},
	{ U_WRITE,	iowrite},
	{ U_REWIND, 	iorew},
	{ U_WEOF,	ioweof},
	{ U_FMF,	iofsf},	/* File Move Forward  	*/
	{ U_FMB,	iobsf},	/* File Move Backward 	*/
	{ U_EOM,	ioeom}	/* Move to EOMedia 	*/
 };

static struct iolist this_dev = {
        next_iodev,     /* Next iodev in List */
	class_name,	/* How it's written in DEVCAPFILE */
	sizeof(list_of_functions)/sizeof(list_of_functions[0]),
	list_of_functions
 };
 
struct iolist *iodevr()
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Get All definitions concerning this class of Devices
.RETURNS The iolist
.REMARKS Simply returns the local iolist address...
------------------------------------------------------------*/
{
  return(&this_dev);
}



#else


static int iobsf(fd,ntm)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Skip backward space file on a tape.
.RETURNS Tape marks skipped/ -1 (error)
------------------------------------------------------------*/
int fd;                 /* IN:  Tape device file descriptor */
int ntm;                /* IN:  Number of tape marks */
{
return 0;
}

#endif

