xref: /netbsd-src/sys/lib/libsa/nfs.c (revision 3f351f34c6d827cf017cdcff3543f6ec0c88b420)
1 /*	$NetBSD: nfs.c,v 1.52 2023/12/14 05:39:00 rin Exp $	*/
2 
3 /*-
4  *  Copyright (c) 1993 John Brezak
5  *  All rights reserved.
6  *
7  *  Redistribution and use in source and binary forms, with or without
8  *  modification, are permitted provided that the following conditions
9  *  are met:
10  *  1. Redistributions of source code must retain the above copyright
11  *     notice, this list of conditions and the following disclaimer.
12  *  2. Redistributions in binary form must reproduce the above copyright
13  *     notice, this list of conditions and the following disclaimer in the
14  *     documentation and/or other materials provided with the distribution.
15  *  3. The name of the author may not be used to endorse or promote products
16  *     derived from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
22  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 /*
32  * XXX Does not currently implement:
33  * XXX
34  * XXX LIBSA_NO_FS_CLOSE
35  * XXX LIBSA_NO_FS_SEEK
36  * XXX LIBSA_NO_FS_WRITE
37  * XXX LIBSA_NO_FS_SYMLINK (does this even make sense?)
38  * XXX LIBSA_FS_SINGLECOMPONENT (does this even make sense?)
39  */
40 
41 #include <sys/param.h>
42 #include <sys/time.h>
43 #include <sys/socket.h>
44 #include <sys/stat.h>
45 #ifdef _STANDALONE
46 #include <lib/libkern/libkern.h>
47 #else
48 #include <string.h>
49 #endif
50 
51 #include <netinet/in.h>
52 #include <netinet/in_systm.h>
53 
54 #include "rpcv2.h"
55 #include "nfsv2.h"
56 #include "nfsv3.h"
57 
58 #include "stand.h"
59 #include "net.h"
60 #include "nfs.h"
61 #include "rpc.h"
62 
63 /* Storage for any filehandle (including length for V3) */
64 #define NFS_FHSTORE (NFS_FHSIZE < NFS_V3FHSIZE ? NFS_V3FHSIZE + 4: NFS_FHSIZE)
65 
66 /* Data part of nfs rpc reply (also the largest thing we receive) */
67 #define NFSREAD_SIZE 1024
68 
69 #ifndef NFS_NOSYMLINK
70 struct nfs_readlnk_repl {
71 	n_long	errno;
72 	n_long	len;
73 	char	path[NFS_MAXPATHLEN];
74 };
75 #endif
76 
77 static inline uint64_t
78 getnquad(n_long x[2]) {
79 	return (uint64_t)ntohl(x[0]) << 32 | ntohl(x[1]);
80 }
81 
82 static inline void
83 setnquad(n_long x[2], uint64_t v)
84 {
85 	x[0] = htonl((n_long)(v >> 32));
86 	x[1] = htonl((n_long)(v & 0xffffffff));
87 }
88 
89 struct nfs_iodesc {
90 	struct	iodesc	*iodesc;
91 	off_t	off;
92 	int	version;
93 	u_char	fh[NFS_FHSTORE];
94 	union {
95 		/* all in network order */
96 		struct nfsv2_fattr v2;
97 		struct nfsv3_fattr v3;
98 	} u_fa;
99 };
100 
101 static inline size_t
102 fhstore(int version, u_char *fh)
103 {
104 	size_t len;
105 
106 	switch (version) {
107 	case NFS_VER2:
108 		len = NFS_FHSIZE;
109 		break;
110 	case NFS_VER3:
111 		len = fh[0] << 24 | fh[1] << 16 | fh[2] << 8 | fh[3];
112 		if (len > NFS_V3FHSIZE)
113 			len = NFS_V3FHSIZE;
114 		len = 4 + roundup(len, 4);
115 		break;
116 	default:
117 		len = 0;
118 		break;
119 	}
120 
121 	return len;
122 }
123 
124 static inline size_t
125 fhcopy(int version, u_char *src, u_char *dst)
126 {
127 	size_t len = fhstore(version, src);
128 	memcpy(dst, src, len);
129 	return len;
130 }
131 
132 #define setfh(d, s) fhcopy((d)->version, (s), (d)->fh)
133 #define getfh(d, s) fhcopy((d)->version, (d)->fh, (s))
134 
135 
136 struct nfs_iodesc nfs_root_node;
137 
138 int	nfs_getrootfh(struct iodesc *, char *, u_char *, int *);
139 int	nfs_lookupfh(struct nfs_iodesc *, const char *, int,
140 	    struct nfs_iodesc *);
141 int	nfs_readlink(struct nfs_iodesc *, char *);
142 ssize_t	nfs_readdata(struct nfs_iodesc *, off_t, void *, size_t);
143 
144 /*
145  * Fetch the root file handle (call mount daemon)
146  * On error, return non-zero and set errno.
147  */
148 int
149 nfs_getrootfh(struct iodesc *d, char *path, u_char *fhp, int *versionp)
150 {
151 	int len;
152 	struct args {
153 		n_long	len;
154 		char	path[FNAME_SIZE];
155 	} *args;
156 	struct repl {
157 		n_long	errno;
158 		u_char	fh[NFS_FHSTORE];
159 	} *repl;
160 	struct {
161 		n_long	h[RPC_HEADER_WORDS];
162 		struct args d;
163 	} sdata;
164 	struct {
165 		n_long	h[RPC_HEADER_WORDS];
166 		struct repl d;
167 	} rdata;
168 	ssize_t cc;
169 
170 #ifdef NFS_DEBUG
171 	if (debug)
172 		printf("%s: %s\n", __func__, path);
173 #endif
174 
175 	args = &sdata.d;
176 	repl = &rdata.d;
177 
178 	(void)memset(args, 0, sizeof(*args));
179 	len = strlen(path);
180 	if ((size_t)len > sizeof(args->path))
181 		len = sizeof(args->path);
182 	args->len = htonl(len);
183 	(void)memcpy(args->path, path, len);
184 	len = 4 + roundup(len, 4);
185 
186 	*versionp = NFS_VER3;
187 	cc = rpc_call(d, RPCPROG_MNT, RPCMNT_VER3, RPCMNT_MOUNT,
188 	    args, len, repl, sizeof(*repl));
189 	if (cc == -1 || cc < 4 || repl->errno) {
190 		*versionp = NFS_VER2;
191 		cc = rpc_call(d, RPCPROG_MNT, RPCMNT_VER1, RPCMNT_MOUNT,
192 		    args, len, repl, sizeof(*repl));
193 	}
194 	if (cc == -1) {
195 		/* errno was set by rpc_call */
196 		return -1;
197 	}
198 	if (cc < 4) {
199 		errno = EBADRPC;
200 		return -1;
201 	}
202 	if (repl->errno) {
203 		errno = ntohl(repl->errno);
204 		return -1;
205 	}
206 	fhcopy(*versionp, repl->fh, fhp);
207 	return 0;
208 }
209 
210 /*
211  * Lookup a file.  Store handle and attributes.
212  * Return zero or error number.
213  */
214 int
215 nfs_lookupfh(struct nfs_iodesc *d, const char *name, int len,
216 	struct nfs_iodesc *newfd)
217 {
218 	struct argsv2 {
219 		u_char	fh[NFS_FHSIZE];
220 		n_long	len;
221 		char	name[FNAME_SIZE];
222 	} *argsv2;
223 	struct argsv3 {
224 		u_char	fh[NFS_FHSTORE];
225 		n_long	len;
226 		char	name[FNAME_SIZE];
227 	} *argsv3;
228 	struct replv2 {
229 		n_long	errno;
230 		u_char	fh[NFS_FHSIZE];
231 		struct	nfsv2_fattr fa;
232 	} *replv2;
233 	struct replv3 {
234 		n_long	errno;
235 		u_char	fh[NFS_FHSTORE];
236 		n_long	fattrflag;
237 		struct	nfsv3_fattr fa;
238 		n_long	dattrflag;
239 		struct	nfsv3_fattr da;
240 	} *replv3;
241 	struct {
242 		n_long	h[RPC_HEADER_WORDS];
243 		union {
244 			struct argsv2 v2;
245 			struct argsv3 v3;
246 		} u_d;
247 	} sdata;
248 	struct {
249 		n_long	h[RPC_HEADER_WORDS];
250 		union {
251 			struct replv2 v2;
252 			struct replv3 v3;
253 		} u_d;
254 	} rdata;
255 	ssize_t cc;
256 	size_t alen;
257 
258 #ifdef NFS_DEBUG
259 	if (debug)
260 		printf("%s: called\n", __func__);
261 #endif
262 
263 	argsv2 = &sdata.u_d.v2;
264 	argsv3 = &sdata.u_d.v3;
265 	replv2 = &rdata.u_d.v2;
266 	replv3 = &rdata.u_d.v3;
267 
268 	switch (d->version) {
269 	case NFS_VER2:
270 		(void)memset(argsv2, 0, sizeof(*argsv2));
271 		getfh(d, argsv2->fh);
272 		if ((size_t)len > sizeof(argsv2->name))
273 			len = sizeof(argsv2->name);
274 		(void)memcpy(argsv2->name, name, len);
275 		argsv2->len = htonl(len);
276 
277 		/* padded name, name length */
278 		len = roundup(len, 4) + 4;
279 		/* filehandle size */
280 		alen = fhstore(d->version, argsv2->fh);
281 
282 		cc = rpc_call(d->iodesc, NFS_PROG, NFS_VER2, NFSPROC_LOOKUP,
283 		    argsv2, alen+len, replv2, sizeof(*replv2));
284 		break;
285 	case NFS_VER3:
286 		(void)memset(argsv3, 0, sizeof(*argsv3));
287 		getfh(d, argsv3->fh);
288 		if ((size_t)len > sizeof(argsv3->name))
289 			len = sizeof(argsv3->name);
290 		(void)memcpy(argsv3->name, name, len);
291 		argsv3->len = htonl(len);
292 
293 		/* padded name, name length */
294 		len = roundup(len, 4) + 4;
295 		/* filehandle size */
296 		alen = fhstore(d->version, argsv3->fh);
297 
298 		/* adjust for variable sized file handle */
299 		memmove(argsv3->fh + alen, &argsv3->len, len);
300 
301 		cc = rpc_call(d->iodesc, NFS_PROG, NFS_VER3, NFSV3PROC_LOOKUP,
302 		    argsv3, alen+len, replv3, sizeof(*replv3));
303 		break;
304 	default:
305 		return ENOSYS;
306 	}
307 
308 	if (cc == -1)
309 		return errno;		/* XXX - from rpc_call */
310 	if (cc < 4)
311 		return EIO;
312 
313 	switch (d->version) {
314 	case NFS_VER2:
315 		if (replv2->errno) {
316 			/* saerrno.h now matches NFS error numbers. */
317 			return ntohl(replv2->errno);
318 		}
319 
320 		setfh(newfd, replv2->fh);
321 		(void)memcpy(&newfd->u_fa.v2, &replv2->fa,
322 		    sizeof(newfd->u_fa.v2));
323 		break;
324 	case NFS_VER3:
325 		if (replv3->errno) {
326 			/* saerrno.h now matches NFS error numbers. */
327 			return ntohl(replv3->errno);
328 		}
329 
330 		setfh(newfd, replv3->fh);
331 
332 		if (replv3->fattrflag) {
333 			(void)memcpy(&newfd->u_fa.v3, &replv3->fa,
334 			    sizeof(newfd->u_fa.v3));
335 		}
336 		break;
337 	}
338 	return 0;
339 }
340 
341 #ifndef NFS_NOSYMLINK
342 /*
343  * Get the destination of a symbolic link.
344  */
345 int
346 nfs_readlink(struct nfs_iodesc *d, char *buf)
347 {
348 	struct {
349 		n_long	h[RPC_HEADER_WORDS];
350 		u_char	fh[NFS_FHSTORE];
351 	} sdata;
352 	struct {
353 		n_long	h[RPC_HEADER_WORDS];
354 		struct nfs_readlnk_repl d;
355 	} rdata;
356 	ssize_t cc;
357 
358 #ifdef NFS_DEBUG
359 	if (debug)
360 		printf("%s: called\n", __func__);
361 #endif
362 
363 	getfh(d, sdata.fh);
364 	cc = rpc_call(d->iodesc, NFS_PROG, d->version, NFSPROC_READLINK,
365 		      sdata.fh, fhstore(d->version, sdata.fh),
366 		      &rdata.d, sizeof(rdata.d));
367 	if (cc == -1)
368 		return errno;
369 
370 	if (cc < 4)
371 		return EIO;
372 
373 	if (rdata.d.errno)
374 		return ntohl(rdata.d.errno);
375 
376 	rdata.d.len = ntohl(rdata.d.len);
377 	if (rdata.d.len > NFS_MAXPATHLEN)
378 		return ENAMETOOLONG;
379 
380 	(void)memcpy(buf, rdata.d.path, rdata.d.len);
381 	buf[rdata.d.len] = 0;
382 	return 0;
383 }
384 #endif
385 
386 /*
387  * Read data from a file.
388  * Return transfer count or -1 (and set errno)
389  */
390 ssize_t
391 nfs_readdata(struct nfs_iodesc *d, off_t off, void *addr, size_t len)
392 {
393 	struct argsv2 {
394 		u_char	fh[NFS_FHSIZE];
395 		n_long	off;
396 		n_long	len;
397 		n_long	xxx;			/* XXX what's this for? */
398 	} *argsv2;
399 	struct argsv3 {
400 		u_char	fh[NFS_FHSTORE];
401 		n_long	off[2];
402 		n_long	len;
403 	} *argsv3;
404 	struct replv2 {
405 		n_long	errno;
406 		struct	nfsv2_fattr fa;
407 		n_long	count;
408 		u_char	data[NFSREAD_SIZE];
409 	} *replv2;
410 	struct replv3 {
411 		n_long	errno;
412 		n_long	attrflag;
413 		struct	nfsv3_fattr fa;
414 		n_long	count;
415 		n_long	eof;
416 		n_long	length;
417 		u_char	data[NFSREAD_SIZE];
418 	} *replv3;
419 	struct replv3_noattr {
420 		n_long	errno;
421 		n_long	attrflag;
422 		n_long	count;
423 		n_long	eof;
424 		n_long	length;
425 		u_char	data[NFSREAD_SIZE];
426 	} *replv3no;
427 	struct {
428 		n_long	h[RPC_HEADER_WORDS];
429 		union {
430 			struct argsv2 v2;
431 			struct argsv3 v3;
432 		} u_d;
433 	} sdata;
434 	struct {
435 		n_long	h[RPC_HEADER_WORDS];
436 		union {
437 			struct replv2 v2;
438 			struct replv3 v3;
439 		} u_d;
440 	} rdata;
441 	ssize_t cc;
442 	long x;
443 	size_t hlen, rlen, alen;
444 	u_char *data;
445 
446 	argsv2 = &sdata.u_d.v2;
447 	argsv3 = &sdata.u_d.v3;
448 	replv2 = &rdata.u_d.v2;
449 	replv3 = &rdata.u_d.v3;
450 
451 	if (len > NFSREAD_SIZE)
452 		len = NFSREAD_SIZE;
453 
454 	switch (d->version) {
455 	case NFS_VER2:
456 		getfh(d, argsv2->fh);
457 		argsv2->off = htonl((n_long)off);
458 		argsv2->len = htonl((n_long)len);
459 		argsv2->xxx = htonl((n_long)0);
460 		hlen = sizeof(*replv2) - NFSREAD_SIZE;
461 		cc = rpc_call(d->iodesc, NFS_PROG, d->version, NFSPROC_READ,
462 		    argsv2, sizeof(*argsv2),
463 		    replv2, sizeof(*replv2));
464 		break;
465 	case NFS_VER3:
466 		getfh(d, argsv3->fh);
467 		setnquad(argsv3->off, (uint64_t)off);
468 		argsv3->len = htonl((n_long)len);
469 		hlen = sizeof(*replv3) - NFSREAD_SIZE;
470 
471 		/* adjust for variable sized file handle */
472 		alen = sizeof(*argsv3) - offsetof(struct argsv3, off);
473 		memmove(argsv3->fh + fhstore(d->version, argsv3->fh),
474 		    &argsv3->off, alen);
475 		alen += fhstore(d->version, argsv3->fh);
476 
477 		cc = rpc_call(d->iodesc, NFS_PROG, d->version, NFSPROC_READ,
478 		    argsv3, alen,
479 		    replv3, sizeof(*replv3));
480 		break;
481 	default:
482 		errno = ENOSYS;
483 		return -1;
484 	}
485 
486 	if (cc == -1) {
487 		/* errno was already set by rpc_call */
488 		return -1;
489 	}
490 	if (cc < (ssize_t)hlen) {
491 		errno = EBADRPC;
492 		return -1;
493 	}
494 
495 	switch (d->version) {
496 	case NFS_VER2:
497 		if (replv2->errno) {
498 			errno = ntohl(replv2->errno);
499 			return -1;
500 		}
501 		x = ntohl(replv2->count);
502 		data = replv2->data;
503 		break;
504 	case NFS_VER3:
505 		if (replv3->errno) {
506 			errno = ntohl(replv3->errno);
507 			return -1;
508 		}
509 
510 		/* adjust for optional attributes */
511 		if (replv3->attrflag) {
512 			x = ntohl(replv3->length);
513 			data = replv3->data;
514 		} else {
515 			replv3no = (struct replv3_noattr *)replv3;
516 			x = ntohl(replv3no->length);
517 			data = replv3no->data;
518 		}
519 		break;
520 	default:
521 		errno = ENOSYS;
522 		return -1;
523 	}
524 
525 	rlen = cc - hlen;
526 	if (rlen < (size_t)x) {
527 		printf("%s: short packet, %zu < %ld\n", __func__, rlen, x);
528 		errno = EBADRPC;
529 		return -1;
530 	}
531 	(void)memcpy(addr, data, x);
532 	return x;
533 }
534 
535 /*
536  * nfs_mount - mount this nfs filesystem to a host
537  * On error, return non-zero and set errno.
538  */
539 int
540 nfs_mount(int sock, struct in_addr ip, char *path)
541 {
542 	struct iodesc *desc;
543 	struct nfsv2_fattr *fa2;
544 	struct nfsv3_fattr *fa3;
545 
546 	if (!(desc = socktodesc(sock))) {
547 		errno = EINVAL;
548 		return -1;
549 	}
550 
551 	/* Bind to a reserved port. */
552 	desc->myport = htons(--rpc_port);
553 	desc->destip = ip;
554 	if (nfs_getrootfh(desc, path, nfs_root_node.fh, &nfs_root_node.version))
555 		return -1;
556 	nfs_root_node.iodesc = desc;
557 	/* Fake up attributes for the root dir. */
558 	switch (nfs_root_node.version) {
559 	case NFS_VER2:
560 		fa2 = &nfs_root_node.u_fa.v2;
561 		fa2->fa_type  = htonl(NFDIR);
562 		fa2->fa_mode  = htonl(0755);
563 		fa2->fa_nlink = htonl(2);
564 		break;
565 	case NFS_VER3:
566 		fa3 = &nfs_root_node.u_fa.v3;
567 		fa3->fa_type  = htonl(NFDIR);
568 		fa3->fa_mode  = htonl(0755);
569 		fa3->fa_nlink = htonl(2);
570 		break;
571 	default:
572 		errno = ENOSYS;
573 		return -1;
574 	}
575 
576 #ifdef NFS_DEBUG
577 	if (debug)
578 		printf("%s: got fh for %s\n", __func__, path);
579 #endif
580 
581 	return 0;
582 }
583 
584 /*
585  * Open a file.
586  * return zero or error number
587  */
588 __compactcall int
589 nfs_open(const char *path, struct open_file *f)
590 {
591 	struct nfs_iodesc *newfd, *currfd;
592 	const char *cp;
593 #ifndef NFS_NOSYMLINK
594 	const char *ncp;
595 	int c;
596 	char namebuf[NFS_MAXPATHLEN + 1];
597 	char linkbuf[NFS_MAXPATHLEN + 1];
598 	int nlinks = 0;
599 	n_long fa_type;
600 #endif
601 	int error = 0;
602 
603 #ifdef NFS_DEBUG
604  	if (debug)
605 		printf("%s: %s\n", __func__, path);
606 #endif
607 
608 	if (nfs_root_node.iodesc == NULL) {
609 		printf("%s: must mount first.\n", __func__);
610 		return ENXIO;
611 	}
612 
613 	currfd = &nfs_root_node;
614 	newfd = 0;
615 
616 #ifndef NFS_NOSYMLINK
617 	cp = path;
618 	while (*cp) {
619 		/*
620 		 * Remove extra separators
621 		 */
622 		while (*cp == '/')
623 			cp++;
624 
625 		if (*cp == '\0')
626 			break;
627 		/*
628 		 * Check that current node is a directory.
629 		 */
630 		switch (currfd->version) {
631 		case NFS_VER2:
632 			fa_type = currfd->u_fa.v2.fa_type;
633 			break;
634 		case NFS_VER3:
635 			fa_type = currfd->u_fa.v3.fa_type;
636 			break;
637 		default:
638 			fa_type = htonl(NFNON);
639 			break;
640 		}
641 		if (fa_type != htonl(NFDIR)) {
642 			error = ENOTDIR;
643 			goto out;
644 		}
645 
646 		/* allocate file system specific data structure */
647 		newfd = alloc(sizeof(*newfd));
648 		newfd->iodesc = currfd->iodesc;
649 		newfd->off = 0;
650 		newfd->version = currfd->version;
651 
652 		/*
653 		 * Get next component of path name.
654 		 */
655 		{
656 			int len = 0;
657 
658 			ncp = cp;
659 			while ((c = *cp) != '\0' && c != '/') {
660 				if (++len > NFS_MAXNAMLEN) {
661 					error = ENOENT;
662 					goto out;
663 				}
664 				cp++;
665 			}
666 		}
667 
668 		/* lookup a file handle */
669 		error = nfs_lookupfh(currfd, ncp, cp - ncp, newfd);
670 		if (error)
671 			goto out;
672 
673 		/*
674 		 * Check for symbolic link
675 		 */
676 		switch (newfd->version) {
677 		case NFS_VER2:
678 			fa_type = newfd->u_fa.v2.fa_type;
679 			break;
680 		case NFS_VER3:
681 			fa_type = newfd->u_fa.v3.fa_type;
682 			break;
683 		default:
684 			fa_type = htonl(NFNON);
685 			break;
686 		}
687 		if (fa_type == htonl(NFLNK)) {
688 			int link_len, len;
689 
690 			error = nfs_readlink(newfd, linkbuf);
691 			if (error)
692 				goto out;
693 
694 			link_len = strlen(linkbuf);
695 			len = strlen(cp);
696 
697 			if (link_len + len > MAXPATHLEN
698 			    || ++nlinks > MAXSYMLINKS) {
699 				error = ENOENT;
700 				goto out;
701 			}
702 
703 			(void)memcpy(&namebuf[link_len], cp, len + 1);
704 			(void)memcpy(namebuf, linkbuf, link_len);
705 
706 			/*
707 			 * If absolute pathname, restart at root.
708 			 * If relative pathname, restart at parent directory.
709 			 */
710 			cp = namebuf;
711 			if (*cp == '/') {
712 				if (currfd != &nfs_root_node)
713 					dealloc(currfd, sizeof(*currfd));
714 				currfd = &nfs_root_node;
715 			}
716 
717 			dealloc(newfd, sizeof(*newfd));
718 			newfd = 0;
719 
720 			continue;
721 		}
722 
723 		if (currfd != &nfs_root_node)
724 			dealloc(currfd, sizeof(*currfd));
725 		currfd = newfd;
726 		newfd = 0;
727 	}
728 
729 	error = 0;
730 
731 out:
732 #else
733 	/* allocate file system specific data structure */
734 	currfd = alloc(sizeof(*currfd));
735 	currfd->iodesc = nfs_root_node.iodesc;
736 	currfd->off = 0;
737 	currfd->version = nfs_root_node.version;
738 
739 	cp = path;
740 	/*
741 	 * Remove extra separators
742 	 */
743 	while (*cp == '/')
744 		cp++;
745 
746 	/* XXX: Check for empty path here? */
747 
748 	error = nfs_lookupfh(&nfs_root_node, cp, strlen(cp), currfd);
749 #endif
750 	if (!error) {
751 		f->f_fsdata = (void *)currfd;
752 		fsmod = "nfs";
753 		return 0;
754 	}
755 
756 #ifdef NFS_DEBUG
757 	if (debug)
758 		printf("%s: %s lookupfh failed: %s\n", __func__,
759 		    path, strerror(error));
760 #endif
761 	if (currfd != &nfs_root_node)
762 		dealloc(currfd, sizeof(*currfd));
763 	if (newfd)
764 		dealloc(newfd, sizeof(*newfd));
765 
766 	return error;
767 }
768 
769 __compactcall int
770 nfs_close(struct open_file *f)
771 {
772 	struct nfs_iodesc *fp = (struct nfs_iodesc *)f->f_fsdata;
773 
774 #ifdef NFS_DEBUG
775 	if (debug)
776 		printf("%s: fp=%p\n", __func__, fp);
777 #endif
778 
779 	if (fp)
780 		dealloc(fp, sizeof(struct nfs_iodesc));
781 	f->f_fsdata = (void *)0;
782 
783 	return 0;
784 }
785 
786 /*
787  * read a portion of a file
788  */
789 __compactcall int
790 nfs_read(struct open_file *f, void *buf, size_t size, size_t *resid)
791 {
792 	struct nfs_iodesc *fp = (struct nfs_iodesc *)f->f_fsdata;
793 	ssize_t cc;
794 	char *addr = buf;
795 
796 #ifdef NFS_DEBUG
797 	if (debug)
798 		printf("%s: size=%zu off=%" PRIx64 "\n", __func__, size, fp->off);
799 #endif
800 	while ((int)size > 0) {
801 #if !defined(LIBSA_NO_TWIDDLE)
802 		twiddle();
803 #endif
804 		cc = nfs_readdata(fp, fp->off, (void *)addr, size);
805 		/* XXX maybe should retry on certain errors */
806 		if (cc == -1) {
807 #ifdef NFS_DEBUG
808 			if (debug)
809 				printf("%s: read: %s\n", __func__,
810 				    strerror(errno));
811 #endif
812 			return errno;	/* XXX - from nfs_readdata */
813 		}
814 		if (cc == 0) {
815 #ifdef NFS_DEBUG
816 			if (debug)
817 				printf("%s: hit EOF unexpectedly\n", __func__);
818 #endif
819 			goto ret;
820 		}
821 		fp->off += cc;
822 		addr += cc;
823 		size -= cc;
824 	}
825 ret:
826 	if (resid)
827 		*resid = size;
828 
829 	return 0;
830 }
831 
832 /*
833  * Not implemented.
834  */
835 __compactcall int
836 nfs_write(struct open_file *f, void *buf, size_t size, size_t *resid)
837 {
838 	return EROFS;
839 }
840 
841 __compactcall off_t
842 nfs_seek(struct open_file *f, off_t offset, int where)
843 {
844 	struct nfs_iodesc *d = (struct nfs_iodesc *)f->f_fsdata;
845 	off_t size;
846 
847 	switch (d->version) {
848 	case NFS_VER2:
849 		size = ntohl(d->u_fa.v2.fa_size);
850 		break;
851 	case NFS_VER3:
852 		size = getnquad(d->u_fa.v3.fa_size);
853 		break;
854 	default:
855 		return -1;
856 	}
857 
858 	switch (where) {
859 	case SEEK_SET:
860 		d->off = offset;
861 		break;
862 	case SEEK_CUR:
863 		d->off += offset;
864 		break;
865 	case SEEK_END:
866 		d->off = size - offset;
867 		break;
868 	default:
869 		return -1;
870 	}
871 
872 	return d->off;
873 }
874 
875 /* NFNON=0, NFREG=1, NFDIR=2, NFBLK=3, NFCHR=4, NFLNK=5 */
876 const int nfs_stat_types[8] = {
877 	0, S_IFREG, S_IFDIR, S_IFBLK, S_IFCHR, S_IFLNK, 0 };
878 
879 __compactcall int
880 nfs_stat(struct open_file *f, struct stat *sb)
881 {
882 	struct nfs_iodesc *fp = (struct nfs_iodesc *)f->f_fsdata;
883 	n_long ftype, mode;
884 
885 	switch (fp->version) {
886 	case NFS_VER2:
887 		ftype = ntohl(fp->u_fa.v2.fa_type);
888 		mode  = ntohl(fp->u_fa.v2.fa_mode);
889 		sb->st_nlink = ntohl(fp->u_fa.v2.fa_nlink);
890 		sb->st_uid   = ntohl(fp->u_fa.v2.fa_uid);
891 		sb->st_gid   = ntohl(fp->u_fa.v2.fa_gid);
892 		sb->st_size  = ntohl(fp->u_fa.v2.fa_size);
893 		break;
894 	case NFS_VER3:
895 		ftype = ntohl(fp->u_fa.v3.fa_type);
896 		mode  = ntohl(fp->u_fa.v3.fa_mode);
897 		sb->st_nlink = ntohl(fp->u_fa.v3.fa_nlink);
898 		sb->st_uid   = ntohl(fp->u_fa.v3.fa_uid);
899 		sb->st_gid   = ntohl(fp->u_fa.v3.fa_gid);
900 		sb->st_size  = getnquad(fp->u_fa.v3.fa_size);
901 		break;
902 	default:
903 		return -1;
904 	}
905 
906 	mode |= nfs_stat_types[ftype & 7];
907 	sb->st_mode  = mode;
908 
909 	return 0;
910 }
911 
912 #if defined(LIBSA_ENABLE_LS_OP)
913 #include "ls.h"
914 __compactcall void
915 nfs_ls(struct open_file *f, const char *pattern)
916 {
917 	lsunsup("nfs");
918 }
919 #endif
920