Home | History | Annotate | Line # | Download | only in libsa
nfs.c revision 1.52
      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