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