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