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