Home | History | Annotate | Line # | Download | only in librmt
rmtlib.c revision 1.2
      1 /*	$NetBSD: rmtlib.c,v 1.2 1996/08/09 03:39:00 jtc Exp $	*/
      2 
      3 /*
      4  *	rmt --- remote tape emulator subroutines
      5  *
      6  *	Originally written by Jeff Lee, modified some by Arnold Robbins
      7  *
      8  *	WARNING:  The man page rmt(8) for /etc/rmt documents the remote mag
      9  *	tape protocol which rdump and rrestore use.  Unfortunately, the man
     10  *	page is *WRONG*.  The author of the routines I'm including originally
     11  *	wrote his code just based on the man page, and it didn't work, so he
     12  *	went to the rdump source to figure out why.  The only thing he had to
     13  *	change was to check for the 'F' return code in addition to the 'E',
     14  *	and to separate the various arguments with \n instead of a space.  I
     15  *	personally don't think that this is much of a problem, but I wanted to
     16  *	point it out.
     17  *	-- Arnold Robbins
     18  *
     19  *	Redone as a library that can replace open, read, write, etc, by
     20  *	Fred Fish, with some additional work by Arnold Robbins.
     21  */
     22 
     23 /*
     24  *	MAXUNIT --- Maximum number of remote tape file units
     25  *
     26  *	READ --- Return the number of the read side file descriptor
     27  *	WRITE --- Return the number of the write side file descriptor
     28  */
     29 
     30 #define RMTIOCTL	1
     31 /* #define USE_REXEC	1	/* rexec code courtesy of Dan Kegel, srs!dan */
     32 
     33 #include <stdio.h>
     34 #include <stdlib.h>
     35 #include <string.h>
     36 #include <signal.h>
     37 #include <sys/types.h>
     38 
     39 #ifdef RMTIOCTL
     40 #include <sys/ioctl.h>
     41 #include <sys/mtio.h>
     42 #endif
     43 
     44 #ifdef USE_REXEC
     45 #include <netdb.h>
     46 #endif
     47 
     48 #include <errno.h>
     49 #include <sys/stat.h>
     50 
     51 #include <fcntl.h>
     52 #include <unistd.h>
     53 
     54 #define BUFMAGIC	64	/* a magic number for buffer sizes */
     55 #define MAXUNIT	4
     56 
     57 #define READ(fd)	(Ctp[fd][0])
     58 #define WRITE(fd)	(Ptc[fd][1])
     59 
     60 static int Ctp[MAXUNIT][2] = { -1, -1, -1, -1, -1, -1, -1, -1 };
     61 static int Ptc[MAXUNIT][2] = { -1, -1, -1, -1, -1, -1, -1, -1 };
     62 
     63 
     64 /*
     65  *	rmtabort --- close off a remote tape connection
     66  */
     67 
     68 static void rmtabort(fildes)
     69 int fildes;
     70 {
     71 	close(READ(fildes));
     72 	close(WRITE(fildes));
     73 	READ(fildes) = -1;
     74 	WRITE(fildes) = -1;
     75 }
     76 
     77 
     78 
     79 /*
     80  *	command --- attempt to perform a remote tape command
     81  */
     82 
     83 static int command(fildes, buf)
     84 int fildes;
     85 char *buf;
     86 {
     87 	register int blen;
     88 	void (*pstat)();
     89 
     90 /*
     91  *	save current pipe status and try to make the request
     92  */
     93 
     94 	blen = strlen(buf);
     95 	pstat = signal(SIGPIPE, SIG_IGN);
     96 	if (write(WRITE(fildes), buf, blen) == blen)
     97 	{
     98 		signal(SIGPIPE, pstat);
     99 		return(0);
    100 	}
    101 
    102 /*
    103  *	something went wrong. close down and go home
    104  */
    105 
    106 	signal(SIGPIPE, pstat);
    107 	rmtabort(fildes);
    108 
    109 	errno = EIO;
    110 	return(-1);
    111 }
    112 
    113 
    114 
    115 /*
    116  *	status --- retrieve the status from the pipe
    117  */
    118 
    119 static int status(fildes)
    120 int fildes;
    121 {
    122 	int i;
    123 	char c, *cp;
    124 	char buffer[BUFMAGIC];
    125 
    126 /*
    127  *	read the reply command line
    128  */
    129 
    130 	for (i = 0, cp = buffer; i < BUFMAGIC; i++, cp++)
    131 	{
    132 		if (read(READ(fildes), cp, 1) != 1)
    133 		{
    134 			rmtabort(fildes);
    135 			errno = EIO;
    136 			return(-1);
    137 		}
    138 		if (*cp == '\n')
    139 		{
    140 			*cp = 0;
    141 			break;
    142 		}
    143 	}
    144 
    145 	if (i == BUFMAGIC)
    146 	{
    147 		rmtabort(fildes);
    148 		errno = EIO;
    149 		return(-1);
    150 	}
    151 
    152 /*
    153  *	check the return status
    154  */
    155 
    156 	for (cp = buffer; *cp; cp++)
    157 		if (*cp != ' ')
    158 			break;
    159 
    160 	if (*cp == 'E' || *cp == 'F')
    161 	{
    162 		errno = atoi(cp + 1);
    163 		while (read(READ(fildes), &c, 1) == 1)
    164 			if (c == '\n')
    165 				break;
    166 
    167 		if (*cp == 'F')
    168 			rmtabort(fildes);
    169 
    170 		return(-1);
    171 	}
    172 
    173 /*
    174  *	check for mis-synced pipes
    175  */
    176 
    177 	if (*cp != 'A')
    178 	{
    179 		rmtabort(fildes);
    180 		errno = EIO;
    181 		return(-1);
    182 	}
    183 
    184 	return(atoi(cp + 1));
    185 }
    186 
    187 #ifdef USE_REXEC
    188 
    189 /*
    190  * _rmt_rexec
    191  *
    192  * execute /etc/rmt on a remote system using rexec().
    193  * Return file descriptor of bidirectional socket for stdin and stdout
    194  * If username is NULL, or an empty string, uses current username.
    195  *
    196  * ADR: By default, this code is not used, since it requires that
    197  * the user have a .netrc file in his/her home directory, or that the
    198  * application designer be willing to have rexec prompt for login and
    199  * password info. This may be unacceptable, and .rhosts files for use
    200  * with rsh are much more common on BSD systems.
    201  */
    202 
    203 static int
    204 _rmt_rexec(host, user)
    205 char *host;
    206 char *user;		/* may be NULL */
    207 {
    208 	struct servent *rexecserv;
    209 
    210 	rexecserv = getservbyname("exec", "tcp");
    211 	if (NULL == rexecserv) {
    212 		fprintf (stderr, "? exec/tcp: service not available.");
    213 		exit (-1);
    214 	}
    215 	if ((user != NULL) && *user == '\0')
    216 		user = (char *) NULL;
    217 	return rexec (&host, rexecserv->s_port, user, NULL,
    218 			"/etc/rmt", (int *)NULL);
    219 }
    220 #endif /* USE_REXEC */
    221 
    222 /*
    223  *	_rmt_open --- open a magtape device on system specified, as given user
    224  *
    225  *	file name has the form [user@]system:/dev/????
    226 #ifdef COMPAT
    227  *	file name has the form system[.user]:/dev/????
    228 #endif
    229  */
    230 
    231 #define MAXHOSTLEN	257	/* BSD allows very long host names... */
    232 
    233 static int _rmt_open (path, oflag, mode)
    234 char *path;
    235 int oflag;
    236 int mode;
    237 {
    238 	int i, rc;
    239 	char buffer[BUFMAGIC];
    240 	char system[MAXHOSTLEN];
    241 	char device[BUFMAGIC];
    242 	char login[BUFMAGIC];
    243 	char *sys, *dev, *user;
    244 
    245 	sys = system;
    246 	dev = device;
    247 	user = login;
    248 
    249 /*
    250  *	first, find an open pair of file descriptors
    251  */
    252 
    253 	for (i = 0; i < MAXUNIT; i++)
    254 		if (READ(i) == -1 && WRITE(i) == -1)
    255 			break;
    256 
    257 	if (i == MAXUNIT)
    258 	{
    259 		errno = EMFILE;
    260 		return(-1);
    261 	}
    262 
    263 /*
    264  *	pull apart system and device, and optional user
    265  *	don't munge original string
    266  *	if COMPAT is defined, also handle old (4.2) style person.site notation.
    267  */
    268 
    269 	while (*path != '@'
    270 #ifdef COMPAT
    271 			&& *path != '.'
    272 #endif
    273 			&& *path != ':') {
    274 		*sys++ = *path++;
    275 	}
    276 	*sys = '\0';
    277 	path++;
    278 
    279 	if (*(path - 1) == '@')
    280 	{
    281 		(void) strcpy (user, system);	/* saw user part of user@host */
    282 		sys = system;			/* start over */
    283 		while (*path != ':') {
    284 			*sys++ = *path++;
    285 		}
    286 		*sys = '\0';
    287 		path++;
    288 	}
    289 #ifdef COMPAT
    290 	else if (*(path - 1) == '.')
    291 	{
    292 		while (*path != ':') {
    293 			*user++ = *path++;
    294 		}
    295 		*user = '\0';
    296 		path++;
    297 	}
    298 #endif
    299 	else
    300 		*user = '\0';
    301 
    302 	while (*path) {
    303 		*dev++ = *path++;
    304 	}
    305 	*dev = '\0';
    306 
    307 #ifdef USE_REXEC
    308 /*
    309  *	Execute the remote command using rexec
    310  */
    311 	READ(i) = WRITE(i) = _rmt_rexec(system, login);
    312 	if (READ(i) < 0)
    313 		return -1;
    314 #else
    315 /*
    316  *	setup the pipes for the 'rsh' command and fork
    317  */
    318 
    319 	if (pipe(Ptc[i]) == -1 || pipe(Ctp[i]) == -1)
    320 		return(-1);
    321 
    322 	if ((rc = fork()) == -1)
    323 		return(-1);
    324 
    325 	if (rc == 0)
    326 	{
    327 		close(0);
    328 		dup(Ptc[i][0]);
    329 		close(Ptc[i][0]); close(Ptc[i][1]);
    330 		close(1);
    331 		dup(Ctp[i][1]);
    332 		close(Ctp[i][0]); close(Ctp[i][1]);
    333 		(void) setuid (getuid ());
    334 		(void) setgid (getgid ());
    335 		if (*login)
    336 		{
    337 			execl("/usr/ucb/rsh", "rsh", system, "-l", login,
    338 				"/etc/rmt", (char *) 0);
    339 			execl("/usr/bin/remsh", "remsh", system, "-l", login,
    340 				"/etc/rmt", (char *) 0);
    341 		}
    342 		else
    343 		{
    344 			execl("/usr/ucb/rsh", "rsh", system,
    345 				"/etc/rmt", (char *) 0);
    346 			execl("/usr/bin/remsh", "remsh", system,
    347 				"/etc/rmt", (char *) 0);
    348 		}
    349 
    350 /*
    351  *	bad problems if we get here
    352  */
    353 
    354 		perror("exec");
    355 		exit(1);
    356 	}
    357 
    358 	close(Ptc[i][0]); close(Ctp[i][1]);
    359 #endif
    360 
    361 /*
    362  *	now attempt to open the tape device
    363  */
    364 
    365 	sprintf(buffer, "O%s\n%d\n", device, oflag);
    366 	if (command(i, buffer) == -1 || status(i) == -1)
    367 		return(-1);
    368 
    369 	return(i);
    370 }
    371 
    372 
    373 
    374 /*
    375  *	_rmt_close --- close a remote magtape unit and shut down
    376  */
    377 
    378 static int _rmt_close(fildes)
    379 int fildes;
    380 {
    381 	int rc;
    382 
    383 	if (command(fildes, "C\n") != -1)
    384 	{
    385 		rc = status(fildes);
    386 
    387 		rmtabort(fildes);
    388 		return(rc);
    389 	}
    390 
    391 	return(-1);
    392 }
    393 
    394 
    395 
    396 /*
    397  *	_rmt_read --- read a buffer from a remote tape
    398  */
    399 
    400 static int _rmt_read(fildes, buf, nbyte)
    401 int fildes;
    402 char *buf;
    403 unsigned int nbyte;
    404 {
    405 	int rc, i;
    406 	char buffer[BUFMAGIC];
    407 
    408 	sprintf(buffer, "R%d\n", nbyte);
    409 	if (command(fildes, buffer) == -1 || (rc = status(fildes)) == -1)
    410 		return(-1);
    411 
    412 	for (i = 0; i < rc; i += nbyte, buf += nbyte)
    413 	{
    414 		nbyte = read(READ(fildes), buf, rc);
    415 		if (nbyte <= 0)
    416 		{
    417 			rmtabort(fildes);
    418 			errno = EIO;
    419 			return(-1);
    420 		}
    421 	}
    422 
    423 	return(rc);
    424 }
    425 
    426 
    427 
    428 /*
    429  *	_rmt_write --- write a buffer to the remote tape
    430  */
    431 
    432 static int _rmt_write(fildes, buf, nbyte)
    433 int fildes;
    434 char *buf;
    435 unsigned int nbyte;
    436 {
    437 	int rc;
    438 	char buffer[BUFMAGIC];
    439 	void (*pstat)();
    440 
    441 	sprintf(buffer, "W%d\n", nbyte);
    442 	if (command(fildes, buffer) == -1)
    443 		return(-1);
    444 
    445 	pstat = signal(SIGPIPE, SIG_IGN);
    446 	if (write(WRITE(fildes), buf, nbyte) == nbyte)
    447 	{
    448 		signal (SIGPIPE, pstat);
    449 		return(status(fildes));
    450 	}
    451 
    452 	signal (SIGPIPE, pstat);
    453 	rmtabort(fildes);
    454 	errno = EIO;
    455 	return(-1);
    456 }
    457 
    458 
    459 
    460 /*
    461  *	_rmt_lseek --- perform an imitation lseek operation remotely
    462  */
    463 
    464 static long _rmt_lseek(fildes, offset, whence)
    465 int fildes;
    466 long offset;
    467 int whence;
    468 {
    469 	char buffer[BUFMAGIC];
    470 
    471 	sprintf(buffer, "L%d\n%d\n", offset, whence);
    472 	if (command(fildes, buffer) == -1)
    473 		return(-1);
    474 
    475 	return(status(fildes));
    476 }
    477 
    478 
    479 /*
    480  *	_rmt_ioctl --- perform raw tape operations remotely
    481  */
    482 
    483 #ifdef RMTIOCTL
    484 static _rmt_ioctl(fildes, op, arg)
    485 int fildes, op;
    486 char *arg;
    487 {
    488 	char c;
    489 	int rc, cnt;
    490 	char buffer[BUFMAGIC];
    491 
    492 /*
    493  *	MTIOCOP is the easy one. nothing is transfered in binary
    494  */
    495 
    496 	if (op == MTIOCTOP)
    497 	{
    498 		sprintf(buffer, "I%d\n%d\n", ((struct mtop *) arg)->mt_op,
    499 			((struct mtop *) arg)->mt_count);
    500 		if (command(fildes, buffer) == -1)
    501 			return(-1);
    502 		return(status(fildes));
    503 	}
    504 
    505 /*
    506  *	we can only handle 2 ops, if not the other one, punt
    507  */
    508 
    509 	if (op != MTIOCGET)
    510 	{
    511 		errno = EINVAL;
    512 		return(-1);
    513 	}
    514 
    515 /*
    516  *	grab the status and read it directly into the structure
    517  *	this assumes that the status buffer is (hopefully) not
    518  *	padded and that 2 shorts fit in a long without any word
    519  *	alignment problems, ie - the whole struct is contiguous
    520  *	NOTE - this is probably NOT a good assumption.
    521  */
    522 
    523 	if (command(fildes, "S") == -1 || (rc = status(fildes)) == -1)
    524 		return(-1);
    525 
    526 	for (; rc > 0; rc -= cnt, arg += cnt)
    527 	{
    528 		cnt = read(READ(fildes), arg, rc);
    529 		if (cnt <= 0)
    530 		{
    531 			rmtabort(fildes);
    532 			errno = EIO;
    533 			return(-1);
    534 		}
    535 	}
    536 
    537 /*
    538  *	now we check for byte position. mt_type is a small integer field
    539  *	(normally) so we will check its magnitude. if it is larger than
    540  *	256, we will assume that the bytes are swapped and go through
    541  *	and reverse all the bytes
    542  */
    543 
    544 	if (((struct mtget *) arg)->mt_type < 256)
    545 		return(0);
    546 
    547 	for (cnt = 0; cnt < rc; cnt += 2)
    548 	{
    549 		c = arg[cnt];
    550 		arg[cnt] = arg[cnt+1];
    551 		arg[cnt+1] = c;
    552 	}
    553 
    554 	return(0);
    555   }
    556 #endif /* RMTIOCTL */
    557 
    558 /*
    559  *	Added routines to replace open(), close(), lseek(), ioctl(), etc.
    560  *	The preprocessor can be used to remap these the rmtopen(), etc
    561  *	thus minimizing source changes:
    562  *
    563  *		#ifdef <something>
    564  *		#  define access rmtaccess
    565  *		#  define close rmtclose
    566  *		#  define creat rmtcreat
    567  *		#  define dup rmtdup
    568  *		#  define fcntl rmtfcntl
    569  *		#  define fstat rmtfstat
    570  *		#  define ioctl rmtioctl
    571  *		#  define isatty rmtisatty
    572  *		#  define lseek rmtlseek
    573  *		#  define lstat rmtlstat
    574  *		#  define open rmtopen
    575  *		#  define read rmtread
    576  *		#  define stat rmtstat
    577  *		#  define write rmtwrite
    578  *		#endif
    579  *
    580  *	-- Fred Fish
    581  *
    582  *	ADR --- I set up a <rmt.h> include file for this
    583  *
    584  */
    585 
    586 /*
    587  *	Note that local vs remote file descriptors are distinquished
    588  *	by adding a bias to the remote descriptors.  This is a quick
    589  *	and dirty trick that may not be portable to some systems.
    590  */
    591 
    592 #define REM_BIAS 128
    593 
    594 
    595 /*
    596  *	Test pathname to see if it is local or remote.  A remote device
    597  *	is any string that contains ":/dev/".  Returns 1 if remote,
    598  *	0 otherwise.
    599  */
    600 
    601 static int remdev (path)
    602 register char *path;
    603 {
    604 	if ((path = strchr (path, ':')) != NULL)
    605 	{
    606 		if (strncmp (path + 1, "/dev/", 5) == 0)
    607 		{
    608 			return (1);
    609 		}
    610 	}
    611 	return (0);
    612 }
    613 
    614 
    615 /*
    616  *	Open a local or remote file.  Looks just like open(2) to
    617  *	caller.
    618  */
    619 
    620 int rmtopen (path, oflag, mode)
    621 char *path;
    622 int oflag;
    623 int mode;
    624 {
    625 	int fd;
    626 
    627 	if (remdev (path))
    628 	{
    629 		fd = _rmt_open (path, oflag, mode);
    630 
    631 		return (fd == -1) ? -1 : (fd + REM_BIAS);
    632 	}
    633 	else
    634 	{
    635 		return (open (path, oflag, mode));
    636 	}
    637 }
    638 
    639 /*
    640  *	Test pathname for specified access.  Looks just like access(2)
    641  *	to caller.
    642  */
    643 
    644 int rmtaccess (path, amode)
    645 char *path;
    646 int amode;
    647 {
    648 	if (remdev (path))
    649 	{
    650 		return (0);		/* Let /etc/rmt find out */
    651 	}
    652 	else
    653 	{
    654 		return (access (path, amode));
    655 	}
    656 }
    657 
    658 
    659 /*
    660  *	Read from stream.  Looks just like read(2) to caller.
    661  */
    662 
    663 int rmtread (fildes, buf, nbyte)
    664 int fildes;
    665 char *buf;
    666 unsigned int nbyte;
    667 {
    668 	if (isrmt (fildes))
    669 	{
    670 		return (_rmt_read (fildes - REM_BIAS, buf, nbyte));
    671 	}
    672 	else
    673 	{
    674 		return (read (fildes, buf, nbyte));
    675 	}
    676 }
    677 
    678 
    679 /*
    680  *	Write to stream.  Looks just like write(2) to caller.
    681  */
    682 
    683 int rmtwrite (fildes, buf, nbyte)
    684 int fildes;
    685 char *buf;
    686 unsigned int nbyte;
    687 {
    688 	if (isrmt (fildes))
    689 	{
    690 		return (_rmt_write (fildes - REM_BIAS, buf, nbyte));
    691 	}
    692 	else
    693 	{
    694 		return (write (fildes, buf, nbyte));
    695 	}
    696 }
    697 
    698 /*
    699  *	Perform lseek on file.  Looks just like lseek(2) to caller.
    700  */
    701 
    702 long rmtlseek (fildes, offset, whence)
    703 int fildes;
    704 long offset;
    705 int whence;
    706 {
    707 	if (isrmt (fildes))
    708 	{
    709 		return (_rmt_lseek (fildes - REM_BIAS, offset, whence));
    710 	}
    711 	else
    712 	{
    713 		return (lseek (fildes, offset, whence));
    714 	}
    715 }
    716 
    717 
    718 /*
    719  *	Close a file.  Looks just like close(2) to caller.
    720  */
    721 
    722 int rmtclose (fildes)
    723 int fildes;
    724 {
    725 	if (isrmt (fildes))
    726 	{
    727 		return (_rmt_close (fildes - REM_BIAS));
    728 	}
    729 	else
    730 	{
    731 		return (close (fildes));
    732 	}
    733 }
    734 
    735 /*
    736  *	Do ioctl on file.  Looks just like ioctl(2) to caller.
    737  */
    738 
    739 int rmtioctl (fildes, request, arg)
    740 int fildes;
    741 unsigned long request;
    742 char *arg;
    743 {
    744 	if (isrmt (fildes))
    745 	{
    746 #ifdef RMTIOCTL
    747 		return (_rmt_ioctl (fildes - REM_BIAS, request, arg));
    748 #else
    749 		errno = EOPNOTSUPP;
    750 		return (-1);		/* For now  (fnf) */
    751 #endif
    752 	}
    753 	else
    754 	{
    755 		return (ioctl (fildes, request, arg));
    756 	}
    757 }
    758 
    759 
    760 /*
    761  *	Duplicate an open file descriptor.  Looks just like dup(2)
    762  *	to caller.
    763  */
    764 
    765 int rmtdup (fildes)
    766 int fildes;
    767 {
    768 	if (isrmt (fildes))
    769 	{
    770 		errno = EOPNOTSUPP;
    771 		return (-1);		/* For now (fnf) */
    772 	}
    773 	else
    774 	{
    775 		return (dup (fildes));
    776 	}
    777 }
    778 
    779 /*
    780  *	Get file status.  Looks just like fstat(2) to caller.
    781  */
    782 
    783 int rmtfstat (fildes, buf)
    784 int fildes;
    785 struct stat *buf;
    786 {
    787 	if (isrmt (fildes))
    788 	{
    789 		errno = EOPNOTSUPP;
    790 		return (-1);		/* For now (fnf) */
    791 	}
    792 	else
    793 	{
    794 		return (fstat (fildes, buf));
    795 	}
    796 }
    797 
    798 
    799 /*
    800  *	Get file status.  Looks just like stat(2) to caller.
    801  */
    802 
    803 int rmtstat (path, buf)
    804 char *path;
    805 struct stat *buf;
    806 {
    807 	if (remdev (path))
    808 	{
    809 		errno = EOPNOTSUPP;
    810 		return (-1);		/* For now (fnf) */
    811 	}
    812 	else
    813 	{
    814 		return (stat (path, buf));
    815 	}
    816 }
    817 
    818 
    819 
    820 /*
    821  *	Create a file from scratch.  Looks just like creat(2) to the caller.
    822  */
    823 
    824 int rmtcreat (path, mode)
    825 char *path;
    826 int mode;
    827 {
    828 	if (remdev (path))
    829 	{
    830 		return (rmtopen (path, 1 | O_CREAT, mode));
    831 	}
    832 	else
    833 	{
    834 		return (creat (path, mode));
    835 	}
    836 }
    837 
    838 /*
    839  *	Isrmt. Let a programmer know he has a remote device.
    840  */
    841 
    842 int isrmt (fd)
    843 int fd;
    844 {
    845 	return (fd >= REM_BIAS);
    846 }
    847 
    848 /*
    849  *	Rmtfcntl. Do a remote fcntl operation.
    850  */
    851 
    852 int rmtfcntl (fd, cmd, arg)
    853 int fd, cmd, arg;
    854 {
    855 	if (isrmt (fd))
    856 	{
    857 		errno = EOPNOTSUPP;
    858 		return (-1);
    859 	}
    860 	else
    861 	{
    862 		return (fcntl (fd, cmd, arg));
    863 	}
    864 }
    865 
    866 /*
    867  *	Rmtisatty.  Do the isatty function.
    868  */
    869 
    870 int rmtisatty (fd)
    871 int fd;
    872 {
    873 	if (isrmt (fd))
    874 		return (0);
    875 	else
    876 		return (isatty (fd));
    877 }
    878 
    879 
    880 /*
    881  *	Get file status, even if symlink.  Looks just like lstat(2) to caller.
    882  */
    883 
    884 int rmtlstat (path, buf)
    885 char *path;
    886 struct stat *buf;
    887 {
    888 	if (remdev (path))
    889 	{
    890 		errno = EOPNOTSUPP;
    891 		return (-1);		/* For now (fnf) */
    892 	}
    893 	else
    894 	{
    895 		return (lstat (path, buf));
    896 	}
    897 }
    898