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