Home | History | Annotate | Line # | Download | only in librmt
rmtlib.c revision 1.5
      1 /*	$NetBSD: rmtlib.c,v 1.5 1997/01/23 14:03:05 mrg 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)strncpy(user, system, sizeof(login) - 1);
    282 				/* saw user part of user@host */
    283 		sys = system;			/* start over */
    284 		while (*path != ':') {
    285 			*sys++ = *path++;
    286 		}
    287 		*sys = '\0';
    288 		path++;
    289 	}
    290 #ifdef COMPAT
    291 	else if (*(path - 1) == '.')
    292 	{
    293 		while (*path != ':') {
    294 			*user++ = *path++;
    295 		}
    296 		*user = '\0';
    297 		path++;
    298 	}
    299 #endif
    300 	else
    301 		*user = '\0';
    302 
    303 	while (*path) {
    304 		*dev++ = *path++;
    305 	}
    306 	*dev = '\0';
    307 
    308 #ifdef USE_REXEC
    309 /*
    310  *	Execute the remote command using rexec
    311  */
    312 	READ(i) = WRITE(i) = _rmt_rexec(system, login);
    313 	if (READ(i) < 0)
    314 		return -1;
    315 #else
    316 /*
    317  *	setup the pipes for the 'rsh' command and fork
    318  */
    319 
    320 	if (pipe(Ptc[i]) == -1 || pipe(Ctp[i]) == -1)
    321 		return(-1);
    322 
    323 	if ((rc = fork()) == -1)
    324 		return(-1);
    325 
    326 	if (rc == 0)
    327 	{
    328 		close(0);
    329 		dup(Ptc[i][0]);
    330 		close(Ptc[i][0]); close(Ptc[i][1]);
    331 		close(1);
    332 		dup(Ctp[i][1]);
    333 		close(Ctp[i][0]); close(Ctp[i][1]);
    334 		(void) setuid (getuid ());
    335 		(void) setgid (getgid ());
    336 		if (*login)
    337 		{
    338 			execl("/usr/bin/rsh", "rsh", system, "-l", login,
    339 				"/etc/rmt", (char *) 0);
    340 		}
    341 		else
    342 		{
    343 			execl("/usr/bin/rsh", "rsh", system,
    344 				"/etc/rmt", (char *) 0);
    345 		}
    346 
    347 /*
    348  *	bad problems if we get here
    349  */
    350 
    351 		perror("exec");
    352 		exit(1);
    353 	}
    354 
    355 	close(Ptc[i][0]); close(Ctp[i][1]);
    356 #endif
    357 
    358 /*
    359  *	now attempt to open the tape device
    360  */
    361 
    362 	(void)snprintf(buffer, sizeof(buffer), "O%s\n%d\n", device, oflag);
    363 	if (command(i, buffer) == -1 || status(i) == -1)
    364 		return(-1);
    365 
    366 	return(i);
    367 }
    368 
    369 
    370 
    371 /*
    372  *	_rmt_close --- close a remote magtape unit and shut down
    373  */
    374 
    375 static int _rmt_close(fildes)
    376 int fildes;
    377 {
    378 	int rc;
    379 
    380 	if (command(fildes, "C\n") != -1)
    381 	{
    382 		rc = status(fildes);
    383 
    384 		rmtabort(fildes);
    385 		return(rc);
    386 	}
    387 
    388 	return(-1);
    389 }
    390 
    391 
    392 
    393 /*
    394  *	_rmt_read --- read a buffer from a remote tape
    395  */
    396 
    397 static int _rmt_read(fildes, buf, nbyte)
    398 int fildes;
    399 char *buf;
    400 unsigned int nbyte;
    401 {
    402 	int rc, i;
    403 	char buffer[BUFMAGIC];
    404 
    405 	(void)snprintf(buffer, sizeof buffer, "R%d\n", nbyte);
    406 	if (command(fildes, buffer) == -1 || (rc = status(fildes)) == -1)
    407 		return(-1);
    408 
    409 	for (i = 0; i < rc; i += nbyte, buf += nbyte)
    410 	{
    411 		nbyte = read(READ(fildes), buf, rc);
    412 		if (nbyte <= 0)
    413 		{
    414 			rmtabort(fildes);
    415 			errno = EIO;
    416 			return(-1);
    417 		}
    418 	}
    419 
    420 	return(rc);
    421 }
    422 
    423 
    424 
    425 /*
    426  *	_rmt_write --- write a buffer to the remote tape
    427  */
    428 
    429 static int _rmt_write(fildes, buf, nbyte)
    430 int fildes;
    431 char *buf;
    432 unsigned int nbyte;
    433 {
    434 	int rc;
    435 	char buffer[BUFMAGIC];
    436 	void (*pstat)();
    437 
    438 	(void)snprintf(buffer, sizeof buffer, "W%d\n", nbyte);
    439 	if (command(fildes, buffer) == -1)
    440 		return(-1);
    441 
    442 	pstat = signal(SIGPIPE, SIG_IGN);
    443 	if (write(WRITE(fildes), buf, nbyte) == nbyte)
    444 	{
    445 		signal (SIGPIPE, pstat);
    446 		return(status(fildes));
    447 	}
    448 
    449 	signal (SIGPIPE, pstat);
    450 	rmtabort(fildes);
    451 	errno = EIO;
    452 	return(-1);
    453 }
    454 
    455 
    456 
    457 /*
    458  *	_rmt_lseek --- perform an imitation lseek operation remotely
    459  */
    460 
    461 static long _rmt_lseek(fildes, offset, whence)
    462 int fildes;
    463 long offset;
    464 int whence;
    465 {
    466 	char buffer[BUFMAGIC];
    467 
    468 	(void)snprintf(buffer, sizeof buffer, "L%d\n%d\n", offset, whence);
    469 	if (command(fildes, buffer) == -1)
    470 		return(-1);
    471 
    472 	return(status(fildes));
    473 }
    474 
    475 
    476 /*
    477  *	_rmt_ioctl --- perform raw tape operations remotely
    478  */
    479 
    480 #ifdef RMTIOCTL
    481 static _rmt_ioctl(fildes, op, arg)
    482 int fildes;
    483 unsigned long op;
    484 char *arg;
    485 {
    486 	char c;
    487 	int rc, cnt;
    488 	char buffer[BUFMAGIC];
    489 
    490 /*
    491  *	MTIOCOP is the easy one. nothing is transfered in binary
    492  */
    493 
    494 	if (op == MTIOCTOP)
    495 	{
    496 		(void)snprintf(buffer, sizeof buffer, "I%d\n%d\n",
    497 		    ((struct mtop *)arg)->mt_op,
    498 		    ((struct mtop *)arg)->mt_count);
    499 		if (command(fildes, buffer) == -1)
    500 			return(-1);
    501 		return(status(fildes));
    502 	}
    503 
    504 /*
    505  *	we can only handle 2 ops, if not the other one, punt
    506  */
    507 
    508 	if (op != MTIOCGET)
    509 	{
    510 		errno = EINVAL;
    511 		return(-1);
    512 	}
    513 
    514 /*
    515  *	grab the status and read it directly into the structure
    516  *	this assumes that the status buffer is (hopefully) not
    517  *	padded and that 2 shorts fit in a long without any word
    518  *	alignment problems, ie - the whole struct is contiguous
    519  *	NOTE - this is probably NOT a good assumption.
    520  */
    521 
    522 	if (command(fildes, "S") == -1 || (rc = status(fildes)) == -1)
    523 		return(-1);
    524 
    525 	for (; rc > 0; rc -= cnt, arg += cnt)
    526 	{
    527 		cnt = read(READ(fildes), arg, rc);
    528 		if (cnt <= 0)
    529 		{
    530 			rmtabort(fildes);
    531 			errno = EIO;
    532 			return(-1);
    533 		}
    534 	}
    535 
    536 /*
    537  *	now we check for byte position. mt_type is a small integer field
    538  *	(normally) so we will check its magnitude. if it is larger than
    539  *	256, we will assume that the bytes are swapped and go through
    540  *	and reverse all the bytes
    541  */
    542 
    543 	if (((struct mtget *) arg)->mt_type < 256)
    544 		return(0);
    545 
    546 	for (cnt = 0; cnt < rc; cnt += 2)
    547 	{
    548 		c = arg[cnt];
    549 		arg[cnt] = arg[cnt+1];
    550 		arg[cnt+1] = c;
    551 	}
    552 
    553 	return(0);
    554   }
    555 #endif /* RMTIOCTL */
    556 
    557 /*
    558  *	Added routines to replace open(), close(), lseek(), ioctl(), etc.
    559  *	The preprocessor can be used to remap these the rmtopen(), etc
    560  *	thus minimizing source changes:
    561  *
    562  *		#ifdef <something>
    563  *		#  define access rmtaccess
    564  *		#  define close rmtclose
    565  *		#  define creat rmtcreat
    566  *		#  define dup rmtdup
    567  *		#  define fcntl rmtfcntl
    568  *		#  define fstat rmtfstat
    569  *		#  define ioctl rmtioctl
    570  *		#  define isatty rmtisatty
    571  *		#  define lseek rmtlseek
    572  *		#  define lstat rmtlstat
    573  *		#  define open rmtopen
    574  *		#  define read rmtread
    575  *		#  define stat rmtstat
    576  *		#  define write rmtwrite
    577  *		#endif
    578  *
    579  *	-- Fred Fish
    580  *
    581  *	ADR --- I set up a <rmt.h> include file for this
    582  *
    583  */
    584 
    585 /*
    586  *	Note that local vs remote file descriptors are distinquished
    587  *	by adding a bias to the remote descriptors.  This is a quick
    588  *	and dirty trick that may not be portable to some systems.
    589  */
    590 
    591 #define REM_BIAS 128
    592 
    593 
    594 /*
    595  *	Test pathname to see if it is local or remote.  A remote device
    596  *	is any string that contains ":/dev/".  Returns 1 if remote,
    597  *	0 otherwise.
    598  */
    599 
    600 static int remdev (path)
    601 register char *path;
    602 {
    603 	if ((path = strchr (path, ':')) != NULL)
    604 	{
    605 		if (strncmp (path + 1, "/dev/", 5) == 0)
    606 		{
    607 			return (1);
    608 		}
    609 	}
    610 	return (0);
    611 }
    612 
    613 
    614 /*
    615  *	Open a local or remote file.  Looks just like open(2) to
    616  *	caller.
    617  */
    618 
    619 int rmtopen (path, oflag, mode)
    620 char *path;
    621 int oflag;
    622 int mode;
    623 {
    624 	int fd;
    625 
    626 	if (remdev (path))
    627 	{
    628 		fd = _rmt_open (path, oflag, mode);
    629 
    630 		return (fd == -1) ? -1 : (fd + REM_BIAS);
    631 	}
    632 	else
    633 	{
    634 		return (open (path, oflag, mode));
    635 	}
    636 }
    637 
    638 /*
    639  *	Test pathname for specified access.  Looks just like access(2)
    640  *	to caller.
    641  */
    642 
    643 int rmtaccess (path, amode)
    644 char *path;
    645 int amode;
    646 {
    647 	if (remdev (path))
    648 	{
    649 		return (0);		/* Let /etc/rmt find out */
    650 	}
    651 	else
    652 	{
    653 		return (access (path, amode));
    654 	}
    655 }
    656 
    657 
    658 /*
    659  *	Read from stream.  Looks just like read(2) to caller.
    660  */
    661 
    662 int rmtread (fildes, buf, nbyte)
    663 int fildes;
    664 char *buf;
    665 unsigned int nbyte;
    666 {
    667 	if (isrmt (fildes))
    668 	{
    669 		return (_rmt_read (fildes - REM_BIAS, buf, nbyte));
    670 	}
    671 	else
    672 	{
    673 		return (read (fildes, buf, nbyte));
    674 	}
    675 }
    676 
    677 
    678 /*
    679  *	Write to stream.  Looks just like write(2) to caller.
    680  */
    681 
    682 int rmtwrite (fildes, buf, nbyte)
    683 int fildes;
    684 char *buf;
    685 unsigned int nbyte;
    686 {
    687 	if (isrmt (fildes))
    688 	{
    689 		return (_rmt_write (fildes - REM_BIAS, buf, nbyte));
    690 	}
    691 	else
    692 	{
    693 		return (write (fildes, buf, nbyte));
    694 	}
    695 }
    696 
    697 /*
    698  *	Perform lseek on file.  Looks just like lseek(2) to caller.
    699  */
    700 
    701 long rmtlseek (fildes, offset, whence)
    702 int fildes;
    703 long offset;
    704 int whence;
    705 {
    706 	if (isrmt (fildes))
    707 	{
    708 		return (_rmt_lseek (fildes - REM_BIAS, offset, whence));
    709 	}
    710 	else
    711 	{
    712 		return (lseek (fildes, offset, whence));
    713 	}
    714 }
    715 
    716 
    717 /*
    718  *	Close a file.  Looks just like close(2) to caller.
    719  */
    720 
    721 int rmtclose (fildes)
    722 int fildes;
    723 {
    724 	if (isrmt (fildes))
    725 	{
    726 		return (_rmt_close (fildes - REM_BIAS));
    727 	}
    728 	else
    729 	{
    730 		return (close (fildes));
    731 	}
    732 }
    733 
    734 /*
    735  *	Do ioctl on file.  Looks just like ioctl(2) to caller.
    736  */
    737 
    738 int rmtioctl (fildes, request, arg)
    739 int fildes;
    740 unsigned long request;
    741 char *arg;
    742 {
    743 	if (isrmt (fildes))
    744 	{
    745 #ifdef RMTIOCTL
    746 		return (_rmt_ioctl (fildes - REM_BIAS, request, arg));
    747 #else
    748 		errno = EOPNOTSUPP;
    749 		return (-1);		/* For now  (fnf) */
    750 #endif
    751 	}
    752 	else
    753 	{
    754 		return (ioctl (fildes, request, arg));
    755 	}
    756 }
    757 
    758 
    759 /*
    760  *	Duplicate an open file descriptor.  Looks just like dup(2)
    761  *	to caller.
    762  */
    763 
    764 int rmtdup (fildes)
    765 int fildes;
    766 {
    767 	if (isrmt (fildes))
    768 	{
    769 		errno = EOPNOTSUPP;
    770 		return (-1);		/* For now (fnf) */
    771 	}
    772 	else
    773 	{
    774 		return (dup (fildes));
    775 	}
    776 }
    777 
    778 /*
    779  *	Get file status.  Looks just like fstat(2) to caller.
    780  */
    781 
    782 int rmtfstat (fildes, buf)
    783 int fildes;
    784 struct stat *buf;
    785 {
    786 	if (isrmt (fildes))
    787 	{
    788 		errno = EOPNOTSUPP;
    789 		return (-1);		/* For now (fnf) */
    790 	}
    791 	else
    792 	{
    793 		return (fstat (fildes, buf));
    794 	}
    795 }
    796 
    797 
    798 /*
    799  *	Get file status.  Looks just like stat(2) to caller.
    800  */
    801 
    802 int rmtstat (path, buf)
    803 char *path;
    804 struct stat *buf;
    805 {
    806 	if (remdev (path))
    807 	{
    808 		errno = EOPNOTSUPP;
    809 		return (-1);		/* For now (fnf) */
    810 	}
    811 	else
    812 	{
    813 		return (stat (path, buf));
    814 	}
    815 }
    816 
    817 
    818 
    819 /*
    820  *	Create a file from scratch.  Looks just like creat(2) to the caller.
    821  */
    822 
    823 int rmtcreat (path, mode)
    824 char *path;
    825 int mode;
    826 {
    827 	if (remdev (path))
    828 	{
    829 		return (rmtopen (path, 1 | O_CREAT, mode));
    830 	}
    831 	else
    832 	{
    833 		return (creat (path, mode));
    834 	}
    835 }
    836 
    837 /*
    838  *	Isrmt. Let a programmer know he has a remote device.
    839  */
    840 
    841 int isrmt (fd)
    842 int fd;
    843 {
    844 	return (fd >= REM_BIAS);
    845 }
    846 
    847 /*
    848  *	Rmtfcntl. Do a remote fcntl operation.
    849  */
    850 
    851 int rmtfcntl (fd, cmd, arg)
    852 int fd, cmd, arg;
    853 {
    854 	if (isrmt (fd))
    855 	{
    856 		errno = EOPNOTSUPP;
    857 		return (-1);
    858 	}
    859 	else
    860 	{
    861 		return (fcntl (fd, cmd, arg));
    862 	}
    863 }
    864 
    865 /*
    866  *	Rmtisatty.  Do the isatty function.
    867  */
    868 
    869 int rmtisatty (fd)
    870 int fd;
    871 {
    872 	if (isrmt (fd))
    873 		return (0);
    874 	else
    875 		return (isatty (fd));
    876 }
    877 
    878 
    879 /*
    880  *	Get file status, even if symlink.  Looks just like lstat(2) to caller.
    881  */
    882 
    883 int rmtlstat (path, buf)
    884 char *path;
    885 struct stat *buf;
    886 {
    887 	if (remdev (path))
    888 	{
    889 		errno = EOPNOTSUPP;
    890 		return (-1);		/* For now (fnf) */
    891 	}
    892 	else
    893 	{
    894 		return (lstat (path, buf));
    895 	}
    896 }
    897