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