/*
 * This file has been modified for the cdrkit suite.
 *
 * The behaviour and appearence of the program code below can differ to a major
 * extent from the version distributed by the original author(s).
 *
 * For details, see Changelog file distributed with the cdrkit package. If you
 * received this file from another source then ask the distributing person for
 * a log of modifications.
 *
 */

/* @(#)rscsi.c	1.29 05/05/16 Copyright 1994,2000-2002 J. Schilling*/
/*
 *	Remote SCSI server
 *
 *	Copyright (c) 1994,2000-2002 J. Schilling
 */
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * 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; see the file COPYING.  If not, write to the Free Software
 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/*#define	FORCE_DEBUG*/

#include <mconfig.h>

#include <stdio.h>
#include <stdxlib.h>
#include <unixstd.h>	/* includes <sys/types.h> */
#include <utypes.h>
#include <fctldefs.h>
#include <statdefs.h>
#include <strdefs.h>
#ifdef	HAVE_SYS_SOCKET_H
#define	USE_REMOTE
#include <sys/socket.h>
#ifdef __DJGPP__
#define socklen_t int
#endif
#endif
#ifdef	 HAVE_SYS_PARAM_H
#include <sys/param.h>	/* BSD-4.2 & Linux need this for MAXHOSTNAMELEN */
#endif
#include <errno.h>
#include <pwd.h>

#include <standard.h>
#include <deflts.h>
#include <patmatch.h>
#include <schily.h>

#include <usal/usalcmd.h>
#include <usal/scsitransp.h>

#include <netinet/in.h>
#ifdef	HAVE_ARPA_INET_H
#include <arpa/inet.h>		/* BeOS does not have <arpa/inet.h> */
#endif				/* but inet_ntaoa() is in <netdb.h> */
#ifdef	HAVE_NETDB_H
#include <netdb.h>
#endif

#ifdef	USE_REMOTE
static	void	checkuser(void);
static	char	*getpeer(void);
static	BOOL	checktarget(void);
static	void	dorscsi(void);
static	void	scsiversion(void);
static	void	openscsi(void);
static	void	closescsi(void);
static	void	maxdma(void);
static	void	getbuf(void);
static	void	freebuf(void);
static	void	havebus(void);
static	void	scsifileno(void);
static	void	initiator_id(void);
static	void	isatapi(void);
static	void	scsireset(void);
static	void	sendcmd(void);

static	int	fillrdbuf(void);
static	int	readchar(char *cp);

static	void	readbuf(char *buf, int n);
static	void	voidarg(int n);
static	void	readarg(char *buf, int n);
static	char *preparebuffer(int size);
static	int	checkscsi(char *decive);
static	void	rscsirespond(int ret, int err);
static	void	rscsireply(int ret);
static	void	rscsierror(int err, char *str, char *xstr);

#define	CMD_SIZE	80

static	SCSI	*scsi_ptr = NULL;
static	char	*Sbuf;
static	long	Sbufsize;

static	char	*username;
static	char	*peername;

static	char	*debug_name;
static	FILE	*debug_file;

#define	DEBUG(fmt)		if (debug_file) fprintf(debug_file, fmt)
#define	DEBUG1(fmt,a)		if (debug_file) fprintf(debug_file, fmt, a)
#define	DEBUG2(fmt,a1,a2)	if (debug_file) fprintf(debug_file, fmt, a1, a2)
#define	DEBUG3(fmt,a1,a2,a3)	if (debug_file) fprintf(debug_file, fmt, a1, a2, a3)
#define	DEBUG4(fmt,a1,a2,a3,a4)	if (debug_file) fprintf(debug_file, fmt, a1, a2, a3, a4)
#define	DEBUG5(fmt,a1,a2,a3,a4,a5)	if (debug_file) fprintf(debug_file, fmt, a1, a2, a3, a4, a5)
#define	DEBUG6(fmt,a1,a2,a3,a4,a5,a6)	if (debug_file) fprintf(debug_file, fmt, a1, a2, a3, a4, a5, a6)
#endif	/* USE_REMOTE */

int
main(int argc, char *argv[])
{
	save_args(argc, argv);
#ifndef	USE_REMOTE
	comerrno(EX_BAD, "No remote SCSI support on this platform.\n");
#else
	argc--, argv++;
	if (argc > 0 && strcmp(*argv, "-c") == 0) {
		/*
		 * Skip params in case we have been installed as shell.
		 */
		argc--, argv++;
		argc--, argv++;
	}
	/*
	 * WARNING you are only allowed to change the defaults configuration
	 * filename if you also change the documentation and add a statement
	 * that makes clear where the official location of the file is, why you
	 * did choose a nonstandard location and that the nonstandard location
	 * only refers to inofficial rscsi versions.
	 *
	 * I was forced to add this because some people change cdrecord without
	 * rational reason and then publish the result. As those people
	 * don't contribute work and don't give support, they are causing extra
	 * work for me and this way slow down the development.
	 */
	if (cfg_open("/etc/netscsid.conf") < 0) {
		rscsierror(geterrno(), errmsgstr(geterrno()),
			"Remote configuration error: Cannot open /etc/netscsid.conf");
/*		rscsirespond(-1, geterrno());*/
		exit(EX_BAD);
	}
	debug_name=cfg_get("DEBUG");
#ifdef	FORCE_DEBUG
	if (debug_name == NULL && argc <= 0)
		debug_name = "/tmp/RSCSI";
#endif
#ifdef	NONONO
	/*
	 * Should we allow root to shoot himself into the foot?
	 * Allowing to write arbitrary files may be a security risk.
	 */
	if (argc > 0 && getuid() == 0)
		debug_name = *argv;
#endif

	/*
	 * XXX If someone sets up debugging and allows the debug file to be
	 * XXX replaced by a symlink to e.g. /etc/passwd this would be a
	 * XXX security risk. But /etc/rscsi.conf is only writable by root
	 * XXX and for this reason a possible security risk would have been
	 * XXX introduced by the administrator.
	 */
    if (debug_name != NULL) {
        /* Try to be careful when opening debug files, might be
         * created in an unsafe location 
         * */
        int fd = open(debug_name, O_CREAT | O_EXCL | O_TRUNC | O_RDWR, 0600);
        if (fd > -1) 
            debug_file = fdopen(fd, "w");
        else {
            rscsirespond(-1, geterrno());
            exit(EX_BAD);
        }
    } 
		
	if (argc > 0) {
		if (debug_file == 0) {
			rscsirespond(-1, geterrno());
			exit(EX_BAD);
		}
		(void) setbuf(debug_file, (char *)0);
	}
	checkuser();		/* Check if we are called by a bad guy	*/
	peername = getpeer();	/* Get host name of caller		*/
	dorscsi();
#endif	/* USE_REMOTE */
	return (0);
}

#ifdef	USE_REMOTE
static void
checkuser()
{
	uid_t	uid = getuid();
	char	*uname;
	struct passwd *pw;

	if (uid == 0) {
		username = "root";
		DEBUG("rscsid: user id 0, name root\n");
		return;
	}
	pw = getpwuid(uid);
	if (pw == NULL)
		goto notfound;

	username = pw->pw_name;
	DEBUG2("rscsid: user id %ld, name %s\n", (long)uid, username);

	cfg_restart();
	while ((uname = cfg_get_next("USER")) != NULL) {
		if (0==strcmp(username, uname))
			return;
	}
notfound:
	DEBUG2("rscsid: Illegal user '%s' id %ld for RSCSI server\n",
						username, (long)uid);
	rscsierror(0, "Illegal user id for RSCSI server", NULL);
	exit(EX_BAD);
}

#ifndef	NI_MAXHOST
#ifdef	MAXHOSTNAMELEN			/* XXX remove this and sys/param.h */
#define	NI_MAXHOST	MAXHOSTNAMELEN
#else
#define	NI_MAXHOST	64
#endif
#endif

static char *
getpeer()
{
#ifdef	HAVE_GETNAMEINFO
#ifdef	HAVE_SOCKADDR_STORAGE
	struct sockaddr_storage sa;
#else
	char			sa[256];
#endif
#else
	struct	sockaddr sa;
	struct hostent	*he;
#endif
	struct	sockaddr *sap;
	struct	sockaddr_in *s;
	socklen_t	 sasize = sizeof (sa);
static	char		buffer[NI_MAXHOST];

	sap = (struct  sockaddr *)&sa;
	if (getpeername(STDIN_FILENO, sap, &sasize) < 0) {
		int		errsav = geterrno();
		struct stat	sb;

		if (fstat(STDIN_FILENO, &sb) >= 0) {
			if (S_ISFIFO(sb.st_mode)) {
				DEBUG("rmt: stdin is a PIPE\n");
				return ("PIPE");
			}
			DEBUG1("rscsid: stdin st_mode %0llo\n", (Llong)sb.st_mode);
		}

		DEBUG1("rscsid: peername %s\n", errmsgstr(errsav));
		return ("ILLEGAL_SOCKET");
	} else {
		s = (struct sockaddr_in *)&sa;
#ifdef	AF_INET6
		if (s->sin_family != AF_INET && s->sin_family != AF_INET6) {
#else
		if (s->sin_family != AF_INET) {
#endif
#ifdef	AF_UNIX
			/*
			 * AF_UNIX is not defined on BeOS
			 */
			if (s->sin_family == AF_UNIX) {
				DEBUG("rmt: stdin is a PIPE (UNIX domain socket)\n");
				return ("PIPE");
			}
#endif
			DEBUG1("rmt: stdin NOT_IP socket (sin_family: %d)\n",
							s->sin_family);
			return ("NOT_IP");
		}
               
#ifdef	HAVE_GETNAMEINFO
		buffer[0] = '\0';
		if (debug_file &&
		    getnameinfo(sap, sasize, buffer, sizeof (buffer), NULL, 0,
		    NI_NUMERICHOST) == 0) {
			DEBUG1("rmt: peername %s\n", buffer);
		}
		buffer[0] = '\0';
		if (getnameinfo(sap, sasize, buffer, sizeof (buffer), NULL, 0,
		    0) == 0) {
			DEBUG1("rmt: peername %s\n", buffer);
			return (buffer);
		}
		return ("CANNOT_MAP_ADDRESS");
#else	/* HAVE_GETNAMEINFO */
#ifdef	HAVE_INET_NTOA
		(void) snprintf(buffer, sizeof(buffer), "%s", inet_ntoa(s->sin_addr));
#else
		(void) snprintf(buffer, sizeof(buffer), "%x", s->sin_addr.s_addr);
#endif
		DEBUG1("rscsid: peername %s\n", buffer);
		he = gethostbyaddr((char *)&s->sin_addr.s_addr, 4, AF_INET);
		DEBUG1("rscsid: peername %s\n", he!=NULL?he->h_name:buffer);
		if (he != NULL)
			return (he->h_name);
		return (buffer);
#endif	/* HAVE_GETNAMEINFO */
	}
}

static BOOL
checktarget()
{
	char	*target;
	char	*user;
	char	*host;
	char	*p;
	int	bus;
	int	chan;
	int	tgt;
	int	lun;

	if (peername == NULL)
		return (FALSE);
  cfg_restart();
	while ((target = cfg_get_next("ACCESS")) != NULL) {
		p = target;
		while (*p == '\t')
			p++;
		user = p;
		if ((p = strchr(p, '\t')) != NULL)
			*p++ = '\0';
		else
			continue;
		if (0!=strcmp(username, user))
			continue;

		while (*p == '\t')
			p++;
		host = p;
		if ((p = strchr(p, '\t')) != NULL)
			*p++ = '\0';
		else
			continue;
		if (0!=strcmp(peername, host))
			continue;

		p = astoi(p, &bus);
		if (*p != '\t')
			continue;
		p = astoi(p, &chan);
		if (*p != '\t')
			continue;
		p = astoi(p, &tgt);
		if (*p != '\t')
			continue;
		p = astoi(p, &lun);

		if (*p != '\t' && *p != '\n' && *p != '\r' && *p != '\0') 
			continue;
		DEBUG6("ACCESS %s %s %d.%d,%d,%d\n", user, host, bus, chan, tgt, lun);

		if (bus != -1 && bus != usal_scsibus(scsi_ptr))
			continue;
		if (tgt != -1 && tgt != usal_target(scsi_ptr))
			continue;
		if (lun != -1 && lun != usal_lun(scsi_ptr))
			continue;
		return (TRUE);
	}
	return (FALSE);
}

static void
dorscsi()
{
	char	c;

	while (readchar(&c) == 1) {
		seterrno(0);

		switch (c) {

		case 'V':		/* "V" ersion	*/
			scsiversion();
			break;
		case 'O':		/* "O" pen	*/
			openscsi();
			break;
		case 'C':		/* "C" lose	*/
			closescsi();
			break;
		case 'D':		/* "D" MA	*/
			maxdma();
			break;
		case 'M':		/* "M" alloc	*/
			getbuf();
			break;
		case 'F':		/* "F" free	*/
			freebuf();
			break;
		case 'B':		/* "B" us	*/
			havebus();
			break;
		case 'T':		/* "T" arget	*/
			scsifileno();
			break;
		case 'I':		/* "I" nitiator	*/
			initiator_id();
			break;
		case 'A':		/* "A" tapi	*/
			isatapi();
			break;
		case 'R':		/* "R" eset	*/
			scsireset();
			break;
		case 'S':		/* "S" end	*/
			sendcmd();
			break;

		default:
			DEBUG1("rscsid: garbage command '%c'\n", c);
			rscsierror(0, "Garbage command", NULL);
			exit(EX_BAD);
		}
	}
	exit(0);
}

static void
scsiversion()
{
	int	ret;
	char	*str;
	char	what[CMD_SIZE];

	readarg(what, sizeof(what));
	DEBUG1("rscsid: V %s\n", what);
	if (scsi_ptr == NULL) {
		rscsirespond(-1, EBADF);
		return;
	}
	str = usal_version(scsi_ptr, atoi(what));
	ret = strlen(str);
	ret++;	/* Include null char */
	rscsirespond(ret, geterrno());
	_nixwrite(STDOUT_FILENO, str, ret);
}

static void
openscsi()
{
	char	device[CMD_SIZE];
	char	errstr[80];
	int	debug = 0;
	int	lverbose = 0;
	int	ret = 0;
	char	rbuf[1600];

	if (scsi_ptr != NULL)
		(void) usal_close(scsi_ptr);

	readarg(device, sizeof(device));
	DEBUG1("rscsid: O %s\n", device);
	if (strncmp(device, "REMOTE", 6) == 0) {
		scsi_ptr = NULL;
		seterrno(EINVAL);
	} else if (!checkscsi(device)) {
		scsi_ptr = NULL;
		seterrno(EACCES);
	} else {
		scsi_ptr = usal_open(device, errstr, sizeof(errstr), debug, lverbose);
		if (scsi_ptr == NULL) {
			ret = -1;
		} else {
			scsi_ptr->silent = 1;
			scsi_ptr->verbose = 0;
			scsi_ptr->debug = 0;
			scsi_ptr->kdebug = 0;
		}
	}
	if (ret < 0) {
		/*
		 * XXX This is currently the only place where we use the
		 * XXX extended error string.
		 */
		rscsierror(geterrno(), errmsgstr(geterrno()), errstr);
/*		rscsirespond(ret, geterrno());*/
		return;
	}
	DEBUG4("rscsid:>A 0 %d.%d,%d,%d\n", 
		usal_scsibus(scsi_ptr),
		0,
		usal_target(scsi_ptr),
		usal_lun(scsi_ptr));

	ret = snprintf(rbuf, sizeof(rbuf), "A0\n%d\n%d\n%d\n%d\n",
		usal_scsibus(scsi_ptr),
		0,
		usal_target(scsi_ptr),
		usal_lun(scsi_ptr));
	(void) _nixwrite(STDOUT_FILENO, rbuf, ret);
}

static void
closescsi()
{
	int	ret;
	char	device[CMD_SIZE];

	readarg(device, sizeof(device));
	DEBUG1("rscsid: C %s\n", device);
	ret = usal_close(scsi_ptr);
	rscsirespond(ret, geterrno());
	scsi_ptr = NULL;
}

static void
maxdma()
{
	int	ret;
	char	amt[CMD_SIZE];

	readarg(amt, sizeof(amt));
	DEBUG1("rscsid: D %s\n", amt);
	if (scsi_ptr == NULL) {
		rscsirespond(-1, EBADF);
		return;
	}
	ret = usal_bufsize(scsi_ptr, atol(amt));
	rscsirespond(ret, geterrno());
}

static void
getbuf()
{
	int	ret = 0;
	char	amt[CMD_SIZE];

	readarg(amt, sizeof(amt));
	DEBUG1("rscsid: M %s\n", amt);
	if (scsi_ptr == NULL) {
		rscsirespond(-1, EBADF);
		return;
	}
	ret = usal_bufsize(scsi_ptr, atol(amt));
	if (preparebuffer(ret) == NULL)
		ret = -1;
	rscsirespond(ret, geterrno());
}

static void
freebuf()
{
	int	ret = 0;
	char	dummy[CMD_SIZE];

	readarg(dummy, sizeof(dummy));
	DEBUG1("rscsid: F %s\n", dummy);
	if (scsi_ptr == NULL) {
		rscsirespond(-1, EBADF);
		return;
	}
	usal_freebuf(scsi_ptr);
	Sbuf = NULL;
	rscsirespond(ret, geterrno());
}

static void
havebus()
{
	int	ret;
	char	bus[CMD_SIZE];
	char	chan[CMD_SIZE];

	readarg(bus, sizeof(bus));
	readarg(chan, sizeof(chan));
	DEBUG2("rscsid: B %s.%s\n", bus, chan);
	if (scsi_ptr == NULL) {
		rscsirespond(-1, EBADF);
		return;
	}
	ret = usal_havebus(scsi_ptr, atol(bus));
	rscsirespond(ret, geterrno());
}

static void
scsifileno()
{
	int	ret;
	char	bus[CMD_SIZE];
	char	chan[CMD_SIZE];
	char	tgt[CMD_SIZE];
	char	lun[CMD_SIZE];

	readarg(bus, sizeof(bus));
	readarg(chan, sizeof(chan));
	readarg(tgt, sizeof(tgt));
	readarg(lun, sizeof(lun));
	DEBUG4("rscsid: T %s.%s,%s,%s\n", bus, chan, tgt, lun);
	if (scsi_ptr == NULL) {
		rscsirespond(-1, EBADF);
		return;
	}
	seterrno(0);
	ret = usal_settarget(scsi_ptr, atoi(bus), atoi(tgt), atoi(lun));
	if (!checktarget()) {
		usal_settarget(scsi_ptr, -1, -1, -1);
		ret = -1;
	}
	if (geterrno() != 0)
		rscsirespond(ret, geterrno());
	else
		rscsireply(ret);
}

static void
initiator_id()
{
	int	ret;
	char	dummy[CMD_SIZE];

	readarg(dummy, sizeof(dummy));
	DEBUG1("rscsid: I %s\n", dummy);
	if (scsi_ptr == NULL) {
		rscsirespond(-1, EBADF);
		return;
	}
	seterrno(0);
	ret = usal_initiator_id(scsi_ptr);
	if (geterrno() != 0)
		rscsirespond(ret, geterrno());
	else
		rscsireply(ret);
}

static void
isatapi()
{
	int	ret;
	char	dummy[CMD_SIZE];

	readarg(dummy, sizeof(dummy));
	DEBUG1("rscsid: A %s\n", dummy);
	if (scsi_ptr == NULL) {
		rscsirespond(-1, EBADF);
		return;
	}
	seterrno(0);
	ret = usal_isatapi(scsi_ptr);
	if (geterrno() != 0)
		rscsirespond(ret, geterrno());
	else
		rscsireply(ret);
}

static void
scsireset()
{
	int	ret;
	char	what[CMD_SIZE];

	readarg(what, sizeof(what));
	DEBUG1("rscsid: R %s\n", what);
	if (scsi_ptr == NULL) {
		rscsirespond(-1, EBADF);
		return;
	}
	ret = usal_reset(scsi_ptr, atoi(what));
	rscsirespond(ret, geterrno());
}

static void
sendcmd()
{
	register struct	usal_cmd	*scmd;
	int	n;
	int	ret;
	char	count[CMD_SIZE];
	char	flags[CMD_SIZE];
	char	cdb_len[CMD_SIZE];
	char	sense_len[CMD_SIZE];
	char	timeout[CMD_SIZE];
	int	csize;
	int	cflags;
	int	clen;
	int	ctimeout;
	char	rbuf[1600];
	char	*p;

	/*
	 *	S count\n
	 *	flags\n
	 *	cdb_len\n
	 *	sense_len\n
	 *	timeout\n
	 *	<data if available>
	 *
	 *	Timeout:
	 *	-	sss	(e.g. 10)
	 *	-	sss.uuu	(e.g. 10.23)
	 */
	readarg(count, sizeof(count));
	readarg(flags, sizeof(flags));
	readarg(cdb_len, sizeof(cdb_len));
	readarg(sense_len, sizeof(sense_len));
	readarg(timeout, sizeof(timeout));
	DEBUG5("rscsid: S %s %s %s %s %s", count, flags, cdb_len, sense_len, timeout);
	csize = atoi(count);
	cflags = atoi(flags);
	clen = atoi(cdb_len);

	p = strchr(timeout, '.');
	if (p)
		*p = '\0';
	ctimeout = atoi(timeout);

	if (scsi_ptr == NULL || clen > SCG_MAX_CMD || csize > Sbufsize) {
		DEBUG("\n");
		voidarg(clen);
		if ((cflags & SCG_RECV_DATA) == 0 && csize > 0)
			voidarg(csize);
		rscsirespond(-1, scsi_ptr==NULL ? EBADF : EINVAL);
		return;
	}

	scmd = scsi_ptr->scmd;
	fillbytes((caddr_t)scmd, sizeof(*scmd), '\0');
	scmd->addr = (caddr_t)Sbuf;
	scmd->size = csize;
	scmd->flags = cflags;
	scmd->cdb_len = clen;
	scmd->sense_len = atoi(sense_len);
	scmd->timeout = ctimeout;
	readbuf((char *)scmd->cdb.cmd_cdb, clen);
	DEBUG6(" 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X\n",
		scmd->cdb.cmd_cdb[0],
		scmd->cdb.cmd_cdb[1],
		scmd->cdb.cmd_cdb[2],
		scmd->cdb.cmd_cdb[3],
		scmd->cdb.cmd_cdb[4],
		scmd->cdb.cmd_cdb[5]);

	if ((cflags & SCG_RECV_DATA) == 0 && csize > 0)
		readbuf(Sbuf, scmd->size);

	scsi_ptr->cmdname = "";

	ret = usal_cmd(scsi_ptr);

	n = 0;
	if ((csize - scmd->resid) > 0)
		n = csize - scmd->resid;

	/*
	 *	A count\n
	 *	error\n
	 *	errno\n
	 *	scb\n
	 *	sense_count\n
	 *	<data if available>
	 */
	DEBUG5("rscsid:>A %d %d %d %d %d\n",
		n,
		scmd->error,
		scmd->ux_errno,
		*(Uchar *)&scmd->scb,
		scmd->sense_count);

	ret = snprintf(rbuf, sizeof(rbuf), "A%d\n%d\n%d\n%d\n%d\n",
		n,
		scmd->error,
		scmd->ux_errno,
		*(Uchar *)&scmd->scb,
		scmd->sense_count);

	if (scmd->sense_count > 0) {
		movebytes(scmd->u_sense.cmd_sense, &rbuf[ret], scmd->sense_count);
		ret += scmd->sense_count;
	}
	if ((cflags & SCG_RECV_DATA) == 0)
		n = 0;
	if (n > 0 && ((ret + n) <= sizeof(rbuf))) {
		movebytes(Sbuf, &rbuf[ret], n);
		ret += n;
		n = 0;
	}
	(void) _nixwrite(STDOUT_FILENO, rbuf, ret);

	if (n > 0)
		(void) _nixwrite(STDOUT_FILENO, Sbuf, n);
}

#define	READB_SIZE	128
static	char		readb[READB_SIZE];
static	char		*readbptr;
static	int		readbcnt;

static int
fillrdbuf()
{
	readbptr = readb;

	return (readbcnt = _niread(STDIN_FILENO, readb, READB_SIZE));
}

static int
readchar(char *cp)
{
	if (--readbcnt < 0) {
		if (fillrdbuf() <= 0)
			return (readbcnt);
		--readbcnt;
	}
	*cp = *readbptr++;
	return (1);
}

static void
readbuf(register char *buf, register int n)
{
	register int	i = 0;
	register int	amt;

	if (readbcnt > 0) {
		amt = readbcnt;
		if (amt > n)
			amt = n;
		movebytes(readbptr, buf, amt);
		readbptr += amt;
		readbcnt -= amt;
		i += amt;
	}

	for (; i < n; i += amt) {
		amt = _niread(STDIN_FILENO, &buf[i], n - i);
		if (amt <= 0) {
			DEBUG("rscsid: premature eof\n");
			rscsierror(0, "Premature eof", NULL);
			exit(EX_BAD);
		}
	}
}

static void
voidarg(register int n)
{
	register int	i;
	register int	amt;
		 char	buf[512];

	for (i = 0; i < n; i += amt) {
		amt = sizeof(buf);
		if ((n - i) < amt)
			amt = n - i;
		readbuf(buf, amt);
	}
}

static void
readarg(char *buf, int n)
{
	int	i;

	for (i = 0; i < n; i++) {
		if (readchar(&buf[i]) != 1)
			exit(0);
		if (buf[i] == '\n')
			break;
	}
	buf[i] = '\0';
}

static char *
preparebuffer(int size)
{
	Sbufsize = size;
	if ((Sbuf = usal_getbuf(scsi_ptr, Sbufsize)) == NULL) {
		Sbufsize = 0L;
		return (Sbuf);
	}
	size = Sbufsize + 1024;	/* Add protocol overhead */

#ifdef	SO_SNDBUF
	while (size > 512 &&
	       setsockopt(STDOUT_FILENO, SOL_SOCKET, SO_SNDBUF, (char *)&size, sizeof (size)) < 0)
		size -= 512;
	DEBUG1("rscsid: sndsize: %d\n", size);
#endif
#ifdef	SO_RCVBUF
	while (size > 512 &&
	       setsockopt(STDIN_FILENO, SOL_SOCKET, SO_RCVBUF, (char *)&size, sizeof (size)) < 0)
		size -= 512;
	DEBUG1("rscsid: rcvsize: %d\n", size);
#endif
	return (Sbuf);
}

static int
checkscsi(char *device)
{
#ifdef	CHECKTAPE
	if (strncmp(device, "/dev/rst", 8) == 0 ||
	    strncmp(device, "/dev/nrst", 9) == 0 ||
	    strcmp(device, "/dev/zero") == 0 ||
	    strcmp(device, "/dev/null") == 0)
		return (1);
	return (0);
#else
	return (1);
#endif
}

static void
rscsirespond(int ret, int err)
{
	if (ret < 0) {
		rscsierror(err, errmsgstr(err), NULL);
	} else {
		rscsireply(ret);
	}
}

static void
rscsireply(int ret)
{
	char	rbuf[CMD_SIZE];

	DEBUG1("rscsid:>A %d\n", ret);
	(void) snprintf(rbuf, sizeof(rbuf), "A%d\n", ret);
	(void) _nixwrite(STDOUT_FILENO, rbuf, strlen(rbuf));
}

static void
rscsierror(int err, char *str, char *xstr)
{
	char	rbuf[1600];
	int	xlen = 0;
	int	n;

	if (xstr != NULL)
		xlen = strlen(xstr) + 1;

	DEBUG3("rscsid:>E %d (%s) [%s]\n", err, str, xstr?xstr:"");
	n = snprintf(rbuf, sizeof(rbuf), "E%d\n%s\n%d\n", err, str, xlen);

	if (xlen > 0 && ((xlen + n) <= sizeof(rbuf))) {
		movebytes(xstr, &rbuf[n], xlen);
		n += xlen;
		xlen = 0;
	}
	(void) _nixwrite(STDOUT_FILENO, rbuf, n);
	if (xlen > 0)
		(void) _nixwrite(STDOUT_FILENO, xstr, xlen);
}
#endif	/* USE_REMOTE */
