# Patch origin: nfs-server source RPM from openSUSE 10.3

--- nfs-server/Makefile.in
+++ nfs-server/Makefile.in	2002/11/08 13:59:16
@@ -100,7 +100,7 @@
 		  utimes.c mkdir.c rename.c getopt.c getopt_long.c \
 		  alloca.c mountlist.c xmalloc.c \
 		  xstrdup.c strdup.c strstr.c nfsmounted.c faccess.c \
-		  haccess.c daemon.c signals.c
+		  haccess.c daemon.c signals.c teahash3.c
 XDRFILES	= mount.x nfs_prot.x
 GENFILES	= mount.h mount_xdr.c mount_svc.c nfs_prot.h nfs_prot_xdr.c \
 		  ugid.h ugid_xdr.c ugid_clnt.c
@@ -112,7 +112,7 @@
 MANPAGES8	= showmount
 MANPAGES	= $(MANPAGES5) $(MANPAGES8p) $(MANPAGES8)
 LIBOBJS		= version.o fsusage.o mountlist.o xmalloc.o xstrdup.o \
-		  nfsmounted.o faccess.o haccess.o daemon.o \
+		  nfsmounted.o faccess.o haccess.o daemon.o teahash3.o \
 		  signals.o @LIBOBJS@ @ALLOCA@
 OBJS		= logging.o fh.o devtab.o auth_init.o auth_clnt.o auth.o
 NFSD_OBJS	= nfsd.o rpcmisc.o nfs_dispatch.o getattr.o setattr.o \
--- nfs-server/auth.c
+++ nfs-server/auth.c	2002/11/08 13:59:16
@@ -83,6 +83,7 @@
 					0,		/* read-only */
 					0,		/* relative links */
 					0,		/* noaccess */
+					0,		/* hashed inodes */
 					1,		/* cross_mounts */
 					1,		/* allow setuid */
 					65534,		/* default uid */
@@ -100,6 +101,7 @@
 					0,		/* relative links */
 					0,		/* noaccess */
 					1,		/* cross_mounts */
+					0,		/* hashed inodes */
 					0,		/* allow setuid */
 					65534,		/* default uid */
 					65534,		/* default gid */
@@ -991,6 +993,7 @@
 	if (mp == 0) {
 		mp = (nfs_mount*) xmalloc(sizeof(nfs_mount));
 		memset(mp, 0, sizeof(*mp));
+		mp->mount_dev = 0;
 		mp->origin = cp;
 		mp->client = cp;
 		mp->path   = xstrdup(path);
@@ -1169,6 +1172,8 @@
 	default_options.nobody_gid = anon_gid;
 	anonymous_options.nobody_uid = anon_uid;
 	anonymous_options.nobody_gid = anon_gid;
+	default_options.cross_mounts = cross_mounts;
+	default_options.hashed_inodes = hashed_inodes;
 
 	memset(cached_clients, 0, sizeof(cached_clients));
 	cached_next = 0;
--- nfs-server/auth.h
+++ nfs-server/auth.h	2002/11/08 13:59:16
@@ -43,15 +43,16 @@
 
 typedef struct nfs_options {
 	ugid_mapping_t		uidmap;		/* uid/gid mapping behavior */
-	int			root_squash;
-	int			all_squash;
-	int			some_squash;	/* speed up luid() etc. */
-	int			secure_port;
-	int			read_only;
-	int			link_relative;
-	int			noaccess;
-	int			cross_mounts;
-	int			allow_setuid;
+	unsigned		root_squash : 1;
+	unsigned		all_squash : 1;
+	unsigned		some_squash : 1;	/* speed up luid() etc. */
+	unsigned		secure_port : 1;
+	unsigned		read_only : 1;
+	unsigned		link_relative : 1;
+	unsigned		noaccess : 1;
+	unsigned		cross_mounts : 1;
+	unsigned		hashed_inodes : 1;
+	unsigned		allow_setuid : 1;
 	uid_t			nobody_uid;
 	gid_t			nobody_gid;
 	char *			clnt_nisdomain;
@@ -64,6 +65,7 @@
 	int			length;
 	char *			path;
 	nfs_options		o;
+	dev_t			mount_dev;
 	/* Original NFS client */
 	struct nfs_client *	origin;
 } nfs_mount;
@@ -121,6 +123,8 @@
 extern void       auth_check_all_netmasks(void);
 extern void	  auth_sort_all_mountlists(void);
 extern void	  auth_log_all(void);
+extern int	  auth_checkdev(nfs_mount *, dev_t dev);
+extern int	  auth_checkpathdev(char *, dev_t dev);
 
 /* This function lets us set our euid/fsuid temporarily */
 extern void       auth_override_uid(uid_t);
--- nfs-server/auth_clnt.c
+++ nfs-server/auth_clnt.c	2002/11/08 13:59:16
@@ -89,6 +89,13 @@
 		return NULL;
 	}
 
+	if (!mp->o.cross_mounts && !mp->mount_dev) {
+		struct stat st;
+		if (!lstat(mp->path, &st) < 0)
+			return NULL;
+		mp->mount_dev = st.st_dev;
+	}
+
 	/* Check request originated on a privileged port. */
 	if (!allow_non_root && mp->o.secure_port
 	 && !SECURE_PORT(svc_getcaller(rqstp->rq_xprt)->sin_port)) {
@@ -350,3 +357,28 @@
 	return 1;
 }
 #endif
+
+int auth_checkpathdev(char *path, dev_t dev)
+{
+	nfs_mount *mp = auth_match_mount(nfsclient, path);
+	if (!mp)
+		return 0;
+	return auth_checkdev(mp, dev);
+}
+
+int auth_checkdev(nfs_mount *mp, dev_t dev)
+{
+	if (!mp->mount_dev)
+		return 1;
+	if (mp->mount_dev != dev) {
+		struct stat st;
+		/* Restat in case the cd switched */
+		if (efs_lstat(mp->path, &st) < 0) {
+			Dprintf(L_ERROR, "Unable to stat mount point %s\n", mp->path);
+			return 0;
+		}
+		mp->mount_dev = st.st_dev;
+	}
+	return mp->mount_dev == dev;
+}
+
--- nfs-server/auth_init.c
+++ nfs-server/auth_init.c	2002/11/08 13:59:16
@@ -320,6 +320,14 @@
 			/* knfsd compatibility, ignore */;
 		else ifkwd(4, "sync")
 			/* knfsd compatibility, ignore */;
+		else ifkwd(13, "hashed_inodes")
+			mp->o.hashed_inodes = 1;
+		else ifkwd(16, "no_hashed_inodes")
+			mp->o.hashed_inodes = 0;
+		else ifkwd(12, "cross_mounts")
+			mp->o.cross_mounts = 1;
+		else ifkwd(15, "no_cross_mounts")
+			mp->o.cross_mounts = 0;
 		else {
 			Dprintf(L_ERROR,
 				"Unknown keyword \"%.*s\" in export file\n",
--- nfs-server/exports.man
+++ nfs-server/exports.man	2002/11/08 13:59:16
@@ -208,6 +208,17 @@
 .IR no_all_squash ,
 which is the default setting.
 .TP
+.IR hashed_inodes
+Use a special scheme to generate inode numbers that may work better with
+reiserfs filesystems.
+.IR no_hashed_inodes
+which uses a direct mapping is the default.
+.TP
+.IR cross_mounts
+Do not cross mount points in exports. Turning this off with
+.IR no_cross_mounts
+avoids inode number space conflicts when there are too many files.
+.TP
 .IR map_daemon
 This option turns on dynamic uid/gid mapping. Each uid in an NFS request
 will be translated to the equivalent server uid, and each uid in an
--- nfs-server/fh.c
+++ nfs-server/fh.c	2002/11/08 14:11:31
@@ -4,8 +4,9 @@
  *
  *		Interfaces:
  *		    pseudo_inode
- *			mostly used internally, but also called from unfsd.c
- *			when reporting directory contents.
+ *			mostly used internally, for hash tables
+ *		    visible_inode
+ *			generate visible inode shown to the client in the fattr.
  *		    fh_init
  *			Initializes the queues and 'flush' timer
  *		    fh_pr
@@ -47,6 +48,8 @@
  * Note: the original code mistakenly assumes that the overall path
  *	length remains within the value given by PATH_MAX... that leads
  *	to interesting buffer overflows all over the place.
+ *
+ *	Depends that dev_t only uses 16bits.
  */
 
 #include <assert.h>
@@ -137,9 +140,9 @@
 };
 
 /* Forward declared local functions */
-static psi_t	path_psi(char *, nfsstat *, struct stat *, int);
+static psi_t	path_psi(char *, nfsstat *, struct stat *, int, int *);
 static psi_t	path_psi_m(char *, nfsstat *, struct stat *,
-					      struct stat *, int);
+					      struct stat *, int, int *);
 static int	fh_flush_fds(void);
 static char *	fh_dump(svc_fh *);
 static void	fh_insert_fdcache(fhcache *fhc);
@@ -173,19 +176,22 @@
 	fh_list_size++;
 
 	/* Insert into hash tab. */
-	hash_slot = &(fh_hashed[fhc->h.psi % HASH_TAB_SIZE]);
+	hash_slot = &(fh_hashed[pseudo_inode(fhc->h.ino,fhc->h.dev) % HASH_TAB_SIZE]);
 	fhc->hash_next = *hash_slot;
 	*hash_slot = fhc;
 }
 
 static fhcache *
-fh_lookup(psi_t psi)
+fh_lookup(ino_t ino, dev_t dev)
 {
 	register fhcache *fhc;
 
-	fhc = fh_hashed[psi % HASH_TAB_SIZE];
-	while (fhc != NULL && fhc->h.psi != psi)
+	fhc = fh_hashed[pseudo_inode(ino,dev) % HASH_TAB_SIZE];
+	while (fhc != NULL) {
+		if (fhc->h.ino == ino && fhc->h.dev == dev)
+			break;
 		fhc = fhc->hash_next;
+	}
 	return (fhc);
 }
 
@@ -193,7 +199,8 @@
 fh_insert_fdcache(fhcache *fhc)
 {
 #ifdef FHTRACE
-	Dprintf(D_FHTRACE, "insert fh %x into fdcache @%d\n", fhc->h.psi, fhc->fd);
+	Dprintf(D_FHTRACE, "insert fh %x,%x into fdcache @%d\n",
+			fhc->h.ino, fhc->h.dev, fhc->fd);
 	if (fhc->fd < 0) {
 		fh_complain("fd cache bug: bad fd", fhc);
 		return;
@@ -289,8 +296,9 @@
 #endif
 
 	Dprintf(D_FHTRACE|D_FHCACHE,
-		"fh_delete: deleting handle %x ('%s', fd=%d)\n",
-		fhc, fhc->path ? fhc->path : "<unnamed>", fhc->fd);
+		"fh_delete: deleting handle %x [%x,%x] ('%s', fd=%d)\n",
+		fhc, fhc->h.dev, fhc->h.ino, fhc->path ? fhc->path : "<unnamed>",
+			fhc->fd);
 
 	/* Remove from current posn */
 	fhc->prev->next = fhc->next;
@@ -298,7 +306,7 @@
 	fh_list_size--;
 
 	/* Remove from hash tab */
-	hash_slot = &(fh_hashed[fhc->h.psi % HASH_TAB_SIZE]);
+	hash_slot = &(fh_hashed[pseudo_inode(fhc->h.ino,fhc->h.dev) % HASH_TAB_SIZE]);
 	while (*hash_slot != NULL && *hash_slot != fhc)
 		hash_slot = &((*hash_slot)->hash_next);
 	if (*hash_slot == NULL)
@@ -528,6 +536,7 @@
 		index -= 8;
 	}
 
+#if 0
 	/* If we have an XXL inode number, spew out warning (but at most
 	 * once a second) */
 	if (inode & ~mask) {
@@ -541,14 +550,34 @@
 		}
 		inode &= mask;
 	}
-
+#endif
 	return (psi_t) (prefix | inode);
 #endif
 }
 
+/* Inode as handed out by attr calls. */
+psi_t
+visible_inode(ino_t ino, dev_t dev, nfs_mount *mount)
+{
+	if (!mount->o.cross_mounts)
+		return ino;
+
+	if (mount->o.hashed_inodes) {
+		extern __u32 teahash3(/*u32 k[2], *//*u8*/const char *msg, int len);
+
+		struct {
+			ino_t ino;
+			dev_t dev;
+		} tup = { ino,dev };
+		return teahash3((char *) &tup, sizeof tup);
+	}
+
+	return pseudo_inode(ino, dev);
+}
+
 #if 1
 static char *
-fh_buildpath(svc_fh *h)
+fh_buildpath(svc_fh *h, dev_t basedev)
 {
 	char		pathbuf[PATH_MAX + NAME_MAX + 1], *path;
 	long		cookie_stack[HP_LEN + 1];
@@ -565,13 +594,17 @@
 
 	if (efs_stat("/", &sbuf) < 0)
 		return (NULL);
-	psi = pseudo_inode(sbuf.st_ino, sbuf.st_dev);
 	if (h->hash_path[0] == 0) {
-		if (psi != h->psi)
-			return (NULL);
-		return xstrdup("/");
+		if (sbuf.st_ino == h->ino && sbuf.st_dev == h->dev)
+			;
+		else
+			return NULL;
+		strcpy(pathbuf,"/");
+		path = xstrdup(pathbuf);
+		return (path);
 	}
 
+	psi = pseudo_inode(sbuf.st_ino, sbuf.st_dev);
 	if (hash_psi(psi) != h->hash_path[1])
 		return (NULL);
 
@@ -599,11 +632,18 @@
 
 				psi = pseudo_inode(dp->d_ino, sbuf.st_dev);
 				if (i == h->hash_path[0] + 1) {
-					if (psi != h->psi)
+					if (sbuf.st_dev != h->dev || dp->d_ino != h->ino)
 						continue;
 					/* GOT IT */
 					strcpy(pathbuf + pathlen, dp->d_name);
-					path = xstrdup(pathbuf);
+					if (!basedev || sbuf.st_dev == basedev ||
+						auth_checkpathdev(pathbuf, sbuf.st_dev)) {
+						path = xstrdup(pathbuf);
+					} else {
+						dprintf(L_ERROR,  "fh_buildpath: basedev %x != dev %x for %s\n",
+								(unsigned)basedev,(unsigned)sbuf.st_dev,pathbuf);
+						path = NULL;
+					}
 					efs_closedir(dir);
 					auth_override_uid(auth_uid);
 					return (path);
@@ -754,16 +794,16 @@
 #endif
 
 static psi_t
-path_psi(char *path, nfsstat *status, struct stat *sbp, int svalid)
+path_psi(char *path, nfsstat *status, struct stat *sbp, int svalid, int *mp)
 {
 	struct stat smounted;
 
-	return path_psi_m(path, status, sbp, &smounted, svalid);
+	return path_psi_m(path, status, sbp, &smounted, svalid, mp);
 }
 
 static psi_t
 path_psi_m(char *path, nfsstat *status,
-		struct stat *sbp, struct stat *mbp, int svalid)
+		struct stat *sbp, struct stat *mbp, int svalid, int *mp)
 {
 	struct stat sbuf, ddbuf;
 
@@ -815,6 +855,8 @@
 			DIR *dirp;
 			struct dirent *dp;
 
+			if (mp) *mp = 1;
+
 			errno = 0;
 			dirp = efs_opendir(dname);
 			fname[-1] = '/';	/* Restore path */
@@ -860,9 +902,70 @@
 }
 
 fhcache *
-fh_find(svc_fh *h, int mode)
+fh_newfh(svc_fh *h, int mode, dev_t basedev)
+{
+	fhcache *fhc, *flush;
+
+	ex_state = active;
+	for (flush = fh_tail.prev; fh_list_size > FH_CACHE_LIMIT; flush = fhc) {
+		/* Don't flush current head. */
+		if (flush == &fh_head)
+			break;
+		fhc = flush->prev;
+		fh_delete(flush);
+	}
+	fhc = (fhcache *) xmalloc(sizeof *fhc);
+	if (mode == FHFIND_FCREATE) {
+		/* File will be created */
+		fhc->path = NULL;
+	} else {
+		/* File must exist. Attempt to construct from hash_path */
+		char *path;
+
+		if ((path = fh_buildpath(h, basedev)) == NULL) {
+#ifdef FHTRACE
+			Dprintf(D_FHTRACE, "fh_find: stale fh (hash path)\n");
+			Dprintf(D_FHTRACE, "\tdata: %s\n", fh_dump(h));
+#endif
+			free(fhc);
+			ex_state = inactive;
+			return NULL;
+		}
+		fhc->path = path;
+	}
+	fhc->flags = 0;
+	if (fhc->path && efs_lstat(fhc->path, &fhc->attrs) >= 0) {
+		if (re_export && nfsmounted(fhc->path, &fhc->attrs))
+			fhc->flags |= FHC_NFSMOUNTED;
+		fhc->flags |= FHC_ATTRVALID;
+	}
+	fhc->fd = -1;
+	fhc->last_used = curtime;
+	fhc->h = *h;
+	fhc->last_clnt = NULL;
+	fhc->last_mount = NULL;
+	fhc->last_uid = (uid_t)-1;
+	fhc->fd_next = fhc->fd_prev = NULL;
+	fh_inserthead(fhc);
+	Dprintf(D_FHCACHE,
+		"fh_find: created new handle %x (path `%s' ino:%x dev:%x)\n",
+		fhc, fhc->path ? fhc->path : "<unnamed>", fhc->h.ino, fhc->h.dev);
+	ex_state = inactive;
+	if (fh_list_size > FH_CACHE_LIMIT)
+		flush_cache(0);
+#ifdef FHTRACE
+	if (fhc->h.hash_path[0] == 0xFF) {
+		Dprintf(L_ERROR, "newly created fh instantly flushed?!");
+		return NULL;
+	}
+#endif
+	return (fhc);
+}
+
+fhcache *
+fh_find(svc_fh *h, int mode, dev_t basedev)
 {
-	register fhcache *fhc, *flush;
+	register fhcache *fhc;
 	int		 check;
 
 	check = (mode & FHFIND_CHECK);
@@ -877,12 +980,12 @@
 
 	ex_state = active;
 	time(&curtime);
-	while ((fhc = fh_lookup(h->psi)) != NULL) {
+	while ((fhc = fh_lookup(h->ino,h->dev)) != NULL) {
 		struct stat	sbuf, *s = NULL;
 		nfsstat		dummy;
 
-		Dprintf(D_FHCACHE, "fh_find: psi=%lx... found '%s', fd=%d\n",
-			(unsigned long) h->psi,
+		Dprintf(D_FHCACHE, "fh_find: (%u,%u)... found '%s', fd=%d\n",
+			h->ino, h->dev,
 			fhc->path ? fhc->path : "<unnamed>",
 			fhc->fd);
 
@@ -905,6 +1008,7 @@
 				Dprintf(D_FHTRACE,
 					"fh_find: stale fh: lstat: %m\n");
 			} else {
+				int mp = 0;
 				/* If device/ino don't match, fhc->path may
 				 * be a mount point (hence lstat() returns
 				 * a different inode number than the readdir()
@@ -915,19 +1019,26 @@
 
 				/* Get the dev/ino of the underlying
 				 * mount point. */
-				path_psi(fhc->path, &dummy, s, 1);
-				if (fh_attrmatch(fhc, s))
-					goto fh_return;
+				if (path_psi(fhc->path, &dummy, s, 1, &mp) &&
+					fh_attrmatch(fhc, s)) {
+					if (!mp)
+						Dprintf(D_FHTRACE,"fh_find: should be mount point %x,%x\n",
+								h->dev,h->ino);
+					
+				}
 
-				Dprintf(D_FHTRACE, "fh_find: stale fh: %lx",
-					(unsigned long) h->psi);
+				Dprintf(D_FHTRACE, "fh_find: stale fh: "
+					"dev/ino %x/%lx ino:%x dev:%x",
+					s->st_dev, s->st_ino,
+					(unsigned)h->ino, (unsigned)h->dev);
 			}
 
 		fh_discard:
 #ifdef FHTRACE
 			Dprintf(D_FHTRACE, "\tdata: %s\n", fh_dump(h));
 #endif
-			Dprintf(D_FHCACHE, "fh_find: delete cached handle\n");
+			Dprintf(D_FHCACHE, "fh_find: delete cached handle %x,%x <%x>\n",
+					fhc->h.dev,fhc->h.ino,fhc->path ? fhc->path : "no path");
 			fh_delete(fhc);
 			break;
 		}
@@ -947,88 +1058,13 @@
 		return (fhc);
 	}
 
-	Dprintf(D_FHCACHE, "fh_find: psi=%lx... not found\n",
-			(unsigned long) h->psi);
-
-	if (mode == FHFIND_FCACHED) {
-		ex_state = inactive;
-		return NULL;
-	}
-
-	for (flush = fh_tail.prev; fh_list_size > FH_CACHE_LIMIT; flush = fhc) {
-		/* Don't flush current head. */
-		if (flush == &fh_head)
-			break;
-		fhc = flush->prev;
-		fh_delete(flush);
-	}
-
-	fhc = (fhcache *) xmalloc(sizeof *fhc);
-	if (mode == FHFIND_FCREATE) {
-		/* File will be created */
-		fhc->path = NULL;
-	} else {
-		/* File must exist. Attempt to construct from hash_path */
-		char *path;
-
-		if ((path = fh_buildpath(h)) == NULL) {
-#ifdef FHTRACE
-			Dprintf(D_FHTRACE, "fh_find: stale fh (hash path)\n");
-			Dprintf(D_FHTRACE, "\tdata: %s\n", fh_dump(h));
-#endif
-			free(fhc);
-			ex_state = inactive;
-			return NULL;
-		}
-		fhc->path = path;
-	}
-
-	fhc->flags = 0;
-	if (fhc->path && efs_lstat(fhc->path, &fhc->attrs) >= 0) {
-		if (nfsmounted(fhc->path, &fhc->attrs)) {
-			fhc->flags |= FHC_NFSMOUNTED;
-#if 0
-			/* We must allow the client to send us the
-			 * file handle for the NFS mount point itself,
-			 * but not for entries within an NFS mount.
-			 * XXX: needs fixing.
-			 */
-			if (!re_export) {
-				Dprintf(D_FHTRACE,
-					"Attempt to use %s (non-exportable)\n",
-					fhc->path);
-				free(fhc);
-				ex_state = inactive;
-				return NULL;
-			}
-#endif
-		}
-		fhc->flags |= FHC_ATTRVALID;
-		fhc->dev   = fhc->attrs.st_dev;
-		fhc->ino   = fhc->attrs.st_ino;
-		fhc->type  = fhc->attrs.st_mode & S_IFMT;
-	}
-	fhc->fd = -1;
-	fhc->last_used = curtime;
-	fhc->h = *h;
-	fhc->last_clnt = NULL;
-	fhc->last_mount = NULL;
-	fhc->last_uid = (uid_t)-1;
-	fhc->fd_next = fhc->fd_prev = NULL;
-	fh_inserthead(fhc);
-	Dprintf(D_FHCACHE,
-		"fh_find: created new handle %x (path `%s' psi %08x)\n",
-		fhc, fhc->path ? fhc->path : "<unnamed>", fhc->h.psi);
 	ex_state = inactive;
-	if (fh_list_size > FH_CACHE_LIMIT)
-		flush_cache(0);
-#ifdef FHTRACE
-	if (fhc->h.hash_path[0] == 0xFF) {
-		Dprintf(L_ERROR, "newly created fh instantly flushed?!");
+
+	Dprintf(D_FHCACHE, "fh_find: (%u,%u) ... not found\n",
+		h->ino, h->dev);
+	if (mode == FHFIND_FCACHED)
 		return NULL;
-	}
-#endif
-	return (fhc);
+	return fh_newfh(h, mode, basedev);
 }
 
 /*
@@ -1040,7 +1076,7 @@
 {
 	fhcache *h;
 
-	if ((h = fh_find((svc_fh *) fh, FHFIND_FCACHED)) == NULL)
+	if ((h = fh_find((svc_fh *) fh, FHFIND_FCACHED, 0)) == NULL)
 		return fh_dump((svc_fh *) fh);
 	return (h->path);
 }
@@ -1050,10 +1086,10 @@
 {
 	static char	buf[65];
 	char		*sp;
-	int		i, n = fh->hash_path[0];
+	int		i, n = fh->hash_path[0], l;
 
-	sprintf(buf, "%08x %02x ", fh->psi, fh->hash_path[0]);
-	for (i = 1, sp = buf + 12; i <= n && i < HP_LEN; i++, sp += 2)
+	l = sprintf(buf, "%08x %04x %02x ", fh->ino, fh->dev, fh->hash_path[0]);
+	for (i = 1, sp = buf + l; i <= n && i < HP_LEN; i++, sp += 2)
 		sprintf(sp, "%02x", fh->hash_path[i]);
 	return buf;
 }
@@ -1082,7 +1118,7 @@
 
 	memset(&key, 0, sizeof(key));
 	status = NFS_OK;
-	if ((psi = path_psi("/", &status, &stb, 0)) == 0)
+	if ((psi = path_psi("/", &status, &stb, 0, NULL)) == 0)
 		return ((int) status);
 
 	s = path;
@@ -1091,7 +1127,7 @@
 			return ((int) NFSERR_NAMETOOLONG);
 		key.hash_path[key.hash_path[0]] = hash_psi(psi);
 		*s = '\0';
-		if ((psi = path_psi(path, &status, &stb, 0)) == 0)
+		if ((psi = path_psi(path, &status, &stb, 0, NULL)) == 0)
 			return ((int) status);
 		*s = '/';
 	}
@@ -1099,11 +1135,12 @@
 		if (++(key.hash_path[0]) >= HP_LEN)
 			return ((int) NFSERR_NAMETOOLONG);
 		key.hash_path[key.hash_path[0]] = hash_psi(psi);
-		if ((psi = path_psi(path, &status, &stb, 0)) == 0)
+		if ((psi = path_psi(path, &status, &stb, 0, NULL)) == 0)
 			return ((int) status);
 	}
-	key.psi = psi;
-	h = fh_find(&key, FHFIND_FCREATE);
+	key.dev = stb.st_dev;
+	key.ino = stb.st_ino;
+	h = fh_find(&key, FHFIND_FCREATE, 0);
 
 #ifdef FHTRACE
 	if (!h)
@@ -1123,6 +1160,7 @@
 	return ((int) status);
 }
 
+#if 0
 char *
 fh_path(nfs_fh *fh, nfsstat *status)
 {
@@ -1135,6 +1173,7 @@
 	*status = NFS_OK;
 	return (h->path);
 }
+#endif
 
 nfs_fh *
 fh_handle(fhcache *h)
@@ -1349,7 +1388,7 @@
 	if (sbp == NULL)
 		sbp = &sbuf;
 
-	if ((dirh = fh_find((svc_fh *) &dopa->dir, FHFIND_FEXISTS)) == NULL)
+	if ((dirh = fh_find((svc_fh *) &dopa->dir, FHFIND_FEXISTS, 0)) == NULL)
 		return NFSERR_STALE;
 
 	/*
@@ -1419,8 +1458,22 @@
 
 	*new_fh = dopa->dir;
 	key = (svc_fh *) new_fh;
-	if ((key->psi = path_psi_m(pathbuf, &ret, sbp, &smount, 0)) == 0)
+
+	if (path_psi_m(pathbuf, &ret, sbp, &smount, 0, NULL) == 0)
 		return (ret);
+	key->ino = sbp->st_ino;
+	key->dev = sbp->st_dev;
+
+	if (sbp->st_dev != dirh->h.dev) {
+		nfs_mount *mp = dirh->last_mount;
+		if (!mp)
+			Dprintf(L_ERROR, "no last mount in fh_compose for %s\n", pathbuf);
+		else if (auth_checkdev(mp, sbp->st_dev) == 0) {
+			Dprintf(L_ERROR, "access to no cross path below mountpoint (<%s>, %x<->%x)\n",
+					pathbuf, mp->mount_dev, sbp->st_dev);
+			return NFSERR_STALE;
+		}
+	}
 
 	if (is_dd) {
 		/* Don't cd .. from root, or mysterious ailments will
@@ -1430,11 +1483,12 @@
 	} else {
 		if (++(key->hash_path[0]) >= HP_LEN)
 			return NFSERR_NAMETOOLONG;
-		key->hash_path[key->hash_path[0]] = hash_psi(dirh->h.psi);
+		key->hash_path[key->hash_path[0]] = hash_psi(pseudo_inode(dirh->h.ino,
+			dirh->h.dev));
 	}
 	/* FIXME: when crossing a mount point, we'll find the real
 	 * dev/ino in sbp and can store it in h... */
-	h = fh_find(key, FHFIND_FCREATE);
+	h = fh_find(key, FHFIND_FCREATE, 0);
 
 #ifdef FHTRACE
 	if (h == NULL)
@@ -1456,7 +1510,7 @@
 		/* We must have cached an old file under the same inode # */
 		Dprintf(D_FHTRACE, "Disposing of fh with bad path.\n");
 		fh_delete(h);
-		h = fh_find(key, FHFIND_FCREATE);
+		h = fh_find(key, FHFIND_FCREATE, dirh->last_mount ? dirh->last_mount->mount_dev : 0);
 #ifdef FHTRACE
 		if (!h) return NFSERR_STALE;
 #endif
@@ -1511,12 +1565,14 @@
 	return (NFS_OK);
 }
 
+#if 0
 psi_t
 fh_psi(nfs_fh *fh)
 {
 	svc_fh *h = (svc_fh *) fh;
 	return (h->psi);
 }
+#endif
 
 void
 fh_remove(char *path)
@@ -1524,12 +1580,13 @@
 	psi_t	psi;
 	nfsstat status;
 	fhcache *fhc;
+	struct stat st;
 
-	psi = path_psi(path, &status, NULL, 0);
+	psi = path_psi(path, &status, &st, 0, NULL);
 	if (psi == 0)
 		return;
 	ex_state = active;
-	fhc = fh_lookup(psi);
+	fhc = fh_lookup(st.st_ino,st.st_dev);
 	if (fhc != NULL)
 		fh_delete(fhc);
 
@@ -1634,6 +1691,11 @@
 fh_init(void)
 {
 	static int	initialized = 0;
+
+	if (sizeof(svc_fh) > 32) {
+		fprintf(stderr, "filehandle wrong size %d\n", sizeof(svc_fh));
+		exit(10);
+	}
 
 	if (initialized)
 		return;
--- nfs-server/fh.h
+++ nfs-server/fh.h	2002/11/08 13:59:16
@@ -20,6 +20,7 @@
 #define	FHC_XONLY_PATH		001	/* NOT USED ANYMORE */
 #define	FHC_ATTRVALID		002
 #define FHC_NFSMOUNTED		004
+#define FHC_CROSS		010
 
 /* Modes for fh_find */
 #define FHFIND_FEXISTS	0	/* file must exist */
@@ -65,11 +66,12 @@
  *
  * hash_path[hash_path[0]+1] ... hash_path[HP_LEN-1] == 0
  */
-#define	HP_LEN		(NFS_FHSIZE - sizeof(psi_t))
+#define        HP_LEN          (NFS_FHSIZE-sizeof(u_int32_t)-sizeof(u_int16_t))
 typedef struct {
-	psi_t		psi;
+	u_int32_t	ino;
+	u_int16_t	dev;
 	__u8		hash_path[HP_LEN];
-} svc_fh;
+} svc_fh __attribute__((packed));
 
 typedef enum { inactive, active } mutex;
 
@@ -100,6 +102,7 @@
 
 	/* These are fixed during the lifetime of this object */
 	svc_fh			h;
+	psi_t			psi;
 	dev_t			dev;
 	ino_t			ino;
 	mode_t			type;	/* st_mode & S_IFMT */
@@ -122,10 +125,11 @@
 /* Global function prototypes. */
 extern nfsstat	nfs_errno(void);
 extern psi_t	pseudo_inode(ino_t inode, dev_t dev);
+extern psi_t	visible_inode(ino_t inode, dev_t dev, nfs_mount *);
 extern void	fh_init(void);
 extern char	*fh_pr(nfs_fh *fh);
 extern int	fh_create(nfs_fh *fh, char *path);
-extern fhcache	*fh_find(svc_fh *h, int create);
+extern fhcache	*fh_find(svc_fh *h, int create, dev_t basedev);
 extern char	*fh_path(nfs_fh *fh, nfsstat *status);
 extern int	path_open(char *path, int omode, int perm);
 extern int	fh_fd(fhcache *fhc, nfsstat *status, int omode);
@@ -139,6 +143,7 @@
 extern void	fh_flush(int force);
 extern RETSIGTYPE flush_cache(int sig);
 extern int	nfsmounted(const char *path, struct stat *sbp);
+extern fhcache *fh_newfh(svc_fh *fh, int mode, dev_t basedev);
 
 #ifdef ENABLE_DEVTAB
 extern unsigned int	devtab_index(dev_t);
--- nfs-server/getattr.c
+++ nfs-server/getattr.c	2002/11/08 13:59:16
@@ -43,7 +43,7 @@
 {
 	fhcache *fhc;
 
-	if ((fhc = fh_find((svc_fh*)fh, FHFIND_FEXISTS)) == NULL) {
+	if ((fhc = fh_find((svc_fh*)fh, FHFIND_FEXISTS, 0)) == NULL) {
 		Dprintf(D_CALL, "getattr: failed! No such file.\n");
 		return (NFSERR_STALE);
 	}
@@ -103,18 +103,8 @@
 #else
 	attr->blocks = st_blocks(s);
 #endif
-#if 0
-	if (nfsmount->o.cross_mounts) {
-		attr->fsid = 1;
-		attr->fileid = fh_psi((nfs_fh *)&(fhc->h));
-	} else {
-		attr->fsid = s->st_dev;
-		attr->fileid = covered_ino(fhc->path);
-	}
-#else
-	attr->fsid   = 1;
-	attr->fileid = fh_psi((nfs_fh *)&(fhc->h));
-#endif
+	attr->fsid   = 1; // XXX
+	attr->fileid = visible_inode(fhc->h.ino, fhc->h.dev, nfsmount);
 
 	/* This may be needed by some Suns... testing */
 #define MINTIME	(24 * 2600)
--- nfs-server/mountd.c
+++ nfs-server/mountd.c	2002/11/08 13:59:16
@@ -36,6 +36,8 @@
 #include "signals.h"
 #include <rpc/pmap_clnt.h>
 
+int cross_mounts = 1;
+int hashed_inodes; /* dummy */
 
 static void	usage(FILE *, int);
 static void	terminate(void);
@@ -58,9 +60,9 @@
       {	"no-spoof-trace",	0,			0,	't' },
       { "version",		0,			0,	'v' },
       { "fail-safe",		optional_argument,	0,	'z' },
+      { "no-cross-mounts",	0,			0,	'x' },
       { "no-tcp",		0,			0,	OPT_NOTCP },
       {	"loopback-only",	0,			0,	OPT_LOOPBACK },
-
       { NULL,			0,			0,	0 }
 };
 static const char *	shortopts = "Fd:f:hnpP:rtvz::";
@@ -80,6 +82,7 @@
 int		need_reinit = 0;
 int		need_flush = 0;
 extern char	version[];
+nfs_client *nfsclient; /* dummy */
 
 /*
  * NULL
@@ -319,6 +322,9 @@
 	opterr = 0;
 	while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != EOF)
 		switch (c) {
+		case 'x':
+			cross_mounts = 0;
+			break;
 		case 'F':
 			foreground = 1;
 			break;
@@ -444,7 +450,7 @@
 				program_name);
 	fprintf(fp, "       [--debug kind] [--help] [--allow-non-root]\n");
 	fprintf(fp, "       [--promiscuous] [--version] [--port portnum]\n");
-	fprintf(fp, "       [--exports-file=file]\n");
+	fprintf(fp, "       [--exports-file=file] [--no-cross-mounts]\n");
 	exit(n);
 }
 
--- nfs-server/nfsd.c
+++ nfs-server/nfsd.c	2002/11/08 14:20:57
@@ -72,7 +72,7 @@
       { "no-tcp",		0,			0,	OPT_NOTCP },
       { "udp-only",		0,			0,	OPT_NOTCP },
       {	"loopback-only",	0,			0,	OPT_LOOPBACK },
-
+      { "hashed-inodes",	0,			0,	'I' },
       { NULL,		0,	0, 0 }
 };
 static const char *	shortopts = "a:d:Ff:hlnP:prR:tvz::";
@@ -91,6 +91,7 @@
 int			need_flush = 0;		/* flush fh cache */
 int			read_only = 0;		/* Global ro forced */
 int			cross_mounts = 1;	/* Transparently cross mnts */
+int			hashed_inodes = 0;
 int			log_transfers = 0;	/* Log transfers */
 static svc_fh		public_fh;		/* Public NFSv2 FH */
 
@@ -122,12 +123,17 @@
 {
 	static int	total = 0, cached = 0;
 	fhcache		*fhc;
+	int		newfh = 0;
 
-	/* Try to map FH. If not cached, reconstruct path with root priv */
-	fhc = fh_find((svc_fh *)fh, FHFIND_FEXISTS|FHFIND_CHECK);
-	if (fhc == NULL) {
-		*statp = NFSERR_STALE;
-		return NULL;
+	/* Try to map FH. */
+	fhc = fh_find((svc_fh *)fh, FHFIND_FCACHED|FHFIND_CHECK, 0);
+	if (!fhc) {
+		fhc = fh_newfh((svc_fh*)fh, FHFIND_FEXISTS|FHFIND_CHECK, 0);
+		if (!fhc) {
+			*statp = NFSERR_STALE;
+			return NULL;
+		}
+		newfh = 1;
 	}
 
 	/* Try to retrieve last client who accessed this fh */
@@ -163,6 +169,16 @@
 			100 * (double) cached / total);
 	 */
 
+	/* Trust the crossmount check of the parent directory for creates */
+	if (newfh &&
+		(fhc->flags & FHC_ATTRVALID) &&
+		auth_checkdev(nfsmount, fhc->attrs.st_dev) == 0) {
+		Dprintf(L_ERROR, "auth_fh: fh crossed mount %s: %x<->%x\n",
+				fhc->path ? fhc->path : "???", nfsmount->mount_dev, fhc->attrs.st_dev);
+		*statp = NFSERR_STALE; /* or ACCES? */
+		return NULL;
+	}
+
 	if (nfsmount->o.noaccess &&
 	    ((flags & CHK_NOACCESS) || strcmp(nfsmount->path, fhc->path))) {
 		struct in_addr	addr = svc_getcaller(rqstp->rq_xprt)->sin_addr;
@@ -195,6 +211,7 @@
 	fhcache		*fhc;
 	nfsstat		status;
 	char		*path = buf, *sp;
+	struct stat	st;
 
 	/* Authenticate directory file handle */
 	if ((fhc = auth_fh(rqstp, &dopa->dir, &status, flags)) == NULL)
@@ -219,6 +236,9 @@
 	if ((nfsmount = auth_path(nfsclient, rqstp, path)) == NULL)
 		return NFSERR_ACCES;
 
+	if (efs_lstat(path, &st) >= 0 && !auth_checkdev(nfsmount, st.st_dev))
+		return NFSERR_ACCES;
+
 	/* XXX: really need to call it again here?
 	 * Already invoked in auth_fh */
 	if (!auth_user(nfsmount, rqstp))
@@ -318,7 +338,8 @@
 	int		ispublic = 0;
 
 	/* First check whether this is the public FH */
-	if (((svc_fh *) fh)->psi == 0 && !memcmp(fh, &public_fh, FHSIZE)) {
+	if (((svc_fh *) fh)->dev == 0 && ((svc_fh*)fh)->ino == 0 &&
+		!memcmp(fh, &public_fh, FHSIZE)) {
 		if (public_root_path == NULL)
 			return NFSERR_ACCES;
 		memcpy(&argp->dir, &public_root, NFS_FHSIZE);
@@ -333,6 +354,7 @@
 	if (!(fhc = auth_fh(rqstp, fh, &status, CHK_READ)))
 		return status;
 
+	/* FIXME: does too many stats */
 	status = fh_compose(argp, &dp->file, &sbuf, -1, -1, ispublic);
 	if (status != NFS_OK)
 		return status;
@@ -896,6 +918,9 @@
 	errno = 0;
 	if (efs_lstat(h->path, &sbuf) < 0 || !(S_ISDIR(sbuf.st_mode)))
 		return (NFSERR_NOTDIR);
+	if (!auth_checkdev(h->last_mount, sbuf.st_dev))
+		dotsonly = 1;
+
 	if ((dirp = efs_opendir(h->path)) == NULL)
 		return ((errno ? nfs_errno() : NFSERR_NAMETOOLONG));
 
@@ -923,7 +948,7 @@
 		}
 
 		e = *ep = (entry *) xmalloc(sizeof(entry));
-		e->fileid = pseudo_inode(dp->d_ino, sbuf.st_dev);
+		e->fileid = visible_inode(dp->d_ino, sbuf.st_dev, h->last_mount);
 		e->name = xmalloc(NLENGTH(dp) + 1);
 		strcpy(e->name, dp->d_name);
 		dloc = htonl(efs_telldir(dirp));
@@ -1033,6 +1058,9 @@
 		case 'x':
 			cross_mounts = 0;
 			break;
+		case 'I':
+			hashed_inodes = 1;
+			break;
 		case 'z':
 			if (optarg)
 				failsafe_level = atoi(optarg);
@@ -1189,7 +1217,7 @@
 "       [--debug kind] [--exports-file=file] [--port port]\n"
 "       [--allow-non-root] [--promiscuous] [--version] [--foreground]\n"
 "       [--re-export] [--log-transfers] [--public-root path]\n"
-"       [--no-spoof-trace] [--help]\n"
+"       [--no-spoof-trace] [--no-cross-mounts] [--hashed-inodes] [--help]\n"
 						, program_name);
 	exit(n);
 }
--- nfs-server/nfsd.h
+++ nfs-server/nfsd.h	2002/11/08 13:59:16
@@ -51,6 +51,7 @@
 extern int			need_reinit;
 extern int			need_flush;
 extern time_t			nfs_dispatch_time;
+extern int cross_mounts, hashed_inodes;
 
 /* Include the other module definitions. */
 #include "auth.h"
--- nfs-server/setattr.c
+++ nfs-server/setattr.c	2002/11/08 13:59:16
@@ -17,6 +17,7 @@
 
 #define IGNORE_TIME	((unsigned int) -1)
 
+#if 0
 /*
  * Set file attributes based on file handle
  */
@@ -33,6 +34,7 @@
 	}
 	return setattr(path, attr, s, rqstp, flags);
 }
+#endif
 
 /*
  * Set file attributes given the path. The flags argument
--- nfs-server/teahash3.c
+++ nfs-server/teahash3.c	2002/11/08 13:59:16
@@ -0,0 +1,168 @@
+/* Taken from the reiserfs source code and hacked slightly by AK.
+ * This is GPLed.  */ 
+/*
+ * Keyed 32-bit hash function using TEA in a Davis-Meyer function
+ *   H0 = Key
+ *   Hi = E Mi(Hi-1) + Hi-1
+ *
+ * (see Applied Cryptography, 2nd edition, p448).
+ *
+ * Jeremy Fitzhardinge <jeremy@zip.com.au> 1998
+ * 
+ * Jeremy has agreed to the contents of reiserfs/README. -Hans
+ */
+
+#include <assert.h>
+
+#if 0 
+/* OK for Intel */
+typedef unsigned long u32;
+typedef const unsigned char u8;
+#else
+#include <inttypes.h>
+typedef uint32_t u32;
+typedef uint8_t u8;
+#endif
+
+
+#define DELTA 0x9E3779B9
+#define FULLROUNDS 10		/* 32 is overkill, 16 is strong crypto */
+#define PARTROUNDS 6		/* 6 gets complete mixing */
+
+/* a, b, c, d - data; h0, h1 - accumulated hash */
+#define TEACORE(rounds)							\
+	do {								\
+		u32 sum = 0;						\
+		int n = rounds;						\
+		u32 b0, b1;						\
+									\
+		b0 = h0;						\
+		b1 = h1;						\
+									\
+		do							\
+		{							\
+			sum += DELTA;					\
+			b0 += ((b1 << 4)+a) ^ (b1+sum) ^ ((b1 >> 5)+b);	\
+			b1 += ((b0 << 4)+c) ^ (b0+sum) ^ ((b0 >> 5)+d);	\
+		} while(--n);						\
+									\
+		h0 += b0;						\
+		h1 += b1;						\
+	} while(0)
+
+u32 teahash3(/*u32 k[2], *//*u8*/const char *msg, int len)
+{
+	u32 k[] = { 0x9464a485, 0x542e1a94, 0x3e846bff, 0xb75bcfc3}; 
+
+	u32 h0 = k[0], h1 = k[1];
+	u32 a, b, c, d;
+	u32 pad;
+	int i;
+ 
+	assert(len >= 0 && len < 256);
+
+	pad = (u32)len | ((u32)len << 8);
+	pad |= pad << 16;
+
+	while(len >= 16)
+	{
+		a = (u32)msg[ 0]      |
+		    (u32)msg[ 1] << 8 |
+		    (u32)msg[ 2] << 16|
+		    (u32)msg[ 3] << 24;
+		b = (u32)msg[ 4]      |
+		    (u32)msg[ 5] << 8 |
+		    (u32)msg[ 6] << 16|
+		    (u32)msg[ 7] << 24;
+		c = (u32)msg[ 8]      |
+		    (u32)msg[ 9] << 8 |
+		    (u32)msg[10] << 16|
+		    (u32)msg[11] << 24;
+		d = (u32)msg[12]      |
+		    (u32)msg[13] << 8 |
+		    (u32)msg[14] << 16|
+		    (u32)msg[15] << 24;
+		
+		TEACORE(PARTROUNDS);
+
+		len -= 16;
+		msg += 16;
+	}
+
+	if (len >= 12)
+	{
+		assert(len < 16);
+
+		a = (u32)msg[ 0]      |
+		    (u32)msg[ 1] << 8 |
+		    (u32)msg[ 2] << 16|
+		    (u32)msg[ 3] << 24;
+		b = (u32)msg[ 4]      |
+		    (u32)msg[ 5] << 8 |
+		    (u32)msg[ 6] << 16|
+		    (u32)msg[ 7] << 24;
+		c = (u32)msg[ 8]      |
+		    (u32)msg[ 9] << 8 |
+		    (u32)msg[10] << 16|
+		    (u32)msg[11] << 24;
+
+		d = pad;
+		for(i = 12; i < len; i++)
+		{
+			d <<= 8;
+			d |= msg[i];
+		}
+	}
+	else if (len >= 8)
+	{
+		assert(len < 12);
+
+		a = (u32)msg[ 0]      |
+		    (u32)msg[ 1] << 8 |
+		    (u32)msg[ 2] << 16|
+		    (u32)msg[ 3] << 24;
+		b = (u32)msg[ 4]      |
+		    (u32)msg[ 5] << 8 |
+		    (u32)msg[ 6] << 16|
+		    (u32)msg[ 7] << 24;
+
+		c = d = pad;
+		for(i = 8; i < len; i++)
+		{
+			c <<= 8;
+			c |= msg[i];
+		}
+	}
+	else if (len >= 4)
+	{
+		assert(len < 8);
+
+		a = (u32)msg[ 0]      |
+		    (u32)msg[ 1] << 8 |
+		    (u32)msg[ 2] << 16|
+		    (u32)msg[ 3] << 24;
+
+		b = c = d = pad;
+		for(i = 4; i < len; i++)
+		{
+			b <<= 8;
+			b |= msg[i];
+		}
+	}
+	else
+	{
+		assert(len < 4);
+
+		a = b = c = d = pad;
+		for(i = 0; i < len; i++)
+		{
+			a <<= 8;
+			a |= msg[i];
+		}
+	}
+
+	TEACORE(FULLROUNDS);
+
+/*	return 0;*/
+	return h0^h1;
+}
--- nfs-server/ugid_map.c
+++ nfs-server/ugid_map.c	2002/11/08 13:59:16
@@ -276,8 +276,10 @@
 	if ((gid == 0 && mountp->o.root_squash) || mountp->o.all_squash)
 		retgid = mountp->o.nobody_gid;
 
+#if 0
 	Dprintf(D_UGID, "lgid(%s, %d) = %d\n",
 			inet_ntoa(mountp->client->clnt_addr), gid, retgid);
+#endif
 	return retgid;
 }
 
