Linux-2.6.12-rc2

Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.

Let it rip!
diff --git a/fs/ncpfs/Kconfig b/fs/ncpfs/Kconfig
new file mode 100644
index 0000000..1428084
--- /dev/null
+++ b/fs/ncpfs/Kconfig
@@ -0,0 +1,87 @@
+#
+# NCP Filesystem configuration
+#
+config NCPFS_PACKET_SIGNING
+	bool "Packet signatures"
+	depends on NCP_FS
+	help
+	  NCP allows packets to be signed for stronger security. If you want
+	  security, say Y.  Normal users can leave it off.  To be able to use
+	  packet signing you must use ncpfs > 2.0.12.
+
+config NCPFS_IOCTL_LOCKING
+	bool "Proprietary file locking"
+	depends on NCP_FS
+	help
+	  Allows locking of records on remote volumes.  Say N unless you have
+	  special applications which are able to utilize this locking scheme.
+
+config NCPFS_STRONG
+	bool "Clear remove/delete inhibit when needed"
+	depends on NCP_FS
+	help
+	  Allows manipulation of files flagged as Delete or Rename Inhibit.
+	  To use this feature you must mount volumes with the ncpmount
+	  parameter "-s" (ncpfs-2.0.12 and newer).  Say Y unless you are not
+	  mounting volumes with -f 444.
+
+config NCPFS_NFS_NS
+	bool "Use NFS namespace if available"
+	depends on NCP_FS
+	help
+	  Allows you to utilize NFS namespace on NetWare servers.  It brings
+	  you case sensitive filenames.  Say Y.  You can disable it at
+	  mount-time with the `-N nfs' parameter of ncpmount.
+
+config NCPFS_OS2_NS
+	bool "Use LONG (OS/2) namespace if available"
+	depends on NCP_FS
+	help
+	  Allows you to utilize OS2/LONG namespace on NetWare servers.
+	  Filenames in this namespace are limited to 255 characters, they are
+	  case insensitive, and case in names is preserved.  Say Y.  You can
+	  disable it at mount time with the -N os2 parameter of ncpmount.
+
+config NCPFS_SMALLDOS
+	bool "Lowercase DOS filenames"
+	depends on NCP_FS
+	---help---
+	  If you say Y here, every filename on a NetWare server volume using
+	  the OS2/LONG namespace and created under DOS or on a volume using
+	  DOS namespace will be converted to lowercase characters.
+	  Saying N here will give you these filenames in uppercase.
+
+	  This is only a cosmetic option since the OS2/LONG namespace is case
+	  insensitive. The only major reason for this option is backward
+	  compatibility when moving from DOS to OS2/LONG namespace support.
+	  Long filenames (created by Win95) will not be affected.
+
+	  This option does not solve the problem that filenames appear
+	  differently under Linux and under Windows, since Windows does an
+	  additional conversions on the client side. You can achieve similar
+	  effects by saying Y to "Allow using of Native Language Support"
+	  below.
+
+config NCPFS_NLS
+	bool "Use Native Language Support"
+	depends on NCP_FS
+	select NLS
+	help
+	  Allows you to use codepages and I/O charsets for file name
+	  translation between the server file system and input/output. This
+	  may be useful, if you want to access the server with other operating
+	  systems, e.g. Windows 95. See also NLS for more Information.
+
+	  To select codepages and I/O charsets use ncpfs-2.2.0.13 or newer.
+
+config NCPFS_EXTRAS
+	bool "Enable symbolic links and execute flags"
+	depends on NCP_FS
+	help
+	  This enables the use of symbolic links and an execute permission
+	  bit on NCPFS. The file server need not have long name space or NFS
+	  name space loaded for these to work.
+
+	  To use the new attributes, it is recommended to use the flags
+	  '-f 600 -d 755' on the ncpmount command line.
+
diff --git a/fs/ncpfs/Makefile b/fs/ncpfs/Makefile
new file mode 100644
index 0000000..68ea095
--- /dev/null
+++ b/fs/ncpfs/Makefile
@@ -0,0 +1,16 @@
+#
+# Makefile for the linux ncp filesystem routines.
+#
+
+obj-$(CONFIG_NCP_FS) += ncpfs.o
+
+ncpfs-y      := dir.o file.o inode.o ioctl.o mmap.o ncplib_kernel.o sock.o \
+		ncpsign_kernel.o getopt.o
+
+ncpfs-$(CONFIG_NCPFS_EXTRAS)   += symlink.o
+ncpfs-$(CONFIG_NCPFS_NFS_NS)   += symlink.o
+
+# If you want debugging output, please uncomment the following line
+# EXTRA_CFLAGS += -DDEBUG_NCP=1
+
+CFLAGS_ncplib_kernel.o := -finline-functions
diff --git a/fs/ncpfs/dir.c b/fs/ncpfs/dir.c
new file mode 100644
index 0000000..2dc2d86
--- /dev/null
+++ b/fs/ncpfs/dir.c
@@ -0,0 +1,1260 @@
+/*
+ *  dir.c
+ *
+ *  Copyright (C) 1995, 1996 by Volker Lendecke
+ *  Modified for big endian by J.F. Chadima and David S. Miller
+ *  Modified 1997 Peter Waltenberg, Bill Hawes, David Woodhouse for 2.1 dcache
+ *  Modified 1998, 1999 Wolfram Pienkoss for NLS
+ *  Modified 1999 Wolfram Pienkoss for directory caching
+ *  Modified 2000 Ben Harris, University of Cambridge for NFS NS meta-info
+ *
+ */
+
+#include <linux/config.h>
+
+#include <linux/time.h>
+#include <linux/errno.h>
+#include <linux/stat.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/mm.h>
+#include <asm/uaccess.h>
+#include <asm/byteorder.h>
+#include <linux/smp_lock.h>
+
+#include <linux/ncp_fs.h>
+
+#include "ncplib_kernel.h"
+
+static void ncp_read_volume_list(struct file *, void *, filldir_t,
+				struct ncp_cache_control *);
+static void ncp_do_readdir(struct file *, void *, filldir_t,
+				struct ncp_cache_control *);
+
+static int ncp_readdir(struct file *, void *, filldir_t);
+
+static int ncp_create(struct inode *, struct dentry *, int, struct nameidata *);
+static struct dentry *ncp_lookup(struct inode *, struct dentry *, struct nameidata *);
+static int ncp_unlink(struct inode *, struct dentry *);
+static int ncp_mkdir(struct inode *, struct dentry *, int);
+static int ncp_rmdir(struct inode *, struct dentry *);
+static int ncp_rename(struct inode *, struct dentry *,
+	  	      struct inode *, struct dentry *);
+static int ncp_mknod(struct inode * dir, struct dentry *dentry,
+		     int mode, dev_t rdev);
+#if defined(CONFIG_NCPFS_EXTRAS) || defined(CONFIG_NCPFS_NFS_NS)
+extern int ncp_symlink(struct inode *, struct dentry *, const char *);
+#else
+#define ncp_symlink NULL
+#endif
+		      
+struct file_operations ncp_dir_operations =
+{
+	.read		= generic_read_dir,
+	.readdir	= ncp_readdir,
+	.ioctl		= ncp_ioctl,
+};
+
+struct inode_operations ncp_dir_inode_operations =
+{
+	.create		= ncp_create,
+	.lookup		= ncp_lookup,
+	.unlink		= ncp_unlink,
+	.symlink	= ncp_symlink,
+	.mkdir		= ncp_mkdir,
+	.rmdir		= ncp_rmdir,
+	.mknod		= ncp_mknod,
+	.rename		= ncp_rename,
+	.setattr	= ncp_notify_change,
+};
+
+/*
+ * Dentry operations routines
+ */
+static int ncp_lookup_validate(struct dentry *, struct nameidata *);
+static int ncp_hash_dentry(struct dentry *, struct qstr *);
+static int ncp_compare_dentry (struct dentry *, struct qstr *, struct qstr *);
+static int ncp_delete_dentry(struct dentry *);
+
+static struct dentry_operations ncp_dentry_operations =
+{
+	.d_revalidate	= ncp_lookup_validate,
+	.d_hash		= ncp_hash_dentry,
+	.d_compare	= ncp_compare_dentry,
+	.d_delete	= ncp_delete_dentry,
+};
+
+struct dentry_operations ncp_root_dentry_operations =
+{
+	.d_hash		= ncp_hash_dentry,
+	.d_compare	= ncp_compare_dentry,
+	.d_delete	= ncp_delete_dentry,
+};
+
+
+/*
+ * Note: leave the hash unchanged if the directory
+ * is case-sensitive.
+ */
+static int 
+ncp_hash_dentry(struct dentry *dentry, struct qstr *this)
+{
+	struct nls_table *t;
+	unsigned long hash;
+	int i;
+
+	t = NCP_IO_TABLE(dentry);
+
+	if (!ncp_case_sensitive(dentry->d_inode)) {
+		hash = init_name_hash();
+		for (i=0; i<this->len ; i++)
+			hash = partial_name_hash(ncp_tolower(t, this->name[i]),
+									hash);
+		this->hash = end_name_hash(hash);
+	}
+	return 0;
+}
+
+static int
+ncp_compare_dentry(struct dentry *dentry, struct qstr *a, struct qstr *b)
+{
+	if (a->len != b->len)
+		return 1;
+
+	if (ncp_case_sensitive(dentry->d_inode))
+		return strncmp(a->name, b->name, a->len);
+
+	return ncp_strnicmp(NCP_IO_TABLE(dentry), a->name, b->name, a->len);
+}
+
+/*
+ * This is the callback from dput() when d_count is going to 0.
+ * We use this to unhash dentries with bad inodes.
+ * Closing files can be safely postponed until iput() - it's done there anyway.
+ */
+static int
+ncp_delete_dentry(struct dentry * dentry)
+{
+	struct inode *inode = dentry->d_inode;
+
+	if (inode) {
+		if (is_bad_inode(inode))
+			return 1;
+	} else
+	{
+	/* N.B. Unhash negative dentries? */
+	}
+	return 0;
+}
+
+static inline int
+ncp_single_volume(struct ncp_server *server)
+{
+	return (server->m.mounted_vol[0] != '\0');
+}
+
+static inline int ncp_is_server_root(struct inode *inode)
+{
+	return (!ncp_single_volume(NCP_SERVER(inode)) &&
+		inode == inode->i_sb->s_root->d_inode);
+}
+
+
+/*
+ * This is the callback when the dcache has a lookup hit.
+ */
+
+
+#ifdef CONFIG_NCPFS_STRONG
+/* try to delete a readonly file (NW R bit set) */
+
+static int
+ncp_force_unlink(struct inode *dir, struct dentry* dentry)
+{
+        int res=0x9c,res2;
+	struct nw_modify_dos_info info;
+	__le32 old_nwattr;
+	struct inode *inode;
+
+	memset(&info, 0, sizeof(info));
+	
+        /* remove the Read-Only flag on the NW server */
+	inode = dentry->d_inode;
+
+	old_nwattr = NCP_FINFO(inode)->nwattr;
+	info.attributes = old_nwattr & ~(aRONLY|aDELETEINHIBIT|aRENAMEINHIBIT);
+	res2 = ncp_modify_file_or_subdir_dos_info_path(NCP_SERVER(inode), inode, NULL, DM_ATTRIBUTES, &info);
+	if (res2)
+		goto leave_me;
+
+        /* now try again the delete operation */
+        res = ncp_del_file_or_subdir2(NCP_SERVER(dir), dentry);
+
+        if (res)  /* delete failed, set R bit again */
+        {
+		info.attributes = old_nwattr;
+		res2 = ncp_modify_file_or_subdir_dos_info_path(NCP_SERVER(inode), inode, NULL, DM_ATTRIBUTES, &info);
+		if (res2)
+                        goto leave_me;
+        }
+leave_me:
+        return(res);
+}
+#endif	/* CONFIG_NCPFS_STRONG */
+
+#ifdef CONFIG_NCPFS_STRONG
+static int
+ncp_force_rename(struct inode *old_dir, struct dentry* old_dentry, char *_old_name,
+                 struct inode *new_dir, struct dentry* new_dentry, char *_new_name)
+{
+	struct nw_modify_dos_info info;
+        int res=0x90,res2;
+	struct inode *old_inode = old_dentry->d_inode;
+	__le32 old_nwattr = NCP_FINFO(old_inode)->nwattr;
+	__le32 new_nwattr = 0; /* shut compiler warning */
+	int old_nwattr_changed = 0;
+	int new_nwattr_changed = 0;
+
+	memset(&info, 0, sizeof(info));
+	
+        /* remove the Read-Only flag on the NW server */
+
+	info.attributes = old_nwattr & ~(aRONLY|aRENAMEINHIBIT|aDELETEINHIBIT);
+	res2 = ncp_modify_file_or_subdir_dos_info_path(NCP_SERVER(old_inode), old_inode, NULL, DM_ATTRIBUTES, &info);
+	if (!res2)
+		old_nwattr_changed = 1;
+	if (new_dentry && new_dentry->d_inode) {
+		new_nwattr = NCP_FINFO(new_dentry->d_inode)->nwattr;
+		info.attributes = new_nwattr & ~(aRONLY|aRENAMEINHIBIT|aDELETEINHIBIT);
+		res2 = ncp_modify_file_or_subdir_dos_info_path(NCP_SERVER(new_dir), new_dir, _new_name, DM_ATTRIBUTES, &info);
+		if (!res2)
+			new_nwattr_changed = 1;
+	}
+        /* now try again the rename operation */
+	/* but only if something really happened */
+	if (new_nwattr_changed || old_nwattr_changed) {
+	        res = ncp_ren_or_mov_file_or_subdir(NCP_SERVER(old_dir),
+        	                                    old_dir, _old_name,
+                	                            new_dir, _new_name);
+	} 
+	if (res)
+		goto leave_me;
+	/* file was successfully renamed, so:
+	   do not set attributes on old file - it no longer exists
+	   copy attributes from old file to new */
+	new_nwattr_changed = old_nwattr_changed;
+	new_nwattr = old_nwattr;
+	old_nwattr_changed = 0;
+	
+leave_me:;
+	if (old_nwattr_changed) {
+		info.attributes = old_nwattr;
+		res2 = ncp_modify_file_or_subdir_dos_info_path(NCP_SERVER(old_inode), old_inode, NULL, DM_ATTRIBUTES, &info);
+		/* ignore errors */
+	}
+	if (new_nwattr_changed)	{
+		info.attributes = new_nwattr;
+		res2 = ncp_modify_file_or_subdir_dos_info_path(NCP_SERVER(new_dir), new_dir, _new_name, DM_ATTRIBUTES, &info);
+		/* ignore errors */
+	}
+        return(res);
+}
+#endif	/* CONFIG_NCPFS_STRONG */
+
+
+static int
+__ncp_lookup_validate(struct dentry * dentry, struct nameidata *nd)
+{
+	struct ncp_server *server;
+	struct dentry *parent;
+	struct inode *dir;
+	struct ncp_entry_info finfo;
+	int res, val = 0, len;
+	__u8 __name[NCP_MAXPATHLEN + 1];
+
+	parent = dget_parent(dentry);
+	dir = parent->d_inode;
+
+	if (!dentry->d_inode)
+		goto finished;
+
+	server = NCP_SERVER(dir);
+
+	if (!ncp_conn_valid(server))
+		goto finished;
+
+	/*
+	 * Inspired by smbfs:
+	 * The default validation is based on dentry age:
+	 * We set the max age at mount time.  (But each
+	 * successful server lookup renews the timestamp.)
+	 */
+	val = NCP_TEST_AGE(server, dentry);
+	if (val)
+		goto finished;
+
+	DDPRINTK("ncp_lookup_validate: %s/%s not valid, age=%ld, server lookup\n",
+		dentry->d_parent->d_name.name, dentry->d_name.name,
+		NCP_GET_AGE(dentry));
+
+	len = sizeof(__name);
+	if (ncp_is_server_root(dir)) {
+		res = ncp_io2vol(server, __name, &len, dentry->d_name.name,
+				 dentry->d_name.len, 1);
+		if (!res)
+			res = ncp_lookup_volume(server, __name, &(finfo.i));
+	} else {
+		res = ncp_io2vol(server, __name, &len, dentry->d_name.name,
+				 dentry->d_name.len, !ncp_preserve_case(dir));
+		if (!res)
+			res = ncp_obtain_info(server, dir, __name, &(finfo.i));
+	}
+	finfo.volume = finfo.i.volNumber;
+	DDPRINTK("ncp_lookup_validate: looked for %s/%s, res=%d\n",
+		dentry->d_parent->d_name.name, __name, res);
+	/*
+	 * If we didn't find it, or if it has a different dirEntNum to
+	 * what we remember, it's not valid any more.
+	 */
+	if (!res) {
+		if (finfo.i.dirEntNum == NCP_FINFO(dentry->d_inode)->dirEntNum) {
+			ncp_new_dentry(dentry);
+			val=1;
+		} else
+			DDPRINTK("ncp_lookup_validate: found, but dirEntNum changed\n");
+
+		ncp_update_inode2(dentry->d_inode, &finfo);
+	}
+
+finished:
+	DDPRINTK("ncp_lookup_validate: result=%d\n", val);
+	dput(parent);
+	return val;
+}
+
+static int
+ncp_lookup_validate(struct dentry * dentry, struct nameidata *nd)
+{
+	int res;
+	lock_kernel();
+	res = __ncp_lookup_validate(dentry, nd);
+	unlock_kernel();
+	return res;
+}
+
+static struct dentry *
+ncp_dget_fpos(struct dentry *dentry, struct dentry *parent, unsigned long fpos)
+{
+	struct dentry *dent = dentry;
+	struct list_head *next;
+
+	if (d_validate(dent, parent)) {
+		if (dent->d_name.len <= NCP_MAXPATHLEN &&
+		    (unsigned long)dent->d_fsdata == fpos) {
+			if (!dent->d_inode) {
+				dput(dent);
+				dent = NULL;
+			}
+			return dent;
+		}
+		dput(dent);
+	}
+
+	/* If a pointer is invalid, we search the dentry. */
+	spin_lock(&dcache_lock);
+	next = parent->d_subdirs.next;
+	while (next != &parent->d_subdirs) {
+		dent = list_entry(next, struct dentry, d_child);
+		if ((unsigned long)dent->d_fsdata == fpos) {
+			if (dent->d_inode)
+				dget_locked(dent);
+			else
+				dent = NULL;
+			spin_unlock(&dcache_lock);
+			goto out;
+		}
+		next = next->next;
+	}
+	spin_unlock(&dcache_lock);
+	return NULL;
+
+out:
+	return dent;
+}
+
+static time_t ncp_obtain_mtime(struct dentry *dentry)
+{
+	struct inode *inode = dentry->d_inode;
+	struct ncp_server *server = NCP_SERVER(inode);
+	struct nw_info_struct i;
+
+	if (!ncp_conn_valid(server) || ncp_is_server_root(inode))
+		return 0;
+
+	if (ncp_obtain_info(server, inode, NULL, &i))
+		return 0;
+
+	return ncp_date_dos2unix(i.modifyTime, i.modifyDate);
+}
+
+static int ncp_readdir(struct file *filp, void *dirent, filldir_t filldir)
+{
+	struct dentry *dentry = filp->f_dentry;
+	struct inode *inode = dentry->d_inode;
+	struct page *page = NULL;
+	struct ncp_server *server = NCP_SERVER(inode);
+	union  ncp_dir_cache *cache = NULL;
+	struct ncp_cache_control ctl;
+	int result, mtime_valid = 0;
+	time_t mtime = 0;
+
+	lock_kernel();
+
+	ctl.page  = NULL;
+	ctl.cache = NULL;
+
+	DDPRINTK("ncp_readdir: reading %s/%s, pos=%d\n",
+		dentry->d_parent->d_name.name, dentry->d_name.name,
+		(int) filp->f_pos);
+
+	result = -EIO;
+	if (!ncp_conn_valid(server))
+		goto out;
+
+	result = 0;
+	if (filp->f_pos == 0) {
+		if (filldir(dirent, ".", 1, 0, inode->i_ino, DT_DIR))
+			goto out;
+		filp->f_pos = 1;
+	}
+	if (filp->f_pos == 1) {
+		if (filldir(dirent, "..", 2, 1, parent_ino(dentry), DT_DIR))
+			goto out;
+		filp->f_pos = 2;
+	}
+
+	page = grab_cache_page(&inode->i_data, 0);
+	if (!page)
+		goto read_really;
+
+	ctl.cache = cache = kmap(page);
+	ctl.head  = cache->head;
+
+	if (!PageUptodate(page) || !ctl.head.eof)
+		goto init_cache;
+
+	if (filp->f_pos == 2) {
+		if (jiffies - ctl.head.time >= NCP_MAX_AGE(server))
+			goto init_cache;
+
+		mtime = ncp_obtain_mtime(dentry);
+		mtime_valid = 1;
+		if ((!mtime) || (mtime != ctl.head.mtime))
+			goto init_cache;
+	}
+
+	if (filp->f_pos > ctl.head.end)
+		goto finished;
+
+	ctl.fpos = filp->f_pos + (NCP_DIRCACHE_START - 2);
+	ctl.ofs  = ctl.fpos / NCP_DIRCACHE_SIZE;
+	ctl.idx  = ctl.fpos % NCP_DIRCACHE_SIZE;
+
+	for (;;) {
+		if (ctl.ofs != 0) {
+			ctl.page = find_lock_page(&inode->i_data, ctl.ofs);
+			if (!ctl.page)
+				goto invalid_cache;
+			ctl.cache = kmap(ctl.page);
+			if (!PageUptodate(ctl.page))
+				goto invalid_cache;
+		}
+		while (ctl.idx < NCP_DIRCACHE_SIZE) {
+			struct dentry *dent;
+			int res;
+
+			dent = ncp_dget_fpos(ctl.cache->dentry[ctl.idx],
+						dentry, filp->f_pos);
+			if (!dent)
+				goto invalid_cache;
+			res = filldir(dirent, dent->d_name.name,
+					dent->d_name.len, filp->f_pos,
+					dent->d_inode->i_ino, DT_UNKNOWN);
+			dput(dent);
+			if (res)
+				goto finished;
+			filp->f_pos += 1;
+			ctl.idx += 1;
+			if (filp->f_pos > ctl.head.end)
+				goto finished;
+		}
+		if (ctl.page) {
+			kunmap(ctl.page);
+			SetPageUptodate(ctl.page);
+			unlock_page(ctl.page);
+			page_cache_release(ctl.page);
+			ctl.page = NULL;
+		}
+		ctl.idx  = 0;
+		ctl.ofs += 1;
+	}
+invalid_cache:
+	if (ctl.page) {
+		kunmap(ctl.page);
+		unlock_page(ctl.page);
+		page_cache_release(ctl.page);
+		ctl.page = NULL;
+	}
+	ctl.cache = cache;
+init_cache:
+	ncp_invalidate_dircache_entries(dentry);
+	if (!mtime_valid) {
+		mtime = ncp_obtain_mtime(dentry);
+		mtime_valid = 1;
+	}
+	ctl.head.mtime = mtime;
+	ctl.head.time = jiffies;
+	ctl.head.eof = 0;
+	ctl.fpos = 2;
+	ctl.ofs = 0;
+	ctl.idx = NCP_DIRCACHE_START;
+	ctl.filled = 0;
+	ctl.valid  = 1;
+read_really:
+	if (ncp_is_server_root(inode)) {
+		ncp_read_volume_list(filp, dirent, filldir, &ctl);
+	} else {
+		ncp_do_readdir(filp, dirent, filldir, &ctl);
+	}
+	ctl.head.end = ctl.fpos - 1;
+	ctl.head.eof = ctl.valid;
+finished:
+	if (page) {
+		cache->head = ctl.head;
+		kunmap(page);
+		SetPageUptodate(page);
+		unlock_page(page);
+		page_cache_release(page);
+	}
+	if (ctl.page) {
+		kunmap(ctl.page);
+		SetPageUptodate(ctl.page);
+		unlock_page(ctl.page);
+		page_cache_release(ctl.page);
+	}
+out:
+	unlock_kernel();
+	return result;
+}
+
+static int
+ncp_fill_cache(struct file *filp, void *dirent, filldir_t filldir,
+		struct ncp_cache_control *ctrl, struct ncp_entry_info *entry)
+{
+	struct dentry *newdent, *dentry = filp->f_dentry;
+	struct inode *newino, *inode = dentry->d_inode;
+	struct ncp_cache_control ctl = *ctrl;
+	struct qstr qname;
+	int valid = 0;
+	int hashed = 0;
+	ino_t ino = 0;
+	__u8 __name[NCP_MAXPATHLEN + 1];
+
+	qname.len = sizeof(__name);
+	if (ncp_vol2io(NCP_SERVER(inode), __name, &qname.len,
+			entry->i.entryName, entry->i.nameLen,
+			!ncp_preserve_entry_case(inode, entry->i.NSCreator)))
+		return 1; /* I'm not sure */
+
+	qname.name = __name;
+	qname.hash = full_name_hash(qname.name, qname.len);
+
+	if (dentry->d_op && dentry->d_op->d_hash)
+		if (dentry->d_op->d_hash(dentry, &qname) != 0)
+			goto end_advance;
+
+	newdent = d_lookup(dentry, &qname);
+
+	if (!newdent) {
+		newdent = d_alloc(dentry, &qname);
+		if (!newdent)
+			goto end_advance;
+	} else {
+		hashed = 1;
+		memcpy((char *) newdent->d_name.name, qname.name,
+							newdent->d_name.len);
+	}
+
+	if (!newdent->d_inode) {
+		entry->opened = 0;
+		entry->ino = iunique(inode->i_sb, 2);
+		newino = ncp_iget(inode->i_sb, entry);
+		if (newino) {
+			newdent->d_op = &ncp_dentry_operations;
+			d_instantiate(newdent, newino);
+			if (!hashed)
+				d_rehash(newdent);
+		}
+	} else
+		ncp_update_inode2(newdent->d_inode, entry);
+
+	if (newdent->d_inode) {
+		ino = newdent->d_inode->i_ino;
+		newdent->d_fsdata = (void *) ctl.fpos;
+		ncp_new_dentry(newdent);
+	}
+
+	if (ctl.idx >= NCP_DIRCACHE_SIZE) {
+		if (ctl.page) {
+			kunmap(ctl.page);
+			SetPageUptodate(ctl.page);
+			unlock_page(ctl.page);
+			page_cache_release(ctl.page);
+		}
+		ctl.cache = NULL;
+		ctl.idx  -= NCP_DIRCACHE_SIZE;
+		ctl.ofs  += 1;
+		ctl.page  = grab_cache_page(&inode->i_data, ctl.ofs);
+		if (ctl.page)
+			ctl.cache = kmap(ctl.page);
+	}
+	if (ctl.cache) {
+		ctl.cache->dentry[ctl.idx] = newdent;
+		valid = 1;
+	}
+	dput(newdent);
+end_advance:
+	if (!valid)
+		ctl.valid = 0;
+	if (!ctl.filled && (ctl.fpos == filp->f_pos)) {
+		if (!ino)
+			ino = find_inode_number(dentry, &qname);
+		if (!ino)
+			ino = iunique(inode->i_sb, 2);
+		ctl.filled = filldir(dirent, qname.name, qname.len,
+				     filp->f_pos, ino, DT_UNKNOWN);
+		if (!ctl.filled)
+			filp->f_pos += 1;
+	}
+	ctl.fpos += 1;
+	ctl.idx  += 1;
+	*ctrl = ctl;
+	return (ctl.valid || !ctl.filled);
+}
+
+static void
+ncp_read_volume_list(struct file *filp, void *dirent, filldir_t filldir,
+			struct ncp_cache_control *ctl)
+{
+	struct dentry *dentry = filp->f_dentry;
+	struct inode *inode = dentry->d_inode;
+	struct ncp_server *server = NCP_SERVER(inode);
+	struct ncp_volume_info info;
+	struct ncp_entry_info entry;
+	int i;
+
+	DPRINTK("ncp_read_volume_list: pos=%ld\n",
+			(unsigned long) filp->f_pos);
+
+	for (i = 0; i < NCP_NUMBER_OF_VOLUMES; i++) {
+
+		if (ncp_get_volume_info_with_number(server, i, &info) != 0)
+			return;
+		if (!strlen(info.volume_name))
+			continue;
+
+		DPRINTK("ncp_read_volume_list: found vol: %s\n",
+			info.volume_name);
+
+		if (ncp_lookup_volume(server, info.volume_name,
+					&entry.i)) {
+			DPRINTK("ncpfs: could not lookup vol %s\n",
+				info.volume_name);
+			continue;
+		}
+		entry.volume = entry.i.volNumber;
+		if (!ncp_fill_cache(filp, dirent, filldir, ctl, &entry))
+			return;
+	}
+}
+
+static void
+ncp_do_readdir(struct file *filp, void *dirent, filldir_t filldir,
+						struct ncp_cache_control *ctl)
+{
+	struct dentry *dentry = filp->f_dentry;
+	struct inode *dir = dentry->d_inode;
+	struct ncp_server *server = NCP_SERVER(dir);
+	struct nw_search_sequence seq;
+	struct ncp_entry_info entry;
+	int err;
+	void* buf;
+	int more;
+	size_t bufsize;
+
+	DPRINTK("ncp_do_readdir: %s/%s, fpos=%ld\n",
+		dentry->d_parent->d_name.name, dentry->d_name.name,
+		(unsigned long) filp->f_pos);
+	PPRINTK("ncp_do_readdir: init %s, volnum=%d, dirent=%u\n",
+		dentry->d_name.name, NCP_FINFO(dir)->volNumber,
+		NCP_FINFO(dir)->dirEntNum);
+
+	err = ncp_initialize_search(server, dir, &seq);
+	if (err) {
+		DPRINTK("ncp_do_readdir: init failed, err=%d\n", err);
+		return;
+	}
+#ifdef USE_OLD_SLOW_DIRECTORY_LISTING
+	for (;;) {
+		err = ncp_search_for_file_or_subdir(server, &seq, &entry.i);
+		if (err) {
+			DPRINTK("ncp_do_readdir: search failed, err=%d\n", err);
+			break;
+		}
+		entry.volume = entry.i.volNumber;
+		if (!ncp_fill_cache(filp, dirent, filldir, ctl, &entry))
+			break;
+	}
+#else
+	/* We MUST NOT use server->buffer_size handshaked with server if we are
+	   using UDP, as for UDP server uses max. buffer size determined by
+	   MTU, and for TCP server uses hardwired value 65KB (== 66560 bytes). 
+	   So we use 128KB, just to be sure, as there is no way how to know
+	   this value in advance. */
+	bufsize = 131072;
+	buf = vmalloc(bufsize);
+	if (!buf)
+		return;
+	do {
+		int cnt;
+		char* rpl;
+		size_t rpls;
+
+		err = ncp_search_for_fileset(server, &seq, &more, &cnt, buf, bufsize, &rpl, &rpls);
+		if (err)		/* Error */
+			break;
+		if (!cnt)		/* prevent endless loop */
+			break;
+		while (cnt--) {
+			size_t onerpl;
+			
+			if (rpls < offsetof(struct nw_info_struct, entryName))
+				break;	/* short packet */
+			ncp_extract_file_info(rpl, &entry.i);
+			onerpl = offsetof(struct nw_info_struct, entryName) + entry.i.nameLen;
+			if (rpls < onerpl)
+				break;	/* short packet */
+			(void)ncp_obtain_nfs_info(server, &entry.i);
+			rpl += onerpl;
+			rpls -= onerpl;
+			entry.volume = entry.i.volNumber;
+			if (!ncp_fill_cache(filp, dirent, filldir, ctl, &entry))
+				break;
+		}
+	} while (more);
+	vfree(buf);
+#endif
+	return;
+}
+
+int ncp_conn_logged_in(struct super_block *sb)
+{
+	struct ncp_server* server = NCP_SBP(sb);
+	int result;
+
+	if (ncp_single_volume(server)) {
+		int len;
+		struct dentry* dent;
+		__u32 volNumber;
+		__le32 dirEntNum;
+		__le32 DosDirNum;
+		__u8 __name[NCP_MAXPATHLEN + 1];
+
+		len = sizeof(__name);
+		result = ncp_io2vol(server, __name, &len, server->m.mounted_vol,
+				    strlen(server->m.mounted_vol), 1);
+		if (result)
+			goto out;
+		result = -ENOENT;
+		if (ncp_get_volume_root(server, __name, &volNumber, &dirEntNum, &DosDirNum)) {
+			PPRINTK("ncp_conn_logged_in: %s not found\n",
+				server->m.mounted_vol);
+			goto out;
+		}
+		dent = sb->s_root;
+		if (dent) {
+			struct inode* ino = dent->d_inode;
+			if (ino) {
+				NCP_FINFO(ino)->volNumber = volNumber;
+				NCP_FINFO(ino)->dirEntNum = dirEntNum;
+				NCP_FINFO(ino)->DosDirNum = DosDirNum;
+			} else {
+				DPRINTK("ncpfs: sb->s_root->d_inode == NULL!\n");
+			}
+		} else {
+			DPRINTK("ncpfs: sb->s_root == NULL!\n");
+		}
+	}
+	result = 0;
+
+out:
+	return result;
+}
+
+static struct dentry *ncp_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd)
+{
+	struct ncp_server *server = NCP_SERVER(dir);
+	struct inode *inode = NULL;
+	struct ncp_entry_info finfo;
+	int error, res, len;
+	__u8 __name[NCP_MAXPATHLEN + 1];
+
+	lock_kernel();
+	error = -EIO;
+	if (!ncp_conn_valid(server))
+		goto finished;
+
+	PPRINTK("ncp_lookup: server lookup for %s/%s\n",
+		dentry->d_parent->d_name.name, dentry->d_name.name);
+
+	len = sizeof(__name);
+	if (ncp_is_server_root(dir)) {
+		res = ncp_io2vol(server, __name, &len, dentry->d_name.name,
+				 dentry->d_name.len, 1);
+		if (!res)
+			res = ncp_lookup_volume(server, __name, &(finfo.i));
+	} else {
+		res = ncp_io2vol(server, __name, &len, dentry->d_name.name,
+				 dentry->d_name.len, !ncp_preserve_case(dir));
+		if (!res)
+			res = ncp_obtain_info(server, dir, __name, &(finfo.i));
+	}
+	PPRINTK("ncp_lookup: looked for %s/%s, res=%d\n",
+		dentry->d_parent->d_name.name, __name, res);
+	/*
+	 * If we didn't find an entry, make a negative dentry.
+	 */
+	if (res)
+		goto add_entry;
+
+	/*
+	 * Create an inode for the entry.
+	 */
+	finfo.opened = 0;
+	finfo.ino = iunique(dir->i_sb, 2);
+	finfo.volume = finfo.i.volNumber;
+	error = -EACCES;
+	inode = ncp_iget(dir->i_sb, &finfo);
+
+	if (inode) {
+		ncp_new_dentry(dentry);
+add_entry:
+		dentry->d_op = &ncp_dentry_operations;
+		d_add(dentry, inode);
+		error = 0;
+	}
+
+finished:
+	PPRINTK("ncp_lookup: result=%d\n", error);
+	unlock_kernel();
+	return ERR_PTR(error);
+}
+
+/*
+ * This code is common to create, mkdir, and mknod.
+ */
+static int ncp_instantiate(struct inode *dir, struct dentry *dentry,
+			struct ncp_entry_info *finfo)
+{
+	struct inode *inode;
+	int error = -EINVAL;
+
+	finfo->ino = iunique(dir->i_sb, 2);
+	inode = ncp_iget(dir->i_sb, finfo);
+	if (!inode)
+		goto out_close;
+	d_instantiate(dentry,inode);
+	error = 0;
+out:
+	return error;
+
+out_close:
+	PPRINTK("ncp_instantiate: %s/%s failed, closing file\n",
+		dentry->d_parent->d_name.name, dentry->d_name.name);
+	ncp_close_file(NCP_SERVER(dir), finfo->file_handle);
+	goto out;
+}
+
+int ncp_create_new(struct inode *dir, struct dentry *dentry, int mode,
+		   dev_t rdev, __le32 attributes)
+{
+	struct ncp_server *server = NCP_SERVER(dir);
+	struct ncp_entry_info finfo;
+	int error, result, len;
+	int opmode;
+	__u8 __name[NCP_MAXPATHLEN + 1];
+	
+	PPRINTK("ncp_create_new: creating %s/%s, mode=%x\n",
+		dentry->d_parent->d_name.name, dentry->d_name.name, mode);
+
+	error = -EIO;
+	lock_kernel();
+	if (!ncp_conn_valid(server))
+		goto out;
+
+	ncp_age_dentry(server, dentry);
+	len = sizeof(__name);
+	error = ncp_io2vol(server, __name, &len, dentry->d_name.name,
+			   dentry->d_name.len, !ncp_preserve_case(dir));
+	if (error)
+		goto out;
+
+	error = -EACCES;
+	
+	if (S_ISREG(mode) && 
+	    (server->m.flags & NCP_MOUNT_EXTRAS) && 
+	    (mode & S_IXUGO))
+		attributes |= aSYSTEM | aSHARED;
+	
+	result = ncp_open_create_file_or_subdir(server, dir, __name,
+				OC_MODE_CREATE | OC_MODE_OPEN | OC_MODE_REPLACE,
+				attributes, AR_READ | AR_WRITE, &finfo);
+	opmode = O_RDWR;
+	if (result) {
+		result = ncp_open_create_file_or_subdir(server, dir, __name,
+				OC_MODE_CREATE | OC_MODE_OPEN | OC_MODE_REPLACE,
+				attributes, AR_WRITE, &finfo);
+		if (result) {
+			if (result == 0x87)
+				error = -ENAMETOOLONG;
+			DPRINTK("ncp_create: %s/%s failed\n",
+				dentry->d_parent->d_name.name, dentry->d_name.name);
+			goto out;
+		}
+		opmode = O_WRONLY;
+	}
+	finfo.access = opmode;
+	if (ncp_is_nfs_extras(server, finfo.volume)) {
+		finfo.i.nfs.mode = mode;
+		finfo.i.nfs.rdev = new_encode_dev(rdev);
+		if (ncp_modify_nfs_info(server, finfo.volume,
+					finfo.i.dirEntNum,
+					mode, new_encode_dev(rdev)) != 0)
+			goto out;
+	}
+
+	error = ncp_instantiate(dir, dentry, &finfo);
+out:
+	unlock_kernel();
+	return error;
+}
+
+static int ncp_create(struct inode *dir, struct dentry *dentry, int mode,
+		struct nameidata *nd)
+{
+	return ncp_create_new(dir, dentry, mode, 0, 0);
+}
+
+static int ncp_mkdir(struct inode *dir, struct dentry *dentry, int mode)
+{
+	struct ncp_entry_info finfo;
+	struct ncp_server *server = NCP_SERVER(dir);
+	int error, len;
+	__u8 __name[NCP_MAXPATHLEN + 1];
+
+	DPRINTK("ncp_mkdir: making %s/%s\n",
+		dentry->d_parent->d_name.name, dentry->d_name.name);
+
+	error = -EIO;
+	lock_kernel();
+	if (!ncp_conn_valid(server))
+		goto out;
+
+	ncp_age_dentry(server, dentry);
+	len = sizeof(__name);
+	error = ncp_io2vol(server, __name, &len, dentry->d_name.name,
+			   dentry->d_name.len, !ncp_preserve_case(dir));
+	if (error)
+		goto out;
+
+	error = -EACCES;
+	if (ncp_open_create_file_or_subdir(server, dir, __name,
+					   OC_MODE_CREATE, aDIR,
+					   cpu_to_le16(0xffff),
+					   &finfo) == 0)
+	{
+		if (ncp_is_nfs_extras(server, finfo.volume)) {
+			mode |= S_IFDIR;
+			finfo.i.nfs.mode = mode;
+			if (ncp_modify_nfs_info(server,
+						finfo.volume,
+						finfo.i.dirEntNum,
+						mode, 0) != 0)
+				goto out;
+		}
+		error = ncp_instantiate(dir, dentry, &finfo);
+	}
+out:
+	unlock_kernel();
+	return error;
+}
+
+static int ncp_rmdir(struct inode *dir, struct dentry *dentry)
+{
+	struct ncp_server *server = NCP_SERVER(dir);
+	int error, result, len;
+	__u8 __name[NCP_MAXPATHLEN + 1];
+
+	DPRINTK("ncp_rmdir: removing %s/%s\n",
+		dentry->d_parent->d_name.name, dentry->d_name.name);
+
+	error = -EIO;
+	lock_kernel();
+	if (!ncp_conn_valid(server))
+		goto out;
+
+	error = -EBUSY;
+	if (!d_unhashed(dentry))
+		goto out;
+
+	len = sizeof(__name);
+	error = ncp_io2vol(server, __name, &len, dentry->d_name.name,
+			   dentry->d_name.len, !ncp_preserve_case(dir));
+	if (error)
+		goto out;
+
+	result = ncp_del_file_or_subdir(server, dir, __name);
+	switch (result) {
+		case 0x00:
+			error = 0;
+			break;
+		case 0x85:	/* unauthorized to delete file */
+		case 0x8A:	/* unauthorized to delete file */
+			error = -EACCES;
+			break;
+		case 0x8F:
+		case 0x90:	/* read only */
+			error = -EPERM;
+			break;
+		case 0x9F:	/* in use by another client */
+			error = -EBUSY;
+			break;
+		case 0xA0:	/* directory not empty */
+			error = -ENOTEMPTY;
+			break;
+		case 0xFF:	/* someone deleted file */
+			error = -ENOENT;
+			break;
+		default:
+			error = -EACCES;
+			break;
+       	}
+out:
+	unlock_kernel();
+	return error;
+}
+
+static int ncp_unlink(struct inode *dir, struct dentry *dentry)
+{
+	struct inode *inode = dentry->d_inode;
+	struct ncp_server *server;
+	int error;
+
+	lock_kernel();
+	server = NCP_SERVER(dir);
+	DPRINTK("ncp_unlink: unlinking %s/%s\n",
+		dentry->d_parent->d_name.name, dentry->d_name.name);
+	
+	error = -EIO;
+	if (!ncp_conn_valid(server))
+		goto out;
+
+	/*
+	 * Check whether to close the file ...
+	 */
+	if (inode) {
+		PPRINTK("ncp_unlink: closing file\n");
+		ncp_make_closed(inode);
+	}
+
+	error = ncp_del_file_or_subdir2(server, dentry);
+#ifdef CONFIG_NCPFS_STRONG
+	/* 9C is Invalid path.. It should be 8F, 90 - read only, but
+	   it is not :-( */
+	if ((error == 0x9C || error == 0x90) && server->m.flags & NCP_MOUNT_STRONG) { /* R/O */
+		error = ncp_force_unlink(dir, dentry);
+	}
+#endif
+	switch (error) {
+		case 0x00:
+			DPRINTK("ncp: removed %s/%s\n",
+				dentry->d_parent->d_name.name, dentry->d_name.name);
+			break;
+		case 0x85:
+		case 0x8A:
+			error = -EACCES;
+			break;
+		case 0x8D:	/* some files in use */
+		case 0x8E:	/* all files in use */
+			error = -EBUSY;
+			break;
+		case 0x8F:	/* some read only */
+		case 0x90:	/* all read only */
+		case 0x9C:	/* !!! returned when in-use or read-only by NW4 */
+			error = -EPERM;
+			break;
+		case 0xFF:
+			error = -ENOENT;
+			break;
+		default:
+			error = -EACCES;
+			break;
+	}
+		
+out:
+	unlock_kernel();
+	return error;
+}
+
+static int ncp_rename(struct inode *old_dir, struct dentry *old_dentry,
+		      struct inode *new_dir, struct dentry *new_dentry)
+{
+	struct ncp_server *server = NCP_SERVER(old_dir);
+	int error;
+	int old_len, new_len;
+	__u8 __old_name[NCP_MAXPATHLEN + 1], __new_name[NCP_MAXPATHLEN + 1];
+
+	DPRINTK("ncp_rename: %s/%s to %s/%s\n",
+		old_dentry->d_parent->d_name.name, old_dentry->d_name.name,
+		new_dentry->d_parent->d_name.name, new_dentry->d_name.name);
+
+	error = -EIO;
+	lock_kernel();
+	if (!ncp_conn_valid(server))
+		goto out;
+
+	ncp_age_dentry(server, old_dentry);
+	ncp_age_dentry(server, new_dentry);
+
+	old_len = sizeof(__old_name);
+	error = ncp_io2vol(server, __old_name, &old_len,
+			   old_dentry->d_name.name, old_dentry->d_name.len,
+			   !ncp_preserve_case(old_dir));
+	if (error)
+		goto out;
+
+	new_len = sizeof(__new_name);
+	error = ncp_io2vol(server, __new_name, &new_len,
+			   new_dentry->d_name.name, new_dentry->d_name.len,
+			   !ncp_preserve_case(new_dir));
+	if (error)
+		goto out;
+
+	error = ncp_ren_or_mov_file_or_subdir(server, old_dir, __old_name,
+						      new_dir, __new_name);
+#ifdef CONFIG_NCPFS_STRONG
+	if ((error == 0x90 || error == 0x8B || error == -EACCES) &&
+			server->m.flags & NCP_MOUNT_STRONG) {	/* RO */
+		error = ncp_force_rename(old_dir, old_dentry, __old_name,
+					 new_dir, new_dentry, __new_name);
+	}
+#endif
+	switch (error) {
+		case 0x00:
+               	        DPRINTK("ncp renamed %s -> %s.\n",
+                                old_dentry->d_name.name,new_dentry->d_name.name);
+			break;
+		case 0x9E:
+			error = -ENAMETOOLONG;
+			break;
+		case 0xFF:
+			error = -ENOENT;
+			break;
+		default:
+			error = -EACCES;
+			break;
+	}
+out:
+	unlock_kernel();
+	return error;
+}
+
+static int ncp_mknod(struct inode * dir, struct dentry *dentry,
+		     int mode, dev_t rdev)
+{
+	if (!new_valid_dev(rdev))
+		return -EINVAL;
+	if (ncp_is_nfs_extras(NCP_SERVER(dir), NCP_FINFO(dir)->volNumber)) {
+		DPRINTK(KERN_DEBUG "ncp_mknod: mode = 0%o\n", mode);
+		return ncp_create_new(dir, dentry, mode, rdev, 0);
+	}
+	return -EPERM; /* Strange, but true */
+}
+
+/* The following routines are taken directly from msdos-fs */
+
+/* Linear day numbers of the respective 1sts in non-leap years. */
+
+static int day_n[] =
+{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 0, 0, 0, 0};
+/* Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec */
+
+
+extern struct timezone sys_tz;
+
+static int utc2local(int time)
+{
+	return time - sys_tz.tz_minuteswest * 60;
+}
+
+static int local2utc(int time)
+{
+	return time + sys_tz.tz_minuteswest * 60;
+}
+
+/* Convert a MS-DOS time/date pair to a UNIX date (seconds since 1 1 70). */
+int
+ncp_date_dos2unix(__le16 t, __le16 d)
+{
+	unsigned short time = le16_to_cpu(t), date = le16_to_cpu(d);
+	int month, year, secs;
+
+	/* first subtract and mask after that... Otherwise, if
+	   date == 0, bad things happen */
+	month = ((date >> 5) - 1) & 15;
+	year = date >> 9;
+	secs = (time & 31) * 2 + 60 * ((time >> 5) & 63) + (time >> 11) * 3600 +
+		86400 * ((date & 31) - 1 + day_n[month] + (year / 4) + 
+		year * 365 - ((year & 3) == 0 && month < 2 ? 1 : 0) + 3653);
+	/* days since 1.1.70 plus 80's leap day */
+	return local2utc(secs);
+}
+
+
+/* Convert linear UNIX date to a MS-DOS time/date pair. */
+void
+ncp_date_unix2dos(int unix_date, __le16 *time, __le16 *date)
+{
+	int day, year, nl_day, month;
+
+	unix_date = utc2local(unix_date);
+	*time = cpu_to_le16(
+		(unix_date % 60) / 2 + (((unix_date / 60) % 60) << 5) +
+		(((unix_date / 3600) % 24) << 11));
+	day = unix_date / 86400 - 3652;
+	year = day / 365;
+	if ((year + 3) / 4 + 365 * year > day)
+		year--;
+	day -= (year + 3) / 4 + 365 * year;
+	if (day == 59 && !(year & 3)) {
+		nl_day = day;
+		month = 2;
+	} else {
+		nl_day = (year & 3) || day <= 59 ? day : day - 1;
+		for (month = 0; month < 12; month++)
+			if (day_n[month] > nl_day)
+				break;
+	}
+	*date = cpu_to_le16(nl_day - day_n[month - 1] + 1 + (month << 5) + (year << 9));
+}
diff --git a/fs/ncpfs/file.c b/fs/ncpfs/file.c
new file mode 100644
index 0000000..4947d9b
--- /dev/null
+++ b/fs/ncpfs/file.c
@@ -0,0 +1,300 @@
+/*
+ *  file.c
+ *
+ *  Copyright (C) 1995, 1996 by Volker Lendecke
+ *  Modified 1997 Peter Waltenberg, Bill Hawes, David Woodhouse for 2.1 dcache
+ *
+ */
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#include <linux/time.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/fcntl.h>
+#include <linux/stat.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/smp_lock.h>
+
+#include <linux/ncp_fs.h>
+#include "ncplib_kernel.h"
+
+static int ncp_fsync(struct file *file, struct dentry *dentry, int datasync)
+{
+	return 0;
+}
+
+/*
+ * Open a file with the specified read/write mode.
+ */
+int ncp_make_open(struct inode *inode, int right)
+{
+	int error;
+	int access;
+
+	error = -EINVAL;
+	if (!inode) {
+		printk(KERN_ERR "ncp_make_open: got NULL inode\n");
+		goto out;
+	}
+
+	DPRINTK("ncp_make_open: opened=%d, volume # %u, dir entry # %u\n",
+		atomic_read(&NCP_FINFO(inode)->opened), 
+		NCP_FINFO(inode)->volNumber, 
+		NCP_FINFO(inode)->dirEntNum);
+	error = -EACCES;
+	down(&NCP_FINFO(inode)->open_sem);
+	if (!atomic_read(&NCP_FINFO(inode)->opened)) {
+		struct ncp_entry_info finfo;
+		int result;
+
+		/* tries max. rights */
+		finfo.access = O_RDWR;
+		result = ncp_open_create_file_or_subdir(NCP_SERVER(inode),
+					inode, NULL, OC_MODE_OPEN,
+					0, AR_READ | AR_WRITE, &finfo);
+		if (!result)
+			goto update;
+		/* RDWR did not succeeded, try readonly or writeonly as requested */
+		switch (right) {
+			case O_RDONLY:
+				finfo.access = O_RDONLY;
+				result = ncp_open_create_file_or_subdir(NCP_SERVER(inode),
+					inode, NULL, OC_MODE_OPEN,
+					0, AR_READ, &finfo);
+				break;
+			case O_WRONLY:
+				finfo.access = O_WRONLY;
+				result = ncp_open_create_file_or_subdir(NCP_SERVER(inode),
+					inode, NULL, OC_MODE_OPEN,
+					0, AR_WRITE, &finfo);
+				break;
+		}
+		if (result) {
+			PPRINTK("ncp_make_open: failed, result=%d\n", result);
+			goto out_unlock;
+		}
+		/*
+		 * Update the inode information.
+		 */
+	update:
+		ncp_update_inode(inode, &finfo);
+		atomic_set(&NCP_FINFO(inode)->opened, 1);
+	}
+
+	access = NCP_FINFO(inode)->access;
+	PPRINTK("ncp_make_open: file open, access=%x\n", access);
+	if (access == right || access == O_RDWR) {
+		atomic_inc(&NCP_FINFO(inode)->opened);
+		error = 0;
+	}
+
+out_unlock:
+	up(&NCP_FINFO(inode)->open_sem);
+out:
+	return error;
+}
+
+static ssize_t
+ncp_file_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
+{
+	struct dentry *dentry = file->f_dentry;
+	struct inode *inode = dentry->d_inode;
+	size_t already_read = 0;
+	off_t pos;
+	size_t bufsize;
+	int error;
+	void* freepage;
+	size_t freelen;
+
+	DPRINTK("ncp_file_read: enter %s/%s\n",
+		dentry->d_parent->d_name.name, dentry->d_name.name);
+
+	if (!ncp_conn_valid(NCP_SERVER(inode)))
+		return -EIO;
+
+	pos = *ppos;
+
+	if ((ssize_t) count < 0) {
+		return -EINVAL;
+	}
+	if (!count)
+		return 0;
+	if (pos > inode->i_sb->s_maxbytes)
+		return 0;
+	if (pos + count > inode->i_sb->s_maxbytes) {
+		count = inode->i_sb->s_maxbytes - pos;
+	}
+
+	error = ncp_make_open(inode, O_RDONLY);
+	if (error) {
+		DPRINTK(KERN_ERR "ncp_file_read: open failed, error=%d\n", error);
+		return error;
+	}
+
+	bufsize = NCP_SERVER(inode)->buffer_size;
+
+	error = -EIO;
+	freelen = ncp_read_bounce_size(bufsize);
+	freepage = vmalloc(freelen);
+	if (!freepage)
+		goto outrel;
+	error = 0;
+	/* First read in as much as possible for each bufsize. */
+	while (already_read < count) {
+		int read_this_time;
+		size_t to_read = min_t(unsigned int,
+				     bufsize - (pos % bufsize),
+				     count - already_read);
+
+		error = ncp_read_bounce(NCP_SERVER(inode),
+			 	NCP_FINFO(inode)->file_handle,
+				pos, to_read, buf, &read_this_time, 
+				freepage, freelen);
+		if (error) {
+			error = -EIO;	/* NW errno -> Linux errno */
+			break;
+		}
+		pos += read_this_time;
+		buf += read_this_time;
+		already_read += read_this_time;
+
+		if (read_this_time != to_read) {
+			break;
+		}
+	}
+	vfree(freepage);
+
+	*ppos = pos;
+
+	file_accessed(file);
+
+	DPRINTK("ncp_file_read: exit %s/%s\n",
+		dentry->d_parent->d_name.name, dentry->d_name.name);
+outrel:
+	ncp_inode_close(inode);		
+	return already_read ? already_read : error;
+}
+
+static ssize_t
+ncp_file_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+	struct dentry *dentry = file->f_dentry;
+	struct inode *inode = dentry->d_inode;
+	size_t already_written = 0;
+	off_t pos;
+	size_t bufsize;
+	int errno;
+	void* bouncebuffer;
+
+	DPRINTK("ncp_file_write: enter %s/%s\n",
+		dentry->d_parent->d_name.name, dentry->d_name.name);
+	if (!ncp_conn_valid(NCP_SERVER(inode)))
+		return -EIO;
+	if ((ssize_t) count < 0)
+		return -EINVAL;
+	pos = *ppos;
+	if (file->f_flags & O_APPEND) {
+		pos = inode->i_size;
+	}
+
+	if (pos + count > MAX_NON_LFS && !(file->f_flags&O_LARGEFILE)) {
+		if (pos >= MAX_NON_LFS) {
+			send_sig(SIGXFSZ, current, 0);
+			return -EFBIG;
+		}
+		if (count > MAX_NON_LFS - (u32)pos) {
+			count = MAX_NON_LFS - (u32)pos;
+		}
+	}
+	if (pos >= inode->i_sb->s_maxbytes) {
+		if (count || pos > inode->i_sb->s_maxbytes) {
+			send_sig(SIGXFSZ, current, 0);
+			return -EFBIG;
+		}
+	}
+	if (pos + count > inode->i_sb->s_maxbytes) {
+		count = inode->i_sb->s_maxbytes - pos;
+	}
+	
+	if (!count)
+		return 0;
+	errno = ncp_make_open(inode, O_WRONLY);
+	if (errno) {
+		DPRINTK(KERN_ERR "ncp_file_write: open failed, error=%d\n", errno);
+		return errno;
+	}
+	bufsize = NCP_SERVER(inode)->buffer_size;
+
+	already_written = 0;
+
+	bouncebuffer = vmalloc(bufsize);
+	if (!bouncebuffer) {
+		errno = -EIO;	/* -ENOMEM */
+		goto outrel;
+	}
+	while (already_written < count) {
+		int written_this_time;
+		size_t to_write = min_t(unsigned int,
+				      bufsize - (pos % bufsize),
+				      count - already_written);
+
+		if (copy_from_user(bouncebuffer, buf, to_write)) {
+			errno = -EFAULT;
+			break;
+		}
+		if (ncp_write_kernel(NCP_SERVER(inode), 
+		    NCP_FINFO(inode)->file_handle,
+		    pos, to_write, bouncebuffer, &written_this_time) != 0) {
+			errno = -EIO;
+			break;
+		}
+		pos += written_this_time;
+		buf += written_this_time;
+		already_written += written_this_time;
+
+		if (written_this_time != to_write) {
+			break;
+		}
+	}
+	vfree(bouncebuffer);
+
+	inode_update_time(inode, 1);
+
+	*ppos = pos;
+
+	if (pos > inode->i_size) {
+		inode->i_size = pos;
+	}
+	DPRINTK("ncp_file_write: exit %s/%s\n",
+		dentry->d_parent->d_name.name, dentry->d_name.name);
+outrel:
+	ncp_inode_close(inode);		
+	return already_written ? already_written : errno;
+}
+
+static int ncp_release(struct inode *inode, struct file *file) {
+	if (ncp_make_closed(inode)) {
+		DPRINTK("ncp_release: failed to close\n");
+	}
+	return 0;
+}
+
+struct file_operations ncp_file_operations =
+{
+	.llseek		= remote_llseek,
+	.read		= ncp_file_read,
+	.write		= ncp_file_write,
+	.ioctl		= ncp_ioctl,
+	.mmap		= ncp_mmap,
+	.release	= ncp_release,
+	.fsync		= ncp_fsync,
+};
+
+struct inode_operations ncp_file_inode_operations =
+{
+	.setattr	= ncp_notify_change,
+};
diff --git a/fs/ncpfs/getopt.c b/fs/ncpfs/getopt.c
new file mode 100644
index 0000000..335b003
--- /dev/null
+++ b/fs/ncpfs/getopt.c
@@ -0,0 +1,75 @@
+/*
+ * getopt.c
+ */
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+
+#include <asm/errno.h>
+
+#include "getopt.h"
+
+/**
+ *	ncp_getopt - option parser
+ *	@caller: name of the caller, for error messages
+ *	@options: the options string
+ *	@opts: an array of &struct option entries controlling parser operations
+ *	@optopt: output; will contain the current option
+ *	@optarg: output; will contain the value (if one exists)
+ *	@flag: output; may be NULL; should point to a long for or'ing flags
+ *	@value: output; may be NULL; will be overwritten with the integer value
+ *		of the current argument.
+ *
+ *	Helper to parse options on the format used by mount ("a=b,c=d,e,f").
+ *	Returns opts->val if a matching entry in the 'opts' array is found,
+ *	0 when no more tokens are found, -1 if an error is encountered.
+ */
+int ncp_getopt(const char *caller, char **options, const struct ncp_option *opts,
+	       char **optopt, char **optarg, unsigned long *value)
+{
+	char *token;
+	char *val;
+
+	do {
+		if ((token = strsep(options, ",")) == NULL)
+			return 0;
+	} while (*token == '\0');
+	if (optopt)
+		*optopt = token;
+
+	if ((val = strchr (token, '=')) != NULL) {
+		*val++ = 0;
+	}
+	*optarg = val;
+	for (; opts->name; opts++) {
+		if (!strcmp(opts->name, token)) {
+			if (!val) {
+				if (opts->has_arg & OPT_NOPARAM) {
+					return opts->val;
+				}
+				printk(KERN_INFO "%s: the %s option requires an argument\n",
+				       caller, token);
+				return -EINVAL;
+			}
+			if (opts->has_arg & OPT_INT) {
+				char* v;
+
+				*value = simple_strtoul(val, &v, 0);
+				if (!*v) {
+					return opts->val;
+				}
+				printk(KERN_INFO "%s: invalid numeric value in %s=%s\n",
+					caller, token, val);
+				return -EDOM;
+			}
+			if (opts->has_arg & OPT_STRING) {
+				return opts->val;
+			}
+			printk(KERN_INFO "%s: unexpected argument %s to the %s option\n",
+				caller, val, token);
+			return -EINVAL;
+		}
+	}
+	printk(KERN_INFO "%s: Unrecognized mount option %s\n", caller, token);
+	return -EOPNOTSUPP;
+}
diff --git a/fs/ncpfs/getopt.h b/fs/ncpfs/getopt.h
new file mode 100644
index 0000000..cccc007
--- /dev/null
+++ b/fs/ncpfs/getopt.h
@@ -0,0 +1,16 @@
+#ifndef _LINUX_GETOPT_H
+#define _LINUX_GETOPT_H
+
+#define OPT_NOPARAM	1
+#define OPT_INT		2
+#define OPT_STRING	4
+struct ncp_option {
+	const char *name;
+	unsigned int has_arg;
+	int val;
+};
+
+extern int ncp_getopt(const char *caller, char **options, const struct ncp_option *opts,
+		      char **optopt, char **optarg, unsigned long *value);
+
+#endif /* _LINUX_GETOPT_H */
diff --git a/fs/ncpfs/inode.c b/fs/ncpfs/inode.c
new file mode 100644
index 0000000..44795d2
--- /dev/null
+++ b/fs/ncpfs/inode.c
@@ -0,0 +1,1012 @@
+/*
+ *  inode.c
+ *
+ *  Copyright (C) 1995, 1996 by Volker Lendecke
+ *  Modified for big endian by J.F. Chadima and David S. Miller
+ *  Modified 1997 Peter Waltenberg, Bill Hawes, David Woodhouse for 2.1 dcache
+ *  Modified 1998 Wolfram Pienkoss for NLS
+ *  Modified 2000 Ben Harris, University of Cambridge for NFS NS meta-info
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <asm/byteorder.h>
+
+#include <linux/time.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/string.h>
+#include <linux/stat.h>
+#include <linux/errno.h>
+#include <linux/file.h>
+#include <linux/fcntl.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <linux/vfs.h>
+
+#include <linux/ncp_fs.h>
+
+#include <net/sock.h>
+
+#include "ncplib_kernel.h"
+#include "getopt.h"
+
+static void ncp_delete_inode(struct inode *);
+static void ncp_put_super(struct super_block *);
+static int  ncp_statfs(struct super_block *, struct kstatfs *);
+
+static kmem_cache_t * ncp_inode_cachep;
+
+static struct inode *ncp_alloc_inode(struct super_block *sb)
+{
+	struct ncp_inode_info *ei;
+	ei = (struct ncp_inode_info *)kmem_cache_alloc(ncp_inode_cachep, SLAB_KERNEL);
+	if (!ei)
+		return NULL;
+	return &ei->vfs_inode;
+}
+
+static void ncp_destroy_inode(struct inode *inode)
+{
+	kmem_cache_free(ncp_inode_cachep, NCP_FINFO(inode));
+}
+
+static void init_once(void * foo, kmem_cache_t * cachep, unsigned long flags)
+{
+	struct ncp_inode_info *ei = (struct ncp_inode_info *) foo;
+
+	if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) ==
+	    SLAB_CTOR_CONSTRUCTOR) {
+		init_MUTEX(&ei->open_sem);
+		inode_init_once(&ei->vfs_inode);
+	}
+}
+ 
+static int init_inodecache(void)
+{
+	ncp_inode_cachep = kmem_cache_create("ncp_inode_cache",
+					     sizeof(struct ncp_inode_info),
+					     0, SLAB_RECLAIM_ACCOUNT,
+					     init_once, NULL);
+	if (ncp_inode_cachep == NULL)
+		return -ENOMEM;
+	return 0;
+}
+
+static void destroy_inodecache(void)
+{
+	if (kmem_cache_destroy(ncp_inode_cachep))
+		printk(KERN_INFO "ncp_inode_cache: not all structures were freed\n");
+}
+
+static int ncp_remount(struct super_block *sb, int *flags, char* data)
+{
+	*flags |= MS_NODIRATIME;
+	return 0;
+}
+
+static struct super_operations ncp_sops =
+{
+	.alloc_inode	= ncp_alloc_inode,
+	.destroy_inode	= ncp_destroy_inode,
+	.drop_inode	= generic_delete_inode,
+	.delete_inode	= ncp_delete_inode,
+	.put_super	= ncp_put_super,
+	.statfs		= ncp_statfs,
+	.remount_fs	= ncp_remount,
+};
+
+extern struct dentry_operations ncp_root_dentry_operations;
+#if defined(CONFIG_NCPFS_EXTRAS) || defined(CONFIG_NCPFS_NFS_NS)
+extern struct address_space_operations ncp_symlink_aops;
+extern int ncp_symlink(struct inode*, struct dentry*, const char*);
+#endif
+
+/*
+ * Fill in the ncpfs-specific information in the inode.
+ */
+static void ncp_update_dirent(struct inode *inode, struct ncp_entry_info *nwinfo)
+{
+	NCP_FINFO(inode)->DosDirNum = nwinfo->i.DosDirNum;
+	NCP_FINFO(inode)->dirEntNum = nwinfo->i.dirEntNum;
+	NCP_FINFO(inode)->volNumber = nwinfo->volume;
+}
+
+void ncp_update_inode(struct inode *inode, struct ncp_entry_info *nwinfo)
+{
+	ncp_update_dirent(inode, nwinfo);
+	NCP_FINFO(inode)->nwattr = nwinfo->i.attributes;
+	NCP_FINFO(inode)->access = nwinfo->access;
+	memcpy(NCP_FINFO(inode)->file_handle, nwinfo->file_handle,
+			sizeof(nwinfo->file_handle));
+	DPRINTK("ncp_update_inode: updated %s, volnum=%d, dirent=%u\n",
+		nwinfo->i.entryName, NCP_FINFO(inode)->volNumber,
+		NCP_FINFO(inode)->dirEntNum);
+}
+
+static void ncp_update_dates(struct inode *inode, struct nw_info_struct *nwi)
+{
+	/* NFS namespace mode overrides others if it's set. */
+	DPRINTK(KERN_DEBUG "ncp_update_dates_and_mode: (%s) nfs.mode=0%o\n",
+		nwi->entryName, nwi->nfs.mode);
+	if (nwi->nfs.mode) {
+		/* XXX Security? */
+		inode->i_mode = nwi->nfs.mode;
+	}
+
+	inode->i_blocks = (inode->i_size + NCP_BLOCK_SIZE - 1) >> NCP_BLOCK_SHIFT;
+
+	inode->i_mtime.tv_sec = ncp_date_dos2unix(nwi->modifyTime, nwi->modifyDate);
+	inode->i_ctime.tv_sec = ncp_date_dos2unix(nwi->creationTime, nwi->creationDate);
+	inode->i_atime.tv_sec = ncp_date_dos2unix(0, nwi->lastAccessDate);
+	inode->i_atime.tv_nsec = 0;
+	inode->i_mtime.tv_nsec = 0;
+	inode->i_ctime.tv_nsec = 0;
+}
+
+static void ncp_update_attrs(struct inode *inode, struct ncp_entry_info *nwinfo)
+{
+	struct nw_info_struct *nwi = &nwinfo->i;
+	struct ncp_server *server = NCP_SERVER(inode);
+
+	if (nwi->attributes & aDIR) {
+		inode->i_mode = server->m.dir_mode;
+		/* for directories dataStreamSize seems to be some
+		   Object ID ??? */
+		inode->i_size = NCP_BLOCK_SIZE;
+	} else {
+		inode->i_mode = server->m.file_mode;
+		inode->i_size = le32_to_cpu(nwi->dataStreamSize);
+#ifdef CONFIG_NCPFS_EXTRAS
+		if ((server->m.flags & (NCP_MOUNT_EXTRAS|NCP_MOUNT_SYMLINKS)) 
+		 && (nwi->attributes & aSHARED)) {
+			switch (nwi->attributes & (aHIDDEN|aSYSTEM)) {
+				case aHIDDEN:
+					if (server->m.flags & NCP_MOUNT_SYMLINKS) {
+						if (/* (inode->i_size >= NCP_MIN_SYMLINK_SIZE)
+						 && */ (inode->i_size <= NCP_MAX_SYMLINK_SIZE)) {
+							inode->i_mode = (inode->i_mode & ~S_IFMT) | S_IFLNK;
+							NCP_FINFO(inode)->flags |= NCPI_KLUDGE_SYMLINK;
+							break;
+						}
+					}
+					/* FALLTHROUGH */
+				case 0:
+					if (server->m.flags & NCP_MOUNT_EXTRAS)
+						inode->i_mode |= S_IRUGO;
+					break;
+				case aSYSTEM:
+					if (server->m.flags & NCP_MOUNT_EXTRAS)
+						inode->i_mode |= (inode->i_mode >> 2) & S_IXUGO;
+					break;
+				/* case aSYSTEM|aHIDDEN: */
+				default:
+					/* reserved combination */
+					break;
+			}
+		}
+#endif
+	}
+	if (nwi->attributes & aRONLY) inode->i_mode &= ~S_IWUGO;
+}
+
+void ncp_update_inode2(struct inode* inode, struct ncp_entry_info *nwinfo)
+{
+	NCP_FINFO(inode)->flags = 0;
+	if (!atomic_read(&NCP_FINFO(inode)->opened)) {
+		NCP_FINFO(inode)->nwattr = nwinfo->i.attributes;
+		ncp_update_attrs(inode, nwinfo);
+	}
+
+	ncp_update_dates(inode, &nwinfo->i);
+	ncp_update_dirent(inode, nwinfo);
+}
+
+/*
+ * Fill in the inode based on the ncp_entry_info structure.
+ */
+static void ncp_set_attr(struct inode *inode, struct ncp_entry_info *nwinfo)
+{
+	struct ncp_server *server = NCP_SERVER(inode);
+
+	NCP_FINFO(inode)->flags = 0;
+	
+	ncp_update_attrs(inode, nwinfo);
+
+	DDPRINTK("ncp_read_inode: inode->i_mode = %u\n", inode->i_mode);
+
+	inode->i_nlink = 1;
+	inode->i_uid = server->m.uid;
+	inode->i_gid = server->m.gid;
+	inode->i_blksize = NCP_BLOCK_SIZE;
+
+	ncp_update_dates(inode, &nwinfo->i);
+	ncp_update_inode(inode, nwinfo);
+}
+
+#if defined(CONFIG_NCPFS_EXTRAS) || defined(CONFIG_NCPFS_NFS_NS)
+static struct inode_operations ncp_symlink_inode_operations = {
+	.readlink	= generic_readlink,
+	.follow_link	= page_follow_link_light,
+	.put_link	= page_put_link,
+	.setattr	= ncp_notify_change,
+};
+#endif
+
+/*
+ * Get a new inode.
+ */
+struct inode * 
+ncp_iget(struct super_block *sb, struct ncp_entry_info *info)
+{
+	struct inode *inode;
+
+	if (info == NULL) {
+		printk(KERN_ERR "ncp_iget: info is NULL\n");
+		return NULL;
+	}
+
+	inode = new_inode(sb);
+	if (inode) {
+		atomic_set(&NCP_FINFO(inode)->opened, info->opened);
+
+		inode->i_ino = info->ino;
+		ncp_set_attr(inode, info);
+		if (S_ISREG(inode->i_mode)) {
+			inode->i_op = &ncp_file_inode_operations;
+			inode->i_fop = &ncp_file_operations;
+		} else if (S_ISDIR(inode->i_mode)) {
+			inode->i_op = &ncp_dir_inode_operations;
+			inode->i_fop = &ncp_dir_operations;
+#ifdef CONFIG_NCPFS_NFS_NS
+		} else if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) || S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {
+			init_special_inode(inode, inode->i_mode,
+				new_decode_dev(info->i.nfs.rdev));
+#endif
+#if defined(CONFIG_NCPFS_EXTRAS) || defined(CONFIG_NCPFS_NFS_NS)
+		} else if (S_ISLNK(inode->i_mode)) {
+			inode->i_op = &ncp_symlink_inode_operations;
+			inode->i_data.a_ops = &ncp_symlink_aops;
+#endif
+		} else {
+			make_bad_inode(inode);
+		}
+		insert_inode_hash(inode);
+	} else
+		printk(KERN_ERR "ncp_iget: iget failed!\n");
+	return inode;
+}
+
+static void
+ncp_delete_inode(struct inode *inode)
+{
+	if (S_ISDIR(inode->i_mode)) {
+		DDPRINTK("ncp_delete_inode: put directory %ld\n", inode->i_ino);
+	}
+
+	if (ncp_make_closed(inode) != 0) {
+		/* We can't do anything but complain. */
+		printk(KERN_ERR "ncp_delete_inode: could not close\n");
+	}
+	clear_inode(inode);
+}
+
+static void ncp_stop_tasks(struct ncp_server *server) {
+	struct sock* sk = server->ncp_sock->sk;
+		
+	sk->sk_error_report = server->error_report;
+	sk->sk_data_ready   = server->data_ready;
+	sk->sk_write_space  = server->write_space;
+	del_timer_sync(&server->timeout_tm);
+	flush_scheduled_work();
+}
+
+static const struct ncp_option ncp_opts[] = {
+	{ "uid",	OPT_INT,	'u' },
+	{ "gid",	OPT_INT,	'g' },
+	{ "owner",	OPT_INT,	'o' },
+	{ "mode",	OPT_INT,	'm' },
+	{ "dirmode",	OPT_INT,	'd' },
+	{ "timeout",	OPT_INT,	't' },
+	{ "retry",	OPT_INT,	'r' },
+	{ "flags",	OPT_INT,	'f' },
+	{ "wdogpid",	OPT_INT,	'w' },
+	{ "ncpfd",	OPT_INT,	'n' },
+	{ "infofd",	OPT_INT,	'i' },	/* v5 */
+	{ "version",	OPT_INT,	'v' },
+	{ NULL,		0,		0 } };
+
+static int ncp_parse_options(struct ncp_mount_data_kernel *data, char *options) {
+	int optval;
+	char *optarg;
+	unsigned long optint;
+	int version = 0;
+
+	data->flags = 0;
+	data->int_flags = 0;
+	data->mounted_uid = 0;
+	data->wdog_pid = -1;
+	data->ncp_fd = ~0;
+	data->time_out = 10;
+	data->retry_count = 20;
+	data->uid = 0;
+	data->gid = 0;
+	data->file_mode = 0600;
+	data->dir_mode = 0700;
+	data->info_fd = -1;
+	data->mounted_vol[0] = 0;
+	
+	while ((optval = ncp_getopt("ncpfs", &options, ncp_opts, NULL, &optarg, &optint)) != 0) {
+		if (optval < 0)
+			return optval;
+		switch (optval) {
+			case 'u':
+				data->uid = optint;
+				break;
+			case 'g':
+				data->gid = optint;
+				break;
+			case 'o':
+				data->mounted_uid = optint;
+				break;
+			case 'm':
+				data->file_mode = optint;
+				break;
+			case 'd':
+				data->dir_mode = optint;
+				break;
+			case 't':
+				data->time_out = optint;
+				break;
+			case 'r':
+				data->retry_count = optint;
+				break;
+			case 'f':
+				data->flags = optint;
+				break;
+			case 'w':
+				data->wdog_pid = optint;
+				break;
+			case 'n':
+				data->ncp_fd = optint;
+				break;
+			case 'i':
+				data->info_fd = optint;
+				break;
+			case 'v':
+				if (optint < NCP_MOUNT_VERSION_V4) {
+					return -ECHRNG;
+				}
+				if (optint > NCP_MOUNT_VERSION_V5) {
+					return -ECHRNG;
+				}
+				version = optint;
+				break;
+			
+		}
+	}
+	return 0;
+}
+
+static int ncp_fill_super(struct super_block *sb, void *raw_data, int silent)
+{
+	struct ncp_mount_data_kernel data;
+	struct ncp_server *server;
+	struct file *ncp_filp;
+	struct inode *root_inode;
+	struct inode *sock_inode;
+	struct socket *sock;
+	int error;
+	int default_bufsize;
+#ifdef CONFIG_NCPFS_PACKET_SIGNING
+	int options;
+#endif
+	struct ncp_entry_info finfo;
+
+	server = kmalloc(sizeof(struct ncp_server), GFP_KERNEL);
+	if (!server)
+		return -ENOMEM;
+	sb->s_fs_info = server;
+	memset(server, 0, sizeof(struct ncp_server));
+
+	error = -EFAULT;
+	if (raw_data == NULL)
+		goto out;
+	switch (*(int*)raw_data) {
+		case NCP_MOUNT_VERSION:
+			{
+				struct ncp_mount_data* md = (struct ncp_mount_data*)raw_data;
+
+				data.flags = md->flags;
+				data.int_flags = NCP_IMOUNT_LOGGEDIN_POSSIBLE;
+				data.mounted_uid = md->mounted_uid;
+				data.wdog_pid = md->wdog_pid;
+				data.ncp_fd = md->ncp_fd;
+				data.time_out = md->time_out;
+				data.retry_count = md->retry_count;
+				data.uid = md->uid;
+				data.gid = md->gid;
+				data.file_mode = md->file_mode;
+				data.dir_mode = md->dir_mode;
+				data.info_fd = -1;
+				memcpy(data.mounted_vol, md->mounted_vol,
+					NCP_VOLNAME_LEN+1);
+			}
+			break;
+		case NCP_MOUNT_VERSION_V4:
+			{
+				struct ncp_mount_data_v4* md = (struct ncp_mount_data_v4*)raw_data;
+
+				data.flags = md->flags;
+				data.int_flags = 0;
+				data.mounted_uid = md->mounted_uid;
+				data.wdog_pid = md->wdog_pid;
+				data.ncp_fd = md->ncp_fd;
+				data.time_out = md->time_out;
+				data.retry_count = md->retry_count;
+				data.uid = md->uid;
+				data.gid = md->gid;
+				data.file_mode = md->file_mode;
+				data.dir_mode = md->dir_mode;
+				data.info_fd = -1;
+				data.mounted_vol[0] = 0;
+			}
+			break;
+		default:
+			error = -ECHRNG;
+			if (memcmp(raw_data, "vers", 4) == 0) {
+				error = ncp_parse_options(&data, raw_data);
+			}
+			if (error)
+				goto out;
+			break;
+	}
+	error = -EBADF;
+	ncp_filp = fget(data.ncp_fd);
+	if (!ncp_filp)
+		goto out;
+	error = -ENOTSOCK;
+	sock_inode = ncp_filp->f_dentry->d_inode;
+	if (!S_ISSOCK(sock_inode->i_mode))
+		goto out_fput;
+	sock = SOCKET_I(sock_inode);
+	if (!sock)
+		goto out_fput;
+		
+	if (sock->type == SOCK_STREAM)
+		default_bufsize = 0xF000;
+	else
+		default_bufsize = 1024;
+
+	sb->s_flags |= MS_NODIRATIME;	/* probably even noatime */
+	sb->s_maxbytes = 0xFFFFFFFFU;
+	sb->s_blocksize = 1024;	/* Eh...  Is this correct? */
+	sb->s_blocksize_bits = 10;
+	sb->s_magic = NCP_SUPER_MAGIC;
+	sb->s_op = &ncp_sops;
+
+	server = NCP_SBP(sb);
+	memset(server, 0, sizeof(*server));
+
+	server->ncp_filp = ncp_filp;
+	server->ncp_sock = sock;
+	
+	if (data.info_fd != -1) {
+		struct socket *info_sock;
+
+		error = -EBADF;
+		server->info_filp = fget(data.info_fd);
+		if (!server->info_filp)
+			goto out_fput;
+		error = -ENOTSOCK;
+		sock_inode = server->info_filp->f_dentry->d_inode;
+		if (!S_ISSOCK(sock_inode->i_mode))
+			goto out_fput2;
+		info_sock = SOCKET_I(sock_inode);
+		if (!info_sock)
+			goto out_fput2;
+		error = -EBADFD;
+		if (info_sock->type != SOCK_STREAM)
+			goto out_fput2;
+		server->info_sock = info_sock;
+	}
+
+/*	server->lock = 0;	*/
+	init_MUTEX(&server->sem);
+	server->packet = NULL;
+/*	server->buffer_size = 0;	*/
+/*	server->conn_status = 0;	*/
+/*	server->root_dentry = NULL;	*/
+/*	server->root_setuped = 0;	*/
+#ifdef CONFIG_NCPFS_PACKET_SIGNING
+/*	server->sign_wanted = 0;	*/
+/*	server->sign_active = 0;	*/
+#endif
+	server->auth.auth_type = NCP_AUTH_NONE;
+/*	server->auth.object_name_len = 0;	*/
+/*	server->auth.object_name = NULL;	*/
+/*	server->auth.object_type = 0;		*/
+/*	server->priv.len = 0;			*/
+/*	server->priv.data = NULL;		*/
+
+	server->m = data;
+	/* Althought anything producing this is buggy, it happens
+	   now because of PATH_MAX changes.. */
+	if (server->m.time_out < 1) {
+		server->m.time_out = 10;
+		printk(KERN_INFO "You need to recompile your ncpfs utils..\n");
+	}
+	server->m.time_out = server->m.time_out * HZ / 100;
+	server->m.file_mode = (server->m.file_mode & S_IRWXUGO) | S_IFREG;
+	server->m.dir_mode = (server->m.dir_mode & S_IRWXUGO) | S_IFDIR;
+
+#ifdef CONFIG_NCPFS_NLS
+	/* load the default NLS charsets */
+	server->nls_vol = load_nls_default();
+	server->nls_io = load_nls_default();
+#endif /* CONFIG_NCPFS_NLS */
+
+	server->dentry_ttl = 0;	/* no caching */
+
+	INIT_LIST_HEAD(&server->tx.requests);
+	init_MUTEX(&server->rcv.creq_sem);
+	server->tx.creq		= NULL;
+	server->rcv.creq	= NULL;
+	server->data_ready	= sock->sk->sk_data_ready;
+	server->write_space	= sock->sk->sk_write_space;
+	server->error_report	= sock->sk->sk_error_report;
+	sock->sk->sk_user_data	= server;
+
+	init_timer(&server->timeout_tm);
+#undef NCP_PACKET_SIZE
+#define NCP_PACKET_SIZE 131072
+	error = -ENOMEM;
+	server->packet_size = NCP_PACKET_SIZE;
+	server->packet = vmalloc(NCP_PACKET_SIZE);
+	if (server->packet == NULL)
+		goto out_nls;
+
+	sock->sk->sk_data_ready	  = ncp_tcp_data_ready;
+	sock->sk->sk_error_report = ncp_tcp_error_report;
+	if (sock->type == SOCK_STREAM) {
+		server->rcv.ptr = (unsigned char*)&server->rcv.buf;
+		server->rcv.len = 10;
+		server->rcv.state = 0;
+		INIT_WORK(&server->rcv.tq, ncp_tcp_rcv_proc, server);
+		INIT_WORK(&server->tx.tq, ncp_tcp_tx_proc, server);
+		sock->sk->sk_write_space = ncp_tcp_write_space;
+	} else {
+		INIT_WORK(&server->rcv.tq, ncpdgram_rcv_proc, server);
+		INIT_WORK(&server->timeout_tq, ncpdgram_timeout_proc, server);
+		server->timeout_tm.data = (unsigned long)server;
+		server->timeout_tm.function = ncpdgram_timeout_call;
+	}
+
+	ncp_lock_server(server);
+	error = ncp_connect(server);
+	ncp_unlock_server(server);
+	if (error < 0)
+		goto out_packet;
+	DPRINTK("ncp_fill_super: NCP_SBP(sb) = %x\n", (int) NCP_SBP(sb));
+
+	error = -EMSGSIZE;	/* -EREMOTESIDEINCOMPATIBLE */
+#ifdef CONFIG_NCPFS_PACKET_SIGNING
+	if (ncp_negotiate_size_and_options(server, default_bufsize,
+		NCP_DEFAULT_OPTIONS, &(server->buffer_size), &options) == 0)
+	{
+		if (options != NCP_DEFAULT_OPTIONS)
+		{
+			if (ncp_negotiate_size_and_options(server, 
+				default_bufsize,
+				options & 2, 
+				&(server->buffer_size), &options) != 0)
+				
+			{
+				goto out_disconnect;
+			}
+		}
+		if (options & 2)
+			server->sign_wanted = 1;
+	}
+	else 
+#endif	/* CONFIG_NCPFS_PACKET_SIGNING */
+	if (ncp_negotiate_buffersize(server, default_bufsize,
+  				     &(server->buffer_size)) != 0)
+		goto out_disconnect;
+	DPRINTK("ncpfs: bufsize = %d\n", server->buffer_size);
+
+	memset(&finfo, 0, sizeof(finfo));
+	finfo.i.attributes	= aDIR;
+	finfo.i.dataStreamSize	= 0;	/* ignored */
+	finfo.i.dirEntNum	= 0;
+	finfo.i.DosDirNum	= 0;
+#ifdef CONFIG_NCPFS_SMALLDOS
+	finfo.i.NSCreator	= NW_NS_DOS;
+#endif
+	finfo.volume		= NCP_NUMBER_OF_VOLUMES;
+	/* set dates of mountpoint to Jan 1, 1986; 00:00 */
+	finfo.i.creationTime	= finfo.i.modifyTime
+				= cpu_to_le16(0x0000);
+	finfo.i.creationDate	= finfo.i.modifyDate
+				= finfo.i.lastAccessDate
+				= cpu_to_le16(0x0C21);
+	finfo.i.nameLen		= 0;
+	finfo.i.entryName[0]	= '\0';
+
+	finfo.opened		= 0;
+	finfo.ino		= 2;	/* tradition */
+
+	server->name_space[finfo.volume] = NW_NS_DOS;
+
+	error = -ENOMEM;
+        root_inode = ncp_iget(sb, &finfo);
+        if (!root_inode)
+		goto out_disconnect;
+	DPRINTK("ncp_fill_super: root vol=%d\n", NCP_FINFO(root_inode)->volNumber);
+	sb->s_root = d_alloc_root(root_inode);
+        if (!sb->s_root)
+		goto out_no_root;
+	sb->s_root->d_op = &ncp_root_dentry_operations;
+	return 0;
+
+out_no_root:
+	iput(root_inode);
+out_disconnect:
+	ncp_lock_server(server);
+	ncp_disconnect(server);
+	ncp_unlock_server(server);
+out_packet:
+	ncp_stop_tasks(server);
+	vfree(server->packet);
+out_nls:
+#ifdef CONFIG_NCPFS_NLS
+	unload_nls(server->nls_io);
+	unload_nls(server->nls_vol);
+#endif
+out_fput2:
+	if (server->info_filp)
+		fput(server->info_filp);
+out_fput:
+	/* 23/12/1998 Marcin Dalecki <dalecki@cs.net.pl>:
+	 * 
+	 * The previously used put_filp(ncp_filp); was bogous, since
+	 * it doesn't proper unlocking.
+	 */
+	fput(ncp_filp);
+out:
+	sb->s_fs_info = NULL;
+	kfree(server);
+	return error;
+}
+
+static void ncp_put_super(struct super_block *sb)
+{
+	struct ncp_server *server = NCP_SBP(sb);
+
+	ncp_lock_server(server);
+	ncp_disconnect(server);
+	ncp_unlock_server(server);
+
+	ncp_stop_tasks(server);
+
+#ifdef CONFIG_NCPFS_NLS
+	/* unload the NLS charsets */
+	if (server->nls_vol)
+	{
+		unload_nls(server->nls_vol);
+		server->nls_vol = NULL;
+	}
+	if (server->nls_io)
+	{
+		unload_nls(server->nls_io);
+		server->nls_io = NULL;
+	}
+#endif /* CONFIG_NCPFS_NLS */
+
+	if (server->info_filp)
+		fput(server->info_filp);
+	fput(server->ncp_filp);
+	kill_proc(server->m.wdog_pid, SIGTERM, 1);
+
+	if (server->priv.data) 
+		ncp_kfree_s(server->priv.data, server->priv.len);
+	if (server->auth.object_name)
+		ncp_kfree_s(server->auth.object_name, server->auth.object_name_len);
+	vfree(server->packet);
+	sb->s_fs_info = NULL;
+	kfree(server);
+}
+
+static int ncp_statfs(struct super_block *sb, struct kstatfs *buf)
+{
+	struct dentry* d;
+	struct inode* i;
+	struct ncp_inode_info* ni;
+	struct ncp_server* s;
+	struct ncp_volume_info vi;
+	int err;
+	__u8 dh;
+	
+	d = sb->s_root;
+	if (!d) {
+		goto dflt;
+	}
+	i = d->d_inode;
+	if (!i) {
+		goto dflt;
+	}
+	ni = NCP_FINFO(i);
+	if (!ni) {
+		goto dflt;
+	}
+	s = NCP_SBP(sb);
+	if (!s) {
+		goto dflt;
+	}
+	if (!s->m.mounted_vol[0]) {
+		goto dflt;
+	}
+
+	err = ncp_dirhandle_alloc(s, ni->volNumber, ni->DosDirNum, &dh);
+	if (err) {
+		goto dflt;
+	}
+	err = ncp_get_directory_info(s, dh, &vi);
+	ncp_dirhandle_free(s, dh);
+	if (err) {
+		goto dflt;
+	}
+	buf->f_type = NCP_SUPER_MAGIC;
+	buf->f_bsize = vi.sectors_per_block * 512;
+	buf->f_blocks = vi.total_blocks;
+	buf->f_bfree = vi.free_blocks;
+	buf->f_bavail = vi.free_blocks;
+	buf->f_files = vi.total_dir_entries;
+	buf->f_ffree = vi.available_dir_entries;
+	buf->f_namelen = 12;
+	return 0;
+
+	/* We cannot say how much disk space is left on a mounted
+	   NetWare Server, because free space is distributed over
+	   volumes, and the current user might have disk quotas. So
+	   free space is not that simple to determine. Our decision
+	   here is to err conservatively. */
+
+dflt:;
+	buf->f_type = NCP_SUPER_MAGIC;
+	buf->f_bsize = NCP_BLOCK_SIZE;
+	buf->f_blocks = 0;
+	buf->f_bfree = 0;
+	buf->f_bavail = 0;
+	buf->f_namelen = 12;
+	return 0;
+}
+
+int ncp_notify_change(struct dentry *dentry, struct iattr *attr)
+{
+	struct inode *inode = dentry->d_inode;
+	int result = 0;
+	__le32 info_mask;
+	struct nw_modify_dos_info info;
+	struct ncp_server *server;
+
+	result = -EIO;
+
+	lock_kernel();	
+
+	server = NCP_SERVER(inode);
+	if ((!server) || !ncp_conn_valid(server))
+		goto out;
+
+	/* ageing the dentry to force validation */
+	ncp_age_dentry(server, dentry);
+
+	result = inode_change_ok(inode, attr);
+	if (result < 0)
+		goto out;
+
+	result = -EPERM;
+	if (((attr->ia_valid & ATTR_UID) &&
+	     (attr->ia_uid != server->m.uid)))
+		goto out;
+
+	if (((attr->ia_valid & ATTR_GID) &&
+	     (attr->ia_gid != server->m.gid)))
+		goto out;
+
+	if (((attr->ia_valid & ATTR_MODE) &&
+	     (attr->ia_mode &
+	      ~(S_IFREG | S_IFDIR | S_IRWXUGO))))
+		goto out;
+
+	info_mask = 0;
+	memset(&info, 0, sizeof(info));
+
+#if 1 
+        if ((attr->ia_valid & ATTR_MODE) != 0)
+        {
+		umode_t newmode = attr->ia_mode;
+
+		info_mask |= DM_ATTRIBUTES;
+
+                if (S_ISDIR(inode->i_mode)) {
+                	newmode &= server->m.dir_mode;
+		} else {
+#ifdef CONFIG_NCPFS_EXTRAS			
+			if (server->m.flags & NCP_MOUNT_EXTRAS) {
+				/* any non-default execute bit set */
+				if (newmode & ~server->m.file_mode & S_IXUGO)
+					info.attributes |= aSHARED | aSYSTEM;
+				/* read for group/world and not in default file_mode */
+				else if (newmode & ~server->m.file_mode & S_IRUGO)
+					info.attributes |= aSHARED;
+			} else
+#endif
+				newmode &= server->m.file_mode;			
+                }
+                if (newmode & S_IWUGO)
+                	info.attributes &= ~(aRONLY|aRENAMEINHIBIT|aDELETEINHIBIT);
+                else
+			info.attributes |=  (aRONLY|aRENAMEINHIBIT|aDELETEINHIBIT);
+
+#ifdef CONFIG_NCPFS_NFS_NS
+		if (ncp_is_nfs_extras(server, NCP_FINFO(inode)->volNumber)) {
+			result = ncp_modify_nfs_info(server,
+						     NCP_FINFO(inode)->volNumber,
+						     NCP_FINFO(inode)->dirEntNum,
+						     attr->ia_mode, 0);
+			if (result != 0)
+				goto out;
+			info.attributes &= ~(aSHARED | aSYSTEM);
+			{
+				/* mark partial success */
+				struct iattr tmpattr;
+				
+				tmpattr.ia_valid = ATTR_MODE;
+				tmpattr.ia_mode = attr->ia_mode;
+
+				result = inode_setattr(inode, &tmpattr);
+				if (result)
+					goto out;
+			}
+		}
+#endif
+        }
+#endif
+
+	/* Do SIZE before attributes, otherwise mtime together with size does not work...
+	 */
+	if ((attr->ia_valid & ATTR_SIZE) != 0) {
+		int written;
+
+		DPRINTK("ncpfs: trying to change size to %ld\n",
+			attr->ia_size);
+
+		if ((result = ncp_make_open(inode, O_WRONLY)) < 0) {
+			result = -EACCES;
+			goto out;
+		}
+		ncp_write_kernel(NCP_SERVER(inode), NCP_FINFO(inode)->file_handle,
+			  attr->ia_size, 0, "", &written);
+
+		/* According to ndir, the changes only take effect after
+		   closing the file */
+		ncp_inode_close(inode);
+		result = ncp_make_closed(inode);
+		if (result)
+			goto out;
+		{
+			struct iattr tmpattr;
+			
+			tmpattr.ia_valid = ATTR_SIZE;
+			tmpattr.ia_size = attr->ia_size;
+			
+			result = inode_setattr(inode, &tmpattr);
+			if (result)
+				goto out;
+		}
+	}
+	if ((attr->ia_valid & ATTR_CTIME) != 0) {
+		info_mask |= (DM_CREATE_TIME | DM_CREATE_DATE);
+		ncp_date_unix2dos(attr->ia_ctime.tv_sec,
+			     &info.creationTime, &info.creationDate);
+	}
+	if ((attr->ia_valid & ATTR_MTIME) != 0) {
+		info_mask |= (DM_MODIFY_TIME | DM_MODIFY_DATE);
+		ncp_date_unix2dos(attr->ia_mtime.tv_sec,
+				  &info.modifyTime, &info.modifyDate);
+	}
+	if ((attr->ia_valid & ATTR_ATIME) != 0) {
+		__le16 dummy;
+		info_mask |= (DM_LAST_ACCESS_DATE);
+		ncp_date_unix2dos(attr->ia_atime.tv_sec,
+				  &dummy, &info.lastAccessDate);
+	}
+	if (info_mask != 0) {
+		result = ncp_modify_file_or_subdir_dos_info(NCP_SERVER(inode),
+				      inode, info_mask, &info);
+		if (result != 0) {
+			result = -EACCES;
+
+			if (info_mask == (DM_CREATE_TIME | DM_CREATE_DATE)) {
+				/* NetWare seems not to allow this. I
+				   do not know why. So, just tell the
+				   user everything went fine. This is
+				   a terrible hack, but I do not know
+				   how to do this correctly. */
+				result = 0;
+			} else
+				goto out;
+		}
+#ifdef CONFIG_NCPFS_STRONG		
+		if ((!result) && (info_mask & DM_ATTRIBUTES))
+			NCP_FINFO(inode)->nwattr = info.attributes;
+#endif
+	}
+	if (!result)
+		result = inode_setattr(inode, attr);
+out:
+	unlock_kernel();
+	return result;
+}
+
+#ifdef DEBUG_NCP_MALLOC
+int ncp_malloced;
+int ncp_current_malloced;
+#endif
+
+static struct super_block *ncp_get_sb(struct file_system_type *fs_type,
+	int flags, const char *dev_name, void *data)
+{
+	return get_sb_nodev(fs_type, flags, data, ncp_fill_super);
+}
+
+static struct file_system_type ncp_fs_type = {
+	.owner		= THIS_MODULE,
+	.name		= "ncpfs",
+	.get_sb		= ncp_get_sb,
+	.kill_sb	= kill_anon_super,
+};
+
+static int __init init_ncp_fs(void)
+{
+	int err;
+	DPRINTK("ncpfs: init_module called\n");
+
+#ifdef DEBUG_NCP_MALLOC
+	ncp_malloced = 0;
+	ncp_current_malloced = 0;
+#endif
+	err = init_inodecache();
+	if (err)
+		goto out1;
+	err = register_filesystem(&ncp_fs_type);
+	if (err)
+		goto out;
+	return 0;
+out:
+	destroy_inodecache();
+out1:
+	return err;
+}
+
+static void __exit exit_ncp_fs(void)
+{
+	DPRINTK("ncpfs: cleanup_module called\n");
+	unregister_filesystem(&ncp_fs_type);
+	destroy_inodecache();
+#ifdef DEBUG_NCP_MALLOC
+	PRINTK("ncp_malloced: %d\n", ncp_malloced);
+	PRINTK("ncp_current_malloced: %d\n", ncp_current_malloced);
+#endif
+}
+
+module_init(init_ncp_fs)
+module_exit(exit_ncp_fs)
+MODULE_LICENSE("GPL");
diff --git a/fs/ncpfs/ioctl.c b/fs/ncpfs/ioctl.c
new file mode 100644
index 0000000..88df793
--- /dev/null
+++ b/fs/ncpfs/ioctl.c
@@ -0,0 +1,649 @@
+/*
+ *  ioctl.c
+ *
+ *  Copyright (C) 1995, 1996 by Volker Lendecke
+ *  Modified 1997 Peter Waltenberg, Bill Hawes, David Woodhouse for 2.1 dcache
+ *  Modified 1998, 1999 Wolfram Pienkoss for NLS
+ *
+ */
+
+#include <linux/config.h>
+
+#include <asm/uaccess.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/ioctl.h>
+#include <linux/time.h>
+#include <linux/mm.h>
+#include <linux/highuid.h>
+#include <linux/vmalloc.h>
+
+#include <linux/ncp_fs.h>
+
+#include "ncplib_kernel.h"
+
+/* maximum limit for ncp_objectname_ioctl */
+#define NCP_OBJECT_NAME_MAX_LEN	4096
+/* maximum limit for ncp_privatedata_ioctl */
+#define NCP_PRIVATE_DATA_MAX_LEN 8192
+/* maximum negotiable packet size */
+#define NCP_PACKET_SIZE_INTERNAL 65536
+
+static int
+ncp_get_fs_info(struct ncp_server* server, struct inode* inode, struct ncp_fs_info __user *arg)
+{
+	struct ncp_fs_info info;
+
+	if ((permission(inode, MAY_WRITE, NULL) != 0)
+	    && (current->uid != server->m.mounted_uid)) {
+		return -EACCES;
+	}
+	if (copy_from_user(&info, arg, sizeof(info)))
+		return -EFAULT;
+
+	if (info.version != NCP_GET_FS_INFO_VERSION) {
+		DPRINTK("info.version invalid: %d\n", info.version);
+		return -EINVAL;
+	}
+	/* TODO: info.addr = server->m.serv_addr; */
+	SET_UID(info.mounted_uid, server->m.mounted_uid);
+	info.connection		= server->connection;
+	info.buffer_size	= server->buffer_size;
+	info.volume_number	= NCP_FINFO(inode)->volNumber;
+	info.directory_id	= NCP_FINFO(inode)->DosDirNum;
+
+	if (copy_to_user(arg, &info, sizeof(info)))
+		return -EFAULT;
+	return 0;
+}
+
+static int
+ncp_get_fs_info_v2(struct ncp_server* server, struct inode* inode, struct ncp_fs_info_v2 __user * arg)
+{
+	struct ncp_fs_info_v2 info2;
+
+	if ((permission(inode, MAY_WRITE, NULL) != 0)
+	    && (current->uid != server->m.mounted_uid)) {
+		return -EACCES;
+	}
+	if (copy_from_user(&info2, arg, sizeof(info2)))
+		return -EFAULT;
+
+	if (info2.version != NCP_GET_FS_INFO_VERSION_V2) {
+		DPRINTK("info.version invalid: %d\n", info2.version);
+		return -EINVAL;
+	}
+	info2.mounted_uid   = server->m.mounted_uid;
+	info2.connection    = server->connection;
+	info2.buffer_size   = server->buffer_size;
+	info2.volume_number = NCP_FINFO(inode)->volNumber;
+	info2.directory_id  = NCP_FINFO(inode)->DosDirNum;
+	info2.dummy1 = info2.dummy2 = info2.dummy3 = 0;
+
+	if (copy_to_user(arg, &info2, sizeof(info2)))
+		return -EFAULT;
+	return 0;
+}
+
+#ifdef CONFIG_NCPFS_NLS
+/* Here we are select the iocharset and the codepage for NLS.
+ * Thanks Petr Vandrovec for idea and many hints.
+ */
+static int
+ncp_set_charsets(struct ncp_server* server, struct ncp_nls_ioctl __user *arg)
+{
+	struct ncp_nls_ioctl user;
+	struct nls_table *codepage;
+	struct nls_table *iocharset;
+	struct nls_table *oldset_io;
+	struct nls_table *oldset_cp;
+
+	if (!capable(CAP_SYS_ADMIN))
+		return -EACCES;
+	if (server->root_setuped)
+		return -EBUSY;
+
+	if (copy_from_user(&user, arg, sizeof(user)))
+		return -EFAULT;
+
+	codepage = NULL;
+	user.codepage[NCP_IOCSNAME_LEN] = 0;
+	if (!user.codepage[0] || !strcmp(user.codepage, "default"))
+		codepage = load_nls_default();
+	else {
+		codepage = load_nls(user.codepage);
+		if (!codepage) {
+			return -EBADRQC;
+		}
+	}
+
+	iocharset = NULL;
+	user.iocharset[NCP_IOCSNAME_LEN] = 0;
+	if (!user.iocharset[0] || !strcmp(user.iocharset, "default")) {
+		iocharset = load_nls_default();
+		NCP_CLR_FLAG(server, NCP_FLAG_UTF8);
+	} else if (!strcmp(user.iocharset, "utf8")) {
+		iocharset = load_nls_default();
+		NCP_SET_FLAG(server, NCP_FLAG_UTF8);
+	} else {
+		iocharset = load_nls(user.iocharset);
+		if (!iocharset) {
+			unload_nls(codepage);
+			return -EBADRQC;
+		}
+		NCP_CLR_FLAG(server, NCP_FLAG_UTF8);
+	}
+
+	oldset_cp = server->nls_vol;
+	server->nls_vol = codepage;
+	oldset_io = server->nls_io;
+	server->nls_io = iocharset;
+
+	if (oldset_cp)
+		unload_nls(oldset_cp);
+	if (oldset_io)
+		unload_nls(oldset_io);
+
+	return 0;
+}
+
+static int
+ncp_get_charsets(struct ncp_server* server, struct ncp_nls_ioctl __user *arg)
+{
+	struct ncp_nls_ioctl user;
+	int len;
+
+	memset(&user, 0, sizeof(user));
+	if (server->nls_vol && server->nls_vol->charset) {
+		len = strlen(server->nls_vol->charset);
+		if (len > NCP_IOCSNAME_LEN)
+			len = NCP_IOCSNAME_LEN;
+		strncpy(user.codepage, server->nls_vol->charset, len);
+		user.codepage[len] = 0;
+	}
+
+	if (NCP_IS_FLAG(server, NCP_FLAG_UTF8))
+		strcpy(user.iocharset, "utf8");
+	else if (server->nls_io && server->nls_io->charset) {
+		len = strlen(server->nls_io->charset);
+		if (len > NCP_IOCSNAME_LEN)
+			len = NCP_IOCSNAME_LEN;
+		strncpy(user.iocharset,	server->nls_io->charset, len);
+		user.iocharset[len] = 0;
+	}
+
+	if (copy_to_user(arg, &user, sizeof(user)))
+		return -EFAULT;
+	return 0;
+}
+#endif /* CONFIG_NCPFS_NLS */
+
+int ncp_ioctl(struct inode *inode, struct file *filp,
+	      unsigned int cmd, unsigned long arg)
+{
+	struct ncp_server *server = NCP_SERVER(inode);
+	int result;
+	struct ncp_ioctl_request request;
+	char* bouncebuffer;
+	void __user *argp = (void __user *)arg;
+
+	switch (cmd) {
+	case NCP_IOC_NCPREQUEST:
+
+		if ((permission(inode, MAY_WRITE, NULL) != 0)
+		    && (current->uid != server->m.mounted_uid)) {
+			return -EACCES;
+		}
+		if (copy_from_user(&request, argp, sizeof(request)))
+			return -EFAULT;
+
+		if ((request.function > 255)
+		    || (request.size >
+		  NCP_PACKET_SIZE - sizeof(struct ncp_request_header))) {
+			return -EINVAL;
+		}
+		bouncebuffer = vmalloc(NCP_PACKET_SIZE_INTERNAL);
+		if (!bouncebuffer)
+			return -ENOMEM;
+		if (copy_from_user(bouncebuffer, request.data, request.size)) {
+			vfree(bouncebuffer);
+			return -EFAULT;
+		}
+		ncp_lock_server(server);
+
+		/* FIXME: We hack around in the server's structures
+		   here to be able to use ncp_request */
+
+		server->has_subfunction = 0;
+		server->current_size = request.size;
+		memcpy(server->packet, bouncebuffer, request.size);
+
+		result = ncp_request2(server, request.function, 
+			bouncebuffer, NCP_PACKET_SIZE_INTERNAL);
+		if (result < 0)
+			result = -EIO;
+		else
+			result = server->reply_size;
+		ncp_unlock_server(server);
+		DPRINTK("ncp_ioctl: copy %d bytes\n",
+			result);
+		if (result >= 0)
+			if (copy_to_user(request.data, bouncebuffer, result))
+				result = -EFAULT;
+		vfree(bouncebuffer);
+		return result;
+
+	case NCP_IOC_CONN_LOGGED_IN:
+
+		if (!capable(CAP_SYS_ADMIN))
+			return -EACCES;
+		if (!(server->m.int_flags & NCP_IMOUNT_LOGGEDIN_POSSIBLE))
+			return -EINVAL;
+		if (server->root_setuped)
+			return -EBUSY;
+		server->root_setuped = 1;
+		return ncp_conn_logged_in(inode->i_sb);
+
+	case NCP_IOC_GET_FS_INFO:
+		return ncp_get_fs_info(server, inode, argp);
+
+	case NCP_IOC_GET_FS_INFO_V2:
+		return ncp_get_fs_info_v2(server, inode, argp);
+
+	case NCP_IOC_GETMOUNTUID2:
+		{
+			unsigned long tmp = server->m.mounted_uid;
+
+			if (   (permission(inode, MAY_READ, NULL) != 0)
+			    && (current->uid != server->m.mounted_uid))
+			{
+				return -EACCES;
+			}
+			if (put_user(tmp, (unsigned long __user *)argp)) 
+				return -EFAULT;
+			return 0;
+		}
+
+	case NCP_IOC_GETROOT:
+		{
+			struct ncp_setroot_ioctl sr;
+
+			if (   (permission(inode, MAY_READ, NULL) != 0)
+			    && (current->uid != server->m.mounted_uid))
+			{
+				return -EACCES;
+			}
+			if (server->m.mounted_vol[0]) {
+				struct dentry* dentry = inode->i_sb->s_root;
+
+				if (dentry) {
+					struct inode* inode = dentry->d_inode;
+				
+					if (inode) {
+						sr.volNumber = NCP_FINFO(inode)->volNumber;
+						sr.dirEntNum = NCP_FINFO(inode)->dirEntNum;
+						sr.namespace = server->name_space[sr.volNumber];
+					} else
+						DPRINTK("ncpfs: s_root->d_inode==NULL\n");
+				} else
+					DPRINTK("ncpfs: s_root==NULL\n");
+			} else {
+				sr.volNumber = -1;
+				sr.namespace = 0;
+				sr.dirEntNum = 0;
+			}
+			if (copy_to_user(argp, &sr, sizeof(sr)))
+				return -EFAULT;
+			return 0;
+		}
+	case NCP_IOC_SETROOT:
+		{
+			struct ncp_setroot_ioctl sr;
+			__u32 vnum;
+			__le32 de;
+			__le32 dosde;
+			struct dentry* dentry;
+
+			if (!capable(CAP_SYS_ADMIN))
+			{
+				return -EACCES;
+			}
+			if (server->root_setuped) return -EBUSY;
+			if (copy_from_user(&sr, argp, sizeof(sr)))
+				return -EFAULT;
+			if (sr.volNumber < 0) {
+				server->m.mounted_vol[0] = 0;
+				vnum = NCP_NUMBER_OF_VOLUMES;
+				de = 0;
+				dosde = 0;
+			} else if (sr.volNumber >= NCP_NUMBER_OF_VOLUMES) {
+				return -EINVAL;
+			} else if (ncp_mount_subdir(server, sr.volNumber,
+						sr.namespace, sr.dirEntNum,
+						&vnum, &de, &dosde)) {
+				return -ENOENT;
+			}
+			
+			dentry = inode->i_sb->s_root;
+			server->root_setuped = 1;
+			if (dentry) {
+				struct inode* inode = dentry->d_inode;
+				
+				if (inode) {
+					NCP_FINFO(inode)->volNumber = vnum;
+					NCP_FINFO(inode)->dirEntNum = de;
+					NCP_FINFO(inode)->DosDirNum = dosde;
+				} else
+					DPRINTK("ncpfs: s_root->d_inode==NULL\n");
+			} else
+				DPRINTK("ncpfs: s_root==NULL\n");
+
+			return 0;
+		}
+
+#ifdef CONFIG_NCPFS_PACKET_SIGNING	
+	case NCP_IOC_SIGN_INIT:
+		if ((permission(inode, MAY_WRITE, NULL) != 0)
+		    && (current->uid != server->m.mounted_uid))
+		{
+			return -EACCES;
+		}
+		if (argp) {
+			if (server->sign_wanted)
+			{
+				struct ncp_sign_init sign;
+
+				if (copy_from_user(&sign, argp, sizeof(sign)))
+					return -EFAULT;
+				memcpy(server->sign_root,sign.sign_root,8);
+				memcpy(server->sign_last,sign.sign_last,16);
+				server->sign_active = 1;
+			}
+			/* ignore when signatures not wanted */
+		} else {
+			server->sign_active = 0;
+		}
+		return 0;		
+		
+        case NCP_IOC_SIGN_WANTED:
+		if (   (permission(inode, MAY_READ, NULL) != 0)
+		    && (current->uid != server->m.mounted_uid))
+		{
+			return -EACCES;
+		}
+		
+                if (put_user(server->sign_wanted, (int __user *)argp))
+			return -EFAULT;
+                return 0;
+	case NCP_IOC_SET_SIGN_WANTED:
+		{
+			int newstate;
+
+			if (   (permission(inode, MAY_WRITE, NULL) != 0)
+			    && (current->uid != server->m.mounted_uid))
+			{
+				return -EACCES;
+			}
+			/* get only low 8 bits... */
+			if (get_user(newstate, (unsigned char __user *)argp))
+				return -EFAULT;
+			if (server->sign_active) {
+				/* cannot turn signatures OFF when active */
+				if (!newstate) return -EINVAL;
+			} else {
+				server->sign_wanted = newstate != 0;
+			}
+			return 0;
+		}
+
+#endif /* CONFIG_NCPFS_PACKET_SIGNING */
+
+#ifdef CONFIG_NCPFS_IOCTL_LOCKING
+	case NCP_IOC_LOCKUNLOCK:
+		if (   (permission(inode, MAY_WRITE, NULL) != 0)
+		    && (current->uid != server->m.mounted_uid))
+		{
+			return -EACCES;
+		}
+		{
+			struct ncp_lock_ioctl	 rqdata;
+			int result;
+
+			if (copy_from_user(&rqdata, argp, sizeof(rqdata)))
+				return -EFAULT;
+			if (rqdata.origin != 0)
+				return -EINVAL;
+			/* check for cmd */
+			switch (rqdata.cmd) {
+				case NCP_LOCK_EX:
+				case NCP_LOCK_SH:
+						if (rqdata.timeout == 0)
+							rqdata.timeout = NCP_LOCK_DEFAULT_TIMEOUT;
+						else if (rqdata.timeout > NCP_LOCK_MAX_TIMEOUT)
+							rqdata.timeout = NCP_LOCK_MAX_TIMEOUT;
+						break;
+				case NCP_LOCK_LOG:
+						rqdata.timeout = NCP_LOCK_DEFAULT_TIMEOUT;	/* has no effect */
+				case NCP_LOCK_CLEAR:
+						break;
+				default:
+						return -EINVAL;
+			}
+			/* locking needs both read and write access */
+			if ((result = ncp_make_open(inode, O_RDWR)) != 0)
+			{
+				return result;
+			}
+			result = -EIO;
+			if (!ncp_conn_valid(server))
+				goto outrel;
+			result = -EISDIR;
+			if (!S_ISREG(inode->i_mode))
+				goto outrel;
+			if (rqdata.cmd == NCP_LOCK_CLEAR)
+			{
+				result = ncp_ClearPhysicalRecord(NCP_SERVER(inode),
+							NCP_FINFO(inode)->file_handle, 
+							rqdata.offset,
+							rqdata.length);
+				if (result > 0) result = 0;	/* no such lock */
+			}
+			else
+			{
+				int lockcmd;
+
+				switch (rqdata.cmd)
+				{
+					case NCP_LOCK_EX:  lockcmd=1; break;
+					case NCP_LOCK_SH:  lockcmd=3; break;
+					default:	   lockcmd=0; break;
+				}
+				result = ncp_LogPhysicalRecord(NCP_SERVER(inode),
+							NCP_FINFO(inode)->file_handle,
+							lockcmd,
+							rqdata.offset,
+							rqdata.length,
+							rqdata.timeout);
+				if (result > 0) result = -EAGAIN;
+			}
+outrel:			
+			ncp_inode_close(inode);
+			return result;
+		}
+#endif	/* CONFIG_NCPFS_IOCTL_LOCKING */
+
+	case NCP_IOC_GETOBJECTNAME:
+		if (current->uid != server->m.mounted_uid) {
+			return -EACCES;
+		}
+		{
+			struct ncp_objectname_ioctl user;
+			size_t outl;
+
+			if (copy_from_user(&user, argp, sizeof(user)))
+				return -EFAULT;
+			user.auth_type = server->auth.auth_type;
+			outl = user.object_name_len;
+			user.object_name_len = server->auth.object_name_len;
+			if (outl > user.object_name_len)
+				outl = user.object_name_len;
+			if (outl) {
+				if (copy_to_user(user.object_name,
+						 server->auth.object_name,
+						 outl)) return -EFAULT;
+			}
+			if (copy_to_user(argp, &user, sizeof(user)))
+				return -EFAULT;
+			return 0;
+		}
+	case NCP_IOC_SETOBJECTNAME:
+		if (current->uid != server->m.mounted_uid) {
+			return -EACCES;
+		}
+		{
+			struct ncp_objectname_ioctl user;
+			void* newname;
+			void* oldname;
+			size_t oldnamelen;
+			void* oldprivate;
+			size_t oldprivatelen;
+
+			if (copy_from_user(&user, argp, sizeof(user)))
+				return -EFAULT;
+			if (user.object_name_len > NCP_OBJECT_NAME_MAX_LEN)
+				return -ENOMEM;
+			if (user.object_name_len) {
+				newname = ncp_kmalloc(user.object_name_len, GFP_USER);
+				if (!newname) return -ENOMEM;
+				if (copy_from_user(newname, user.object_name, user.object_name_len)) {
+					ncp_kfree_s(newname, user.object_name_len);
+					return -EFAULT;
+				}
+			} else {
+				newname = NULL;
+			}
+			/* enter critical section */
+			/* maybe that kfree can sleep so do that this way */
+			/* it is at least more SMP friendly (in future...) */
+			oldname = server->auth.object_name;
+			oldnamelen = server->auth.object_name_len;
+			oldprivate = server->priv.data;
+			oldprivatelen = server->priv.len;
+			server->auth.auth_type = user.auth_type;
+			server->auth.object_name_len = user.object_name_len;
+			server->auth.object_name = newname;
+			server->priv.len = 0;
+			server->priv.data = NULL;
+			/* leave critical section */
+			if (oldprivate) ncp_kfree_s(oldprivate, oldprivatelen);
+			if (oldname) ncp_kfree_s(oldname, oldnamelen);
+			return 0;
+		}
+	case NCP_IOC_GETPRIVATEDATA:
+		if (current->uid != server->m.mounted_uid) {
+			return -EACCES;
+		}
+		{
+			struct ncp_privatedata_ioctl user;
+			size_t outl;
+
+			if (copy_from_user(&user, argp, sizeof(user)))
+				return -EFAULT;
+			outl = user.len;
+			user.len = server->priv.len;
+			if (outl > user.len) outl = user.len;
+			if (outl) {
+				if (copy_to_user(user.data,
+						 server->priv.data,
+						 outl)) return -EFAULT;
+			}
+			if (copy_to_user(argp, &user, sizeof(user)))
+				return -EFAULT;
+			return 0;
+		}
+	case NCP_IOC_SETPRIVATEDATA:
+		if (current->uid != server->m.mounted_uid) {
+			return -EACCES;
+		}
+		{
+			struct ncp_privatedata_ioctl user;
+			void* new;
+			void* old;
+			size_t oldlen;
+
+			if (copy_from_user(&user, argp, sizeof(user)))
+				return -EFAULT;
+			if (user.len > NCP_PRIVATE_DATA_MAX_LEN)
+				return -ENOMEM;
+			if (user.len) {
+				new = ncp_kmalloc(user.len, GFP_USER);
+				if (!new) return -ENOMEM;
+				if (copy_from_user(new, user.data, user.len)) {
+					ncp_kfree_s(new, user.len);
+					return -EFAULT;
+				}
+			} else {
+				new = NULL;
+			}
+			/* enter critical section */
+			old = server->priv.data;
+			oldlen = server->priv.len;
+			server->priv.len = user.len;
+			server->priv.data = new;
+			/* leave critical section */
+			if (old) ncp_kfree_s(old, oldlen);
+			return 0;
+		}
+
+#ifdef CONFIG_NCPFS_NLS
+	case NCP_IOC_SETCHARSETS:
+		return ncp_set_charsets(server, argp);
+		
+	case NCP_IOC_GETCHARSETS:
+		return ncp_get_charsets(server, argp);
+
+#endif /* CONFIG_NCPFS_NLS */
+
+	case NCP_IOC_SETDENTRYTTL:
+		if ((permission(inode, MAY_WRITE, NULL) != 0) &&
+				 (current->uid != server->m.mounted_uid))
+			return -EACCES;
+		{
+			u_int32_t user;
+
+			if (copy_from_user(&user, argp, sizeof(user)))
+				return -EFAULT;
+			/* 20 secs at most... */
+			if (user > 20000)
+				return -EINVAL;
+			user = (user * HZ) / 1000;
+			server->dentry_ttl = user;
+			return 0;
+		}
+		
+	case NCP_IOC_GETDENTRYTTL:
+		{
+			u_int32_t user = (server->dentry_ttl * 1000) / HZ;
+			if (copy_to_user(argp, &user, sizeof(user)))
+				return -EFAULT;
+			return 0;
+		}
+
+	}
+/* #ifdef CONFIG_UID16 */
+	/* NCP_IOC_GETMOUNTUID may be same as NCP_IOC_GETMOUNTUID2,
+           so we have this out of switch */
+	if (cmd == NCP_IOC_GETMOUNTUID) {
+		__kernel_uid_t uid = 0;
+		if ((permission(inode, MAY_READ, NULL) != 0)
+		    && (current->uid != server->m.mounted_uid)) {
+			return -EACCES;
+		}
+		SET_UID(uid, server->m.mounted_uid);
+		if (put_user(uid, (__kernel_uid_t __user *)argp))
+			return -EFAULT;
+		return 0;
+	}
+/* #endif */
+	return -EINVAL;
+}
diff --git a/fs/ncpfs/mmap.c b/fs/ncpfs/mmap.c
new file mode 100644
index 0000000..52d60c3
--- /dev/null
+++ b/fs/ncpfs/mmap.c
@@ -0,0 +1,128 @@
+/*
+ *  mmap.c
+ *
+ *  Copyright (C) 1995, 1996 by Volker Lendecke
+ *  Modified 1997 Peter Waltenberg, Bill Hawes, David Woodhouse for 2.1 dcache
+ *
+ */
+
+#include <linux/stat.h>
+#include <linux/time.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/shm.h>
+#include <linux/errno.h>
+#include <linux/mman.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/fcntl.h>
+#include <linux/ncp_fs.h>
+
+#include "ncplib_kernel.h"
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+/*
+ * Fill in the supplied page for mmap
+ */
+static struct page* ncp_file_mmap_nopage(struct vm_area_struct *area,
+				     unsigned long address, int *type)
+{
+	struct file *file = area->vm_file;
+	struct dentry *dentry = file->f_dentry;
+	struct inode *inode = dentry->d_inode;
+	struct page* page;
+	char *pg_addr;
+	unsigned int already_read;
+	unsigned int count;
+	int bufsize;
+	int pos;
+
+	page = alloc_page(GFP_HIGHUSER); /* ncpfs has nothing against high pages
+	           as long as recvmsg and memset works on it */
+	if (!page)
+		return page;
+	pg_addr = kmap(page);
+	address &= PAGE_MASK;
+	pos = address - area->vm_start + (area->vm_pgoff << PAGE_SHIFT);
+
+	count = PAGE_SIZE;
+	if (address + PAGE_SIZE > area->vm_end) {
+		count = area->vm_end - address;
+	}
+	/* what we can read in one go */
+	bufsize = NCP_SERVER(inode)->buffer_size;
+
+	already_read = 0;
+	if (ncp_make_open(inode, O_RDONLY) >= 0) {
+		while (already_read < count) {
+			int read_this_time;
+			int to_read;
+
+			to_read = bufsize - (pos % bufsize);
+
+			to_read = min_t(unsigned int, to_read, count - already_read);
+
+			if (ncp_read_kernel(NCP_SERVER(inode),
+				     NCP_FINFO(inode)->file_handle,
+				     pos, to_read,
+				     pg_addr + already_read,
+				     &read_this_time) != 0) {
+				read_this_time = 0;
+			}
+			pos += read_this_time;
+			already_read += read_this_time;
+
+			if (read_this_time < to_read) {
+				break;
+			}
+		}
+		ncp_inode_close(inode);
+
+	}
+
+	if (already_read < PAGE_SIZE)
+		memset(pg_addr + already_read, 0, PAGE_SIZE - already_read);
+	flush_dcache_page(page);
+	kunmap(page);
+
+	/*
+	 * If I understand ncp_read_kernel() properly, the above always
+	 * fetches from the network, here the analogue of disk.
+	 * -- wli
+	 */
+	if (type)
+		*type = VM_FAULT_MAJOR;
+	inc_page_state(pgmajfault);
+	return page;
+}
+
+static struct vm_operations_struct ncp_file_mmap =
+{
+	.nopage	= ncp_file_mmap_nopage,
+};
+
+
+/* This is used for a general mmap of a ncp file */
+int ncp_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	struct inode *inode = file->f_dentry->d_inode;
+	
+	DPRINTK("ncp_mmap: called\n");
+
+	if (!ncp_conn_valid(NCP_SERVER(inode)))
+		return -EIO;
+
+	/* only PAGE_COW or read-only supported now */
+	if (vma->vm_flags & VM_SHARED)
+		return -EINVAL;
+	/* we do not support files bigger than 4GB... We eventually 
+	   supports just 4GB... */
+	if (((vma->vm_end - vma->vm_start) >> PAGE_SHIFT) + vma->vm_pgoff 
+	   > (1U << (32 - PAGE_SHIFT)))
+		return -EFBIG;
+
+	vma->vm_ops = &ncp_file_mmap;
+	file_accessed(file);
+	return 0;
+}
diff --git a/fs/ncpfs/ncplib_kernel.c b/fs/ncpfs/ncplib_kernel.c
new file mode 100644
index 0000000..e4eb5ed
--- /dev/null
+++ b/fs/ncpfs/ncplib_kernel.c
@@ -0,0 +1,1355 @@
+/*
+ *  ncplib_kernel.c
+ *
+ *  Copyright (C) 1995, 1996 by Volker Lendecke
+ *  Modified for big endian by J.F. Chadima and David S. Miller
+ *  Modified 1997 Peter Waltenberg, Bill Hawes, David Woodhouse for 2.1 dcache
+ *  Modified 1999 Wolfram Pienkoss for NLS
+ *  Modified 2000 Ben Harris, University of Cambridge for NFS NS meta-info
+ *
+ */
+
+
+#include <linux/config.h>
+
+#include "ncplib_kernel.h"
+
+static inline void assert_server_locked(struct ncp_server *server)
+{
+	if (server->lock == 0) {
+		DPRINTK("ncpfs: server not locked!\n");
+	}
+}
+
+static void ncp_add_byte(struct ncp_server *server, __u8 x)
+{
+	assert_server_locked(server);
+	*(__u8 *) (&(server->packet[server->current_size])) = x;
+	server->current_size += 1;
+	return;
+}
+
+static void ncp_add_word(struct ncp_server *server, __le16 x)
+{
+	assert_server_locked(server);
+	put_unaligned(x, (__le16 *) (&(server->packet[server->current_size])));
+	server->current_size += 2;
+	return;
+}
+
+static void ncp_add_be16(struct ncp_server *server, __u16 x)
+{
+	assert_server_locked(server);
+	put_unaligned(cpu_to_be16(x), (__be16 *) (&(server->packet[server->current_size])));
+	server->current_size += 2;
+}
+
+static void ncp_add_dword(struct ncp_server *server, __le32 x)
+{
+	assert_server_locked(server);
+	put_unaligned(x, (__le32 *) (&(server->packet[server->current_size])));
+	server->current_size += 4;
+	return;
+}
+
+static void ncp_add_be32(struct ncp_server *server, __u32 x)
+{
+	assert_server_locked(server);
+	put_unaligned(cpu_to_be32(x), (__be32 *)(&(server->packet[server->current_size])));
+	server->current_size += 4;
+}
+
+static inline void ncp_add_dword_lh(struct ncp_server *server, __u32 x) {
+	ncp_add_dword(server, cpu_to_le32(x));
+}
+
+static void ncp_add_mem(struct ncp_server *server, const void *source, int size)
+{
+	assert_server_locked(server);
+	memcpy(&(server->packet[server->current_size]), source, size);
+	server->current_size += size;
+	return;
+}
+
+static void ncp_add_pstring(struct ncp_server *server, const char *s)
+{
+	int len = strlen(s);
+	assert_server_locked(server);
+	if (len > 255) {
+		DPRINTK("ncpfs: string too long: %s\n", s);
+		len = 255;
+	}
+	ncp_add_byte(server, len);
+	ncp_add_mem(server, s, len);
+	return;
+}
+
+static inline void ncp_init_request(struct ncp_server *server)
+{
+	ncp_lock_server(server);
+
+	server->current_size = sizeof(struct ncp_request_header);
+	server->has_subfunction = 0;
+}
+
+static inline void ncp_init_request_s(struct ncp_server *server, int subfunction)
+{
+	ncp_lock_server(server);
+	
+	server->current_size = sizeof(struct ncp_request_header) + 2;
+	ncp_add_byte(server, subfunction);
+
+	server->has_subfunction = 1;
+}
+
+static inline char *
+ ncp_reply_data(struct ncp_server *server, int offset)
+{
+	return &(server->packet[sizeof(struct ncp_reply_header) + offset]);
+}
+
+static inline __u8 BVAL(void* data)
+{
+	return get_unaligned((__u8*)data);
+}
+
+static __u8
+ ncp_reply_byte(struct ncp_server *server, int offset)
+{
+	return get_unaligned((__u8 *) ncp_reply_data(server, offset));
+}
+
+static inline __u16 WVAL_LH(void* data)
+{
+	return le16_to_cpu(get_unaligned((__le16*)data));
+}
+
+static __u16
+ ncp_reply_le16(struct ncp_server *server, int offset)
+{
+	return le16_to_cpu(get_unaligned((__le16 *) ncp_reply_data(server, offset)));
+}
+
+static __u16
+ ncp_reply_be16(struct ncp_server *server, int offset)
+{
+	return be16_to_cpu(get_unaligned((__be16 *) ncp_reply_data(server, offset)));
+}
+
+static inline __u32 DVAL_LH(void* data)
+{
+	return le32_to_cpu(get_unaligned((__le32*)data));
+}
+
+static __le32
+ ncp_reply_dword(struct ncp_server *server, int offset)
+{
+	return get_unaligned((__le32 *) ncp_reply_data(server, offset));
+}
+
+static inline __u32 ncp_reply_dword_lh(struct ncp_server* server, int offset) {
+	return le32_to_cpu(ncp_reply_dword(server, offset));
+}
+
+int
+ncp_negotiate_buffersize(struct ncp_server *server, int size, int *target)
+{
+	int result;
+
+	ncp_init_request(server);
+	ncp_add_be16(server, size);
+
+	if ((result = ncp_request(server, 33)) != 0) {
+		ncp_unlock_server(server);
+		return result;
+	}
+	*target = min_t(unsigned int, ncp_reply_be16(server, 0), size);
+
+	ncp_unlock_server(server);
+	return 0;
+}
+
+
+/* options: 
+ *	bit 0	ipx checksum
+ *	bit 1	packet signing
+ */
+int
+ncp_negotiate_size_and_options(struct ncp_server *server, 
+	int size, int options, int *ret_size, int *ret_options) {
+	int result;
+
+	/* there is minimum */
+	if (size < NCP_BLOCK_SIZE) size = NCP_BLOCK_SIZE;
+
+	ncp_init_request(server);
+	ncp_add_be16(server, size);
+	ncp_add_byte(server, options);
+	
+	if ((result = ncp_request(server, 0x61)) != 0)
+	{
+		ncp_unlock_server(server);
+		return result;
+	}
+
+	/* NCP over UDP returns 0 (!!!) */
+	result = ncp_reply_be16(server, 0);
+	if (result >= NCP_BLOCK_SIZE)
+		size = min(result, size);
+	*ret_size = size;
+	*ret_options = ncp_reply_byte(server, 4);
+
+	ncp_unlock_server(server);
+	return 0;
+}
+
+int ncp_get_volume_info_with_number(struct ncp_server* server,
+			     int n, struct ncp_volume_info* target) {
+	int result;
+	int len;
+
+	ncp_init_request_s(server, 44);
+	ncp_add_byte(server, n);
+
+	if ((result = ncp_request(server, 22)) != 0) {
+		goto out;
+	}
+	target->total_blocks = ncp_reply_dword_lh(server, 0);
+	target->free_blocks = ncp_reply_dword_lh(server, 4);
+	target->purgeable_blocks = ncp_reply_dword_lh(server, 8);
+	target->not_yet_purgeable_blocks = ncp_reply_dword_lh(server, 12);
+	target->total_dir_entries = ncp_reply_dword_lh(server, 16);
+	target->available_dir_entries = ncp_reply_dword_lh(server, 20);
+	target->sectors_per_block = ncp_reply_byte(server, 28);
+
+	memset(&(target->volume_name), 0, sizeof(target->volume_name));
+
+	result = -EIO;
+	len = ncp_reply_byte(server, 29);
+	if (len > NCP_VOLNAME_LEN) {
+		DPRINTK("ncpfs: volume name too long: %d\n", len);
+		goto out;
+	}
+	memcpy(&(target->volume_name), ncp_reply_data(server, 30), len);
+	result = 0;
+out:
+	ncp_unlock_server(server);
+	return result;
+}
+
+int ncp_get_directory_info(struct ncp_server* server, __u8 n, 
+			   struct ncp_volume_info* target) {
+	int result;
+	int len;
+
+	ncp_init_request_s(server, 45);
+	ncp_add_byte(server, n);
+
+	if ((result = ncp_request(server, 22)) != 0) {
+		goto out;
+	}
+	target->total_blocks = ncp_reply_dword_lh(server, 0);
+	target->free_blocks = ncp_reply_dword_lh(server, 4);
+	target->purgeable_blocks = 0;
+	target->not_yet_purgeable_blocks = 0;
+	target->total_dir_entries = ncp_reply_dword_lh(server, 8);
+	target->available_dir_entries = ncp_reply_dword_lh(server, 12);
+	target->sectors_per_block = ncp_reply_byte(server, 20);
+
+	memset(&(target->volume_name), 0, sizeof(target->volume_name));
+
+	result = -EIO;
+	len = ncp_reply_byte(server, 21);
+	if (len > NCP_VOLNAME_LEN) {
+		DPRINTK("ncpfs: volume name too long: %d\n", len);
+		goto out;
+	}
+	memcpy(&(target->volume_name), ncp_reply_data(server, 22), len);
+	result = 0;
+out:
+	ncp_unlock_server(server);
+	return result;
+}
+
+int
+ncp_close_file(struct ncp_server *server, const char *file_id)
+{
+	int result;
+
+	ncp_init_request(server);
+	ncp_add_byte(server, 0);
+	ncp_add_mem(server, file_id, 6);
+
+	result = ncp_request(server, 66);
+	ncp_unlock_server(server);
+	return result;
+}
+
+int
+ncp_make_closed(struct inode *inode)
+{
+	int err;
+
+	err = 0;
+	down(&NCP_FINFO(inode)->open_sem);	
+	if (atomic_read(&NCP_FINFO(inode)->opened) == 1) {
+		atomic_set(&NCP_FINFO(inode)->opened, 0);
+		err = ncp_close_file(NCP_SERVER(inode), NCP_FINFO(inode)->file_handle);
+
+		if (!err)
+			PPRINTK("ncp_make_closed: volnum=%d, dirent=%u, error=%d\n",
+				NCP_FINFO(inode)->volNumber,
+				NCP_FINFO(inode)->dirEntNum, err);
+	}
+	up(&NCP_FINFO(inode)->open_sem);
+	return err;
+}
+
+static void ncp_add_handle_path(struct ncp_server *server, __u8 vol_num,
+				__le32 dir_base, int have_dir_base, 
+				const char *path)
+{
+	ncp_add_byte(server, vol_num);
+	ncp_add_dword(server, dir_base);
+	if (have_dir_base != 0) {
+		ncp_add_byte(server, 1);	/* dir_base */
+	} else {
+		ncp_add_byte(server, 0xff);	/* no handle */
+	}
+	if (path != NULL) {
+		ncp_add_byte(server, 1);	/* 1 component */
+		ncp_add_pstring(server, path);
+	} else {
+		ncp_add_byte(server, 0);
+	}
+}
+
+int ncp_dirhandle_alloc(struct ncp_server* server, __u8 volnum, __le32 dirent,
+			__u8* dirhandle) {
+	int result;
+
+	ncp_init_request(server);
+	ncp_add_byte(server, 12);		/* subfunction */
+	ncp_add_byte(server, NW_NS_DOS);
+	ncp_add_byte(server, 0);
+	ncp_add_word(server, 0);
+	ncp_add_handle_path(server, volnum, dirent, 1, NULL);
+	if ((result = ncp_request(server, 87)) == 0) {
+		*dirhandle = ncp_reply_byte(server, 0);
+	}
+	ncp_unlock_server(server);
+	return result;
+}
+
+int ncp_dirhandle_free(struct ncp_server* server, __u8 dirhandle) {
+	int result;
+	
+	ncp_init_request_s(server, 20);
+	ncp_add_byte(server, dirhandle);
+	result = ncp_request(server, 22);
+	ncp_unlock_server(server);
+	return result;
+}
+
+void ncp_extract_file_info(void *structure, struct nw_info_struct *target)
+{
+	__u8 *name_len;
+	const int info_struct_size = offsetof(struct nw_info_struct, nameLen);
+
+	memcpy(target, structure, info_struct_size);
+	name_len = structure + info_struct_size;
+	target->nameLen = *name_len;
+	memcpy(target->entryName, name_len + 1, *name_len);
+	target->entryName[*name_len] = '\0';
+	target->volNumber = le32_to_cpu(target->volNumber);
+	return;
+}
+
+#ifdef CONFIG_NCPFS_NFS_NS
+static inline void ncp_extract_nfs_info(unsigned char *structure,
+				 struct nw_nfs_info *target)
+{
+	target->mode = DVAL_LH(structure);
+	target->rdev = DVAL_LH(structure + 8);
+}
+#endif
+
+int ncp_obtain_nfs_info(struct ncp_server *server,
+		        struct nw_info_struct *target)
+
+{
+	int result = 0;
+#ifdef CONFIG_NCPFS_NFS_NS
+	__u32 volnum = target->volNumber;
+
+	if (ncp_is_nfs_extras(server, volnum)) {
+		ncp_init_request(server);
+		ncp_add_byte(server, 19);	/* subfunction */
+		ncp_add_byte(server, server->name_space[volnum]);
+		ncp_add_byte(server, NW_NS_NFS);
+		ncp_add_byte(server, 0);
+		ncp_add_byte(server, volnum);
+		ncp_add_dword(server, target->dirEntNum);
+		/* We must retrieve both nlinks and rdev, otherwise some server versions
+		   report zeroes instead of valid data */
+		ncp_add_dword_lh(server, NSIBM_NFS_MODE | NSIBM_NFS_NLINKS | NSIBM_NFS_RDEV);
+
+		if ((result = ncp_request(server, 87)) == 0) {
+			ncp_extract_nfs_info(ncp_reply_data(server, 0), &target->nfs);
+			DPRINTK(KERN_DEBUG
+				"ncp_obtain_nfs_info: (%s) mode=0%o, rdev=0x%x\n",
+				target->entryName, target->nfs.mode,
+				target->nfs.rdev);
+		} else {
+			target->nfs.mode = 0;
+			target->nfs.rdev = 0;
+		}
+	        ncp_unlock_server(server);
+
+	} else
+#endif
+	{
+		target->nfs.mode = 0;
+		target->nfs.rdev = 0;
+	}
+	return result;
+}
+
+/*
+ * Returns information for a (one-component) name relative to
+ * the specified directory.
+ */
+int ncp_obtain_info(struct ncp_server *server, struct inode *dir, char *path,
+			struct nw_info_struct *target)
+{
+	__u8  volnum = NCP_FINFO(dir)->volNumber;
+	__le32 dirent = NCP_FINFO(dir)->dirEntNum;
+	int result;
+
+	if (target == NULL) {
+		printk(KERN_ERR "ncp_obtain_info: invalid call\n");
+		return -EINVAL;
+	}
+	ncp_init_request(server);
+	ncp_add_byte(server, 6);	/* subfunction */
+	ncp_add_byte(server, server->name_space[volnum]);
+	ncp_add_byte(server, server->name_space[volnum]); /* N.B. twice ?? */
+	ncp_add_word(server, cpu_to_le16(0x8006));	/* get all */
+	ncp_add_dword(server, RIM_ALL);
+	ncp_add_handle_path(server, volnum, dirent, 1, path);
+
+	if ((result = ncp_request(server, 87)) != 0)
+		goto out;
+	ncp_extract_file_info(ncp_reply_data(server, 0), target);
+	ncp_unlock_server(server);
+	
+	result = ncp_obtain_nfs_info(server, target);
+	return result;
+
+out:
+	ncp_unlock_server(server);
+	return result;
+}
+
+#ifdef CONFIG_NCPFS_NFS_NS
+static int
+ncp_obtain_DOS_dir_base(struct ncp_server *server,
+		__u8 volnum, __le32 dirent,
+		char *path, /* At most 1 component */
+		__le32 *DOS_dir_base)
+{
+	int result;
+
+	ncp_init_request(server);
+	ncp_add_byte(server, 6); /* subfunction */
+	ncp_add_byte(server, server->name_space[volnum]);
+	ncp_add_byte(server, server->name_space[volnum]);
+	ncp_add_word(server, cpu_to_le16(0x8006)); /* get all */
+	ncp_add_dword(server, RIM_DIRECTORY);
+	ncp_add_handle_path(server, volnum, dirent, 1, path);
+
+	if ((result = ncp_request(server, 87)) == 0)
+	{
+	   	if (DOS_dir_base) *DOS_dir_base=ncp_reply_dword(server, 0x34);
+	}
+	ncp_unlock_server(server);
+	return result;
+}
+#endif /* CONFIG_NCPFS_NFS_NS */
+
+static inline int
+ncp_get_known_namespace(struct ncp_server *server, __u8 volume)
+{
+#if defined(CONFIG_NCPFS_OS2_NS) || defined(CONFIG_NCPFS_NFS_NS)
+	int result;
+	__u8 *namespace;
+	__u16 no_namespaces;
+
+	ncp_init_request(server);
+	ncp_add_byte(server, 24);	/* Subfunction: Get Name Spaces Loaded */
+	ncp_add_word(server, 0);
+	ncp_add_byte(server, volume);
+
+	if ((result = ncp_request(server, 87)) != 0) {
+		ncp_unlock_server(server);
+		return NW_NS_DOS; /* not result ?? */
+	}
+
+	result = NW_NS_DOS;
+	no_namespaces = ncp_reply_le16(server, 0);
+	namespace = ncp_reply_data(server, 2);
+
+	while (no_namespaces > 0) {
+		DPRINTK("get_namespaces: found %d on %d\n", *namespace, volume);
+
+#ifdef CONFIG_NCPFS_NFS_NS
+		if ((*namespace == NW_NS_NFS) && !(server->m.flags&NCP_MOUNT_NO_NFS)) 
+		{
+			result = NW_NS_NFS;
+			break;
+		}
+#endif	/* CONFIG_NCPFS_NFS_NS */
+#ifdef CONFIG_NCPFS_OS2_NS
+		if ((*namespace == NW_NS_OS2) && !(server->m.flags&NCP_MOUNT_NO_OS2))
+		{
+			result = NW_NS_OS2;
+		}
+#endif	/* CONFIG_NCPFS_OS2_NS */
+		namespace += 1;
+		no_namespaces -= 1;
+	}
+	ncp_unlock_server(server);
+	return result;
+#else	/* neither OS2 nor NFS - only DOS */
+	return NW_NS_DOS;
+#endif	/* defined(CONFIG_NCPFS_OS2_NS) || defined(CONFIG_NCPFS_NFS_NS) */
+}
+
+static int
+ncp_ObtainSpecificDirBase(struct ncp_server *server,
+		__u8 nsSrc, __u8 nsDst, __u8 vol_num, __le32 dir_base,
+		char *path, /* At most 1 component */
+		__le32 *dirEntNum, __le32 *DosDirNum)
+{
+	int result;
+
+	ncp_init_request(server);
+	ncp_add_byte(server, 6); /* subfunction */
+	ncp_add_byte(server, nsSrc);
+	ncp_add_byte(server, nsDst);
+	ncp_add_word(server, cpu_to_le16(0x8006)); /* get all */
+	ncp_add_dword(server, RIM_ALL);
+	ncp_add_handle_path(server, vol_num, dir_base, 1, path);
+
+	if ((result = ncp_request(server, 87)) != 0)
+	{
+		ncp_unlock_server(server);
+		return result;
+	}
+
+	if (dirEntNum)
+		*dirEntNum = ncp_reply_dword(server, 0x30);
+	if (DosDirNum)
+		*DosDirNum = ncp_reply_dword(server, 0x34);
+	ncp_unlock_server(server);
+	return 0;
+}
+
+int
+ncp_mount_subdir(struct ncp_server *server,
+		 __u8 volNumber, __u8 srcNS, __le32 dirEntNum,
+		 __u32* volume, __le32* newDirEnt, __le32* newDosEnt)
+{
+	int dstNS;
+	int result;
+	
+	dstNS = ncp_get_known_namespace(server, volNumber);
+	if ((result = ncp_ObtainSpecificDirBase(server, srcNS, dstNS, volNumber, 
+				      dirEntNum, NULL, newDirEnt, newDosEnt)) != 0)
+	{
+		return result;
+	}
+	server->name_space[volNumber] = dstNS;
+	*volume = volNumber;
+	server->m.mounted_vol[1] = 0;
+	server->m.mounted_vol[0] = 'X';
+	return 0;
+}
+
+int 
+ncp_get_volume_root(struct ncp_server *server, const char *volname,
+		    __u32* volume, __le32* dirent, __le32* dosdirent)
+{
+	int result;
+	__u8 volnum;
+
+	DPRINTK("ncp_get_volume_root: looking up vol %s\n", volname);
+
+	ncp_init_request(server);
+	ncp_add_byte(server, 22);	/* Subfunction: Generate dir handle */
+	ncp_add_byte(server, 0);	/* DOS namespace */
+	ncp_add_byte(server, 0);	/* reserved */
+	ncp_add_byte(server, 0);	/* reserved */
+	ncp_add_byte(server, 0);	/* reserved */
+
+	ncp_add_byte(server, 0);	/* faked volume number */
+	ncp_add_dword(server, 0);	/* faked dir_base */
+	ncp_add_byte(server, 0xff);	/* Don't have a dir_base */
+	ncp_add_byte(server, 1);	/* 1 path component */
+	ncp_add_pstring(server, volname);
+
+	if ((result = ncp_request(server, 87)) != 0) {
+		ncp_unlock_server(server);
+		return result;
+	}
+	*dirent = *dosdirent = ncp_reply_dword(server, 4);
+	volnum = ncp_reply_byte(server, 8);
+	ncp_unlock_server(server);
+	*volume = volnum;
+
+	server->name_space[volnum] = ncp_get_known_namespace(server, volnum);
+
+	DPRINTK("lookup_vol: namespace[%d] = %d\n",
+		volnum, server->name_space[volnum]);
+
+	return 0;
+}
+
+int
+ncp_lookup_volume(struct ncp_server *server, const char *volname,
+		  struct nw_info_struct *target)
+{
+	int result;
+
+	memset(target, 0, sizeof(*target));
+	result = ncp_get_volume_root(server, volname,
+			&target->volNumber, &target->dirEntNum, &target->DosDirNum);
+	if (result) {
+		return result;
+	}
+	target->nameLen = strlen(volname);
+	memcpy(target->entryName, volname, target->nameLen+1);
+	target->attributes = aDIR;
+	/* set dates to Jan 1, 1986  00:00 */
+	target->creationTime = target->modifyTime = cpu_to_le16(0x0000);
+	target->creationDate = target->modifyDate = target->lastAccessDate = cpu_to_le16(0x0C21);
+	target->nfs.mode = 0;
+	return 0;
+}
+
+int ncp_modify_file_or_subdir_dos_info_path(struct ncp_server *server,
+					    struct inode *dir,
+					    const char *path,
+					    __le32 info_mask,
+					    const struct nw_modify_dos_info *info)
+{
+	__u8  volnum = NCP_FINFO(dir)->volNumber;
+	__le32 dirent = NCP_FINFO(dir)->dirEntNum;
+	int result;
+
+	ncp_init_request(server);
+	ncp_add_byte(server, 7);	/* subfunction */
+	ncp_add_byte(server, server->name_space[volnum]);
+	ncp_add_byte(server, 0);	/* reserved */
+	ncp_add_word(server, cpu_to_le16(0x8006));	/* search attribs: all */
+
+	ncp_add_dword(server, info_mask);
+	ncp_add_mem(server, info, sizeof(*info));
+	ncp_add_handle_path(server, volnum, dirent, 1, path);
+
+	result = ncp_request(server, 87);
+	ncp_unlock_server(server);
+	return result;
+}
+
+int ncp_modify_file_or_subdir_dos_info(struct ncp_server *server,
+				       struct inode *dir,
+				       __le32 info_mask,
+				       const struct nw_modify_dos_info *info)
+{
+	return ncp_modify_file_or_subdir_dos_info_path(server, dir, NULL,
+		info_mask, info);
+}
+
+#ifdef CONFIG_NCPFS_NFS_NS
+int ncp_modify_nfs_info(struct ncp_server *server, __u8 volnum, __le32 dirent,
+			       __u32 mode, __u32 rdev)
+
+{
+	int result = 0;
+
+	if (server->name_space[volnum] == NW_NS_NFS) {
+		ncp_init_request(server);
+		ncp_add_byte(server, 25);	/* subfunction */
+		ncp_add_byte(server, server->name_space[volnum]);
+		ncp_add_byte(server, NW_NS_NFS);
+		ncp_add_byte(server, volnum);
+		ncp_add_dword(server, dirent);
+		/* we must always operate on both nlinks and rdev, otherwise
+		   rdev is not set */
+		ncp_add_dword_lh(server, NSIBM_NFS_MODE | NSIBM_NFS_NLINKS | NSIBM_NFS_RDEV);
+		ncp_add_dword_lh(server, mode);
+		ncp_add_dword_lh(server, 1);	/* nlinks */
+		ncp_add_dword_lh(server, rdev);
+		result = ncp_request(server, 87);
+		ncp_unlock_server(server);
+	}
+	return result;
+}
+#endif
+
+
+static int
+ncp_DeleteNSEntry(struct ncp_server *server,
+		  __u8 have_dir_base, __u8 volnum, __le32 dirent,
+		  char* name, __u8 ns, __le16 attr)
+{
+	int result;
+
+	ncp_init_request(server);
+	ncp_add_byte(server, 8);	/* subfunction */
+	ncp_add_byte(server, ns);
+	ncp_add_byte(server, 0);	/* reserved */
+	ncp_add_word(server, attr);	/* search attribs: all */
+	ncp_add_handle_path(server, volnum, dirent, have_dir_base, name);
+
+	result = ncp_request(server, 87);
+	ncp_unlock_server(server);
+	return result;
+}
+
+int
+ncp_del_file_or_subdir2(struct ncp_server *server,
+			struct dentry *dentry)
+{
+	struct inode *inode = dentry->d_inode;
+	__u8  volnum;
+	__le32 dirent;
+
+	if (!inode) {
+#ifdef CONFIG_NCPFS_DEBUGDENTRY
+		PRINTK("ncpfs: ncpdel2: dentry->d_inode == NULL\n");
+#endif
+		return 0xFF;	/* Any error */
+	}
+	volnum = NCP_FINFO(inode)->volNumber;
+	dirent = NCP_FINFO(inode)->DosDirNum;
+	return ncp_DeleteNSEntry(server, 1, volnum, dirent, NULL, NW_NS_DOS, cpu_to_le16(0x8006));
+}
+
+int
+ncp_del_file_or_subdir(struct ncp_server *server,
+		       struct inode *dir, char *name)
+{
+	__u8  volnum = NCP_FINFO(dir)->volNumber;
+	__le32 dirent = NCP_FINFO(dir)->dirEntNum;
+
+#ifdef CONFIG_NCPFS_NFS_NS
+	if (server->name_space[volnum]==NW_NS_NFS)
+ 	{
+ 		int result;
+ 
+ 		result=ncp_obtain_DOS_dir_base(server, volnum, dirent, name, &dirent);
+ 		if (result) return result;
+ 		return ncp_DeleteNSEntry(server, 1, volnum, dirent, NULL, NW_NS_DOS, cpu_to_le16(0x8006));
+ 	}
+ 	else
+#endif	/* CONFIG_NCPFS_NFS_NS */
+ 		return ncp_DeleteNSEntry(server, 1, volnum, dirent, name, server->name_space[volnum], cpu_to_le16(0x8006));
+}
+
+static inline void ConvertToNWfromDWORD(__u16 v0, __u16 v1, __u8 ret[6])
+{
+	__le16 *dest = (__le16 *) ret;
+	dest[1] = cpu_to_le16(v0);
+	dest[2] = cpu_to_le16(v1);
+	dest[0] = cpu_to_le16(v0 + 1);
+	return;
+}
+
+/* If both dir and name are NULL, then in target there's already a
+   looked-up entry that wants to be opened. */
+int ncp_open_create_file_or_subdir(struct ncp_server *server,
+				   struct inode *dir, char *name,
+				   int open_create_mode,
+				   __le32 create_attributes,
+				   __le16 desired_acc_rights,
+				   struct ncp_entry_info *target)
+{
+	__le16 search_attribs = cpu_to_le16(0x0006);
+	__u8  volnum;
+	__le32 dirent;
+	int result;
+
+	volnum = NCP_FINFO(dir)->volNumber;
+	dirent = NCP_FINFO(dir)->dirEntNum;
+
+	if ((create_attributes & aDIR) != 0) {
+		search_attribs |= cpu_to_le16(0x8000);
+	}
+	ncp_init_request(server);
+	ncp_add_byte(server, 1);	/* subfunction */
+	ncp_add_byte(server, server->name_space[volnum]);
+	ncp_add_byte(server, open_create_mode);
+	ncp_add_word(server, search_attribs);
+	ncp_add_dword(server, RIM_ALL);
+	ncp_add_dword(server, create_attributes);
+	/* The desired acc rights seem to be the inherited rights mask
+	   for directories */
+	ncp_add_word(server, desired_acc_rights);
+	ncp_add_handle_path(server, volnum, dirent, 1, name);
+
+	if ((result = ncp_request(server, 87)) != 0)
+		goto out;
+	if (!(create_attributes & aDIR))
+		target->opened = 1;
+
+	/* in target there's a new finfo to fill */
+	ncp_extract_file_info(ncp_reply_data(server, 6), &(target->i));
+	target->volume = target->i.volNumber;
+	ConvertToNWfromDWORD(ncp_reply_le16(server, 0),
+			     ncp_reply_le16(server, 2),
+			     target->file_handle);
+	
+	ncp_unlock_server(server);
+
+	(void)ncp_obtain_nfs_info(server, &(target->i));
+	return 0;
+
+out:
+	ncp_unlock_server(server);
+	return result;
+}
+
+int
+ncp_initialize_search(struct ncp_server *server, struct inode *dir,
+			struct nw_search_sequence *target)
+{
+	__u8  volnum = NCP_FINFO(dir)->volNumber;
+	__le32 dirent = NCP_FINFO(dir)->dirEntNum;
+	int result;
+
+	ncp_init_request(server);
+	ncp_add_byte(server, 2);	/* subfunction */
+	ncp_add_byte(server, server->name_space[volnum]);
+	ncp_add_byte(server, 0);	/* reserved */
+	ncp_add_handle_path(server, volnum, dirent, 1, NULL);
+
+	result = ncp_request(server, 87);
+	if (result)
+		goto out;
+	memcpy(target, ncp_reply_data(server, 0), sizeof(*target));
+
+out:
+	ncp_unlock_server(server);
+	return result;
+}
+
+/* Search for everything */
+int ncp_search_for_file_or_subdir(struct ncp_server *server,
+				  struct nw_search_sequence *seq,
+				  struct nw_info_struct *target)
+{
+	int result;
+
+	ncp_init_request(server);
+	ncp_add_byte(server, 3);	/* subfunction */
+	ncp_add_byte(server, server->name_space[seq->volNumber]);
+	ncp_add_byte(server, 0);	/* data stream (???) */
+	ncp_add_word(server, cpu_to_le16(0x8006));	/* Search attribs */
+	ncp_add_dword(server, RIM_ALL);		/* return info mask */
+	ncp_add_mem(server, seq, 9);
+#ifdef CONFIG_NCPFS_NFS_NS
+	if (server->name_space[seq->volNumber] == NW_NS_NFS) {
+		ncp_add_byte(server, 0);	/* 0 byte pattern */
+	} else 
+#endif
+	{
+		ncp_add_byte(server, 2);	/* 2 byte pattern */
+		ncp_add_byte(server, 0xff);	/* following is a wildcard */
+		ncp_add_byte(server, '*');
+	}
+	
+	if ((result = ncp_request(server, 87)) != 0)
+		goto out;
+	memcpy(seq, ncp_reply_data(server, 0), sizeof(*seq));
+	ncp_extract_file_info(ncp_reply_data(server, 10), target);
+
+	ncp_unlock_server(server);
+	
+	result = ncp_obtain_nfs_info(server, target);
+	return result;
+
+out:
+	ncp_unlock_server(server);
+	return result;
+}
+
+int ncp_search_for_fileset(struct ncp_server *server,
+			   struct nw_search_sequence *seq,
+			   int* more,
+			   int* cnt,
+			   char* buffer,
+			   size_t bufsize,
+			   char** rbuf,
+			   size_t* rsize)
+{
+	int result;
+
+	ncp_init_request(server);
+	ncp_add_byte(server, 20);
+	ncp_add_byte(server, server->name_space[seq->volNumber]);
+	ncp_add_byte(server, 0);		/* datastream */
+	ncp_add_word(server, cpu_to_le16(0x8006));
+	ncp_add_dword(server, RIM_ALL);
+	ncp_add_word(server, cpu_to_le16(32767));	/* max returned items */
+	ncp_add_mem(server, seq, 9);
+#ifdef CONFIG_NCPFS_NFS_NS
+	if (server->name_space[seq->volNumber] == NW_NS_NFS) {
+		ncp_add_byte(server, 0);	/* 0 byte pattern */
+	} else 
+#endif
+	{
+		ncp_add_byte(server, 2);	/* 2 byte pattern */
+		ncp_add_byte(server, 0xff);	/* following is a wildcard */
+		ncp_add_byte(server, '*');
+	}
+	result = ncp_request2(server, 87, buffer, bufsize);
+	if (result) {
+		ncp_unlock_server(server);
+		return result;
+	}
+	if (server->ncp_reply_size < 12) {
+		ncp_unlock_server(server);
+		return 0xFF;
+	}
+	*rsize = server->ncp_reply_size - 12;
+	ncp_unlock_server(server);
+	buffer = buffer + sizeof(struct ncp_reply_header);
+	*rbuf = buffer + 12;
+	*cnt = WVAL_LH(buffer + 10);
+	*more = BVAL(buffer + 9);
+	memcpy(seq, buffer, 9);
+	return 0;
+}
+
+static int
+ncp_RenameNSEntry(struct ncp_server *server,
+		  struct inode *old_dir, char *old_name, __le16 old_type,
+		  struct inode *new_dir, char *new_name)
+{
+	int result = -EINVAL;
+
+	if ((old_dir == NULL) || (old_name == NULL) ||
+	    (new_dir == NULL) || (new_name == NULL))
+		goto out;
+
+	ncp_init_request(server);
+	ncp_add_byte(server, 4);	/* subfunction */
+	ncp_add_byte(server, server->name_space[NCP_FINFO(old_dir)->volNumber]);
+	ncp_add_byte(server, 1);	/* rename flag */
+	ncp_add_word(server, old_type);	/* search attributes */
+
+	/* source Handle Path */
+	ncp_add_byte(server, NCP_FINFO(old_dir)->volNumber);
+	ncp_add_dword(server, NCP_FINFO(old_dir)->dirEntNum);
+	ncp_add_byte(server, 1);
+	ncp_add_byte(server, 1);	/* 1 source component */
+
+	/* dest Handle Path */
+	ncp_add_byte(server, NCP_FINFO(new_dir)->volNumber);
+	ncp_add_dword(server, NCP_FINFO(new_dir)->dirEntNum);
+	ncp_add_byte(server, 1);
+	ncp_add_byte(server, 1);	/* 1 destination component */
+
+	/* source path string */
+	ncp_add_pstring(server, old_name);
+	/* dest path string */
+	ncp_add_pstring(server, new_name);
+
+	result = ncp_request(server, 87);
+	ncp_unlock_server(server);
+out:
+	return result;
+}
+
+int ncp_ren_or_mov_file_or_subdir(struct ncp_server *server,
+				struct inode *old_dir, char *old_name,
+				struct inode *new_dir, char *new_name)
+{
+        int result;
+        __le16 old_type = cpu_to_le16(0x06);
+
+/* If somebody can do it atomic, call me... vandrove@vc.cvut.cz */
+	result = ncp_RenameNSEntry(server, old_dir, old_name, old_type,
+	                                   new_dir, new_name);
+        if (result == 0xFF)	/* File Not Found, try directory */
+	{
+		old_type = cpu_to_le16(0x16);
+		result = ncp_RenameNSEntry(server, old_dir, old_name, old_type,
+						   new_dir, new_name);
+	}
+	if (result != 0x92) return result;	/* All except NO_FILES_RENAMED */
+	result = ncp_del_file_or_subdir(server, new_dir, new_name);
+	if (result != 0) return -EACCES;
+	result = ncp_RenameNSEntry(server, old_dir, old_name, old_type,
+					   new_dir, new_name);
+	return result;
+}
+	
+
+/* We have to transfer to/from user space */
+int
+ncp_read_kernel(struct ncp_server *server, const char *file_id,
+	     __u32 offset, __u16 to_read, char *target, int *bytes_read)
+{
+	char *source;
+	int result;
+
+	ncp_init_request(server);
+	ncp_add_byte(server, 0);
+	ncp_add_mem(server, file_id, 6);
+	ncp_add_be32(server, offset);
+	ncp_add_be16(server, to_read);
+
+	if ((result = ncp_request(server, 72)) != 0) {
+		goto out;
+	}
+	*bytes_read = ncp_reply_be16(server, 0);
+	source = ncp_reply_data(server, 2 + (offset & 1));
+
+	memcpy(target, source, *bytes_read);
+out:
+	ncp_unlock_server(server);
+	return result;
+}
+
+/* There is a problem... egrep and some other silly tools do:
+	x = mmap(NULL, MAP_PRIVATE, PROT_READ|PROT_WRITE, <ncpfs fd>, 32768);
+	read(<ncpfs fd>, x, 32768);
+   Now copying read result by copy_to_user causes pagefault. This pagefault
+   could not be handled because of server was locked due to read. So we have
+   to use temporary buffer. So ncp_unlock_server must be done before
+   copy_to_user (and for write, copy_from_user must be done before 
+   ncp_init_request... same applies for send raw packet ioctl). Because of
+   file is normally read in bigger chunks, caller provides kmalloced 
+   (vmalloced) chunk of memory with size >= to_read...
+ */
+int
+ncp_read_bounce(struct ncp_server *server, const char *file_id,
+	 __u32 offset, __u16 to_read, char __user *target, int *bytes_read,
+	 void* bounce, __u32 bufsize)
+{
+	int result;
+
+	ncp_init_request(server);
+	ncp_add_byte(server, 0);
+	ncp_add_mem(server, file_id, 6);
+	ncp_add_be32(server, offset);
+	ncp_add_be16(server, to_read);
+	result = ncp_request2(server, 72, bounce, bufsize);
+	ncp_unlock_server(server);
+	if (!result) {
+		int len = be16_to_cpu(get_unaligned((__be16*)((char*)bounce + 
+			  sizeof(struct ncp_reply_header))));
+		result = -EIO;
+		if (len <= to_read) {
+			char* source;
+
+			source = (char*)bounce + 
+			         sizeof(struct ncp_reply_header) + 2 + 
+			         (offset & 1);
+			*bytes_read = len;
+			result = 0;
+			if (copy_to_user(target, source, len))
+				result = -EFAULT;
+		}
+	}
+	return result;
+}
+
+int
+ncp_write_kernel(struct ncp_server *server, const char *file_id,
+		 __u32 offset, __u16 to_write,
+		 const char *source, int *bytes_written)
+{
+	int result;
+
+	ncp_init_request(server);
+	ncp_add_byte(server, 0);
+	ncp_add_mem(server, file_id, 6);
+	ncp_add_be32(server, offset);
+	ncp_add_be16(server, to_write);
+	ncp_add_mem(server, source, to_write);
+	
+	if ((result = ncp_request(server, 73)) == 0)
+		*bytes_written = to_write;
+	ncp_unlock_server(server);
+	return result;
+}
+
+#ifdef CONFIG_NCPFS_IOCTL_LOCKING
+int
+ncp_LogPhysicalRecord(struct ncp_server *server, const char *file_id,
+	  __u8 locktype, __u32 offset, __u32 length, __u16 timeout)
+{
+	int result;
+
+	ncp_init_request(server);
+	ncp_add_byte(server, locktype);
+	ncp_add_mem(server, file_id, 6);
+	ncp_add_be32(server, offset);
+	ncp_add_be32(server, length);
+	ncp_add_be16(server, timeout);
+
+	if ((result = ncp_request(server, 0x1A)) != 0)
+	{
+		ncp_unlock_server(server);
+		return result;
+	}
+	ncp_unlock_server(server);
+	return 0;
+}
+
+int
+ncp_ClearPhysicalRecord(struct ncp_server *server, const char *file_id,
+	  __u32 offset, __u32 length)
+{
+	int result;
+
+	ncp_init_request(server);
+	ncp_add_byte(server, 0);	/* who knows... lanalyzer says that */
+	ncp_add_mem(server, file_id, 6);
+	ncp_add_be32(server, offset);
+	ncp_add_be32(server, length);
+
+	if ((result = ncp_request(server, 0x1E)) != 0)
+	{
+		ncp_unlock_server(server);
+		return result;
+	}
+	ncp_unlock_server(server);
+	return 0;
+}
+#endif	/* CONFIG_NCPFS_IOCTL_LOCKING */
+
+#ifdef CONFIG_NCPFS_NLS
+/* This are the NLS conversion routines with inspirations and code parts
+ * from the vfat file system and hints from Petr Vandrovec.
+ */
+
+int
+ncp__io2vol(struct ncp_server *server, unsigned char *vname, unsigned int *vlen,
+		const unsigned char *iname, unsigned int ilen, int cc)
+{
+	struct nls_table *in = server->nls_io;
+	struct nls_table *out = server->nls_vol;
+	unsigned char *vname_start;
+	unsigned char *vname_end;
+	const unsigned char *iname_end;
+
+	iname_end = iname + ilen;
+	vname_start = vname;
+	vname_end = vname + *vlen - 1;
+
+	while (iname < iname_end) {
+		int chl;
+		wchar_t ec;
+
+		if (NCP_IS_FLAG(server, NCP_FLAG_UTF8)) {
+			int k;
+
+			k = utf8_mbtowc(&ec, iname, iname_end - iname);
+			if (k < 0)
+				return -EINVAL;
+			iname += k;
+		} else {
+			if (*iname == NCP_ESC) {
+				int k;
+
+				if (iname_end - iname < 5)
+					goto nospec;
+
+				ec = 0;
+				for (k = 1; k < 5; k++) {
+					unsigned char nc;
+
+					nc = iname[k] - '0';
+					if (nc >= 10) {
+						nc -= 'A' - '0' - 10;
+						if ((nc < 10) || (nc > 15)) {
+							goto nospec;
+						}
+					}
+					ec = (ec << 4) | nc;
+				}
+				iname += 5;
+			} else {
+nospec:;			
+				if ( (chl = in->char2uni(iname, iname_end - iname, &ec)) < 0)
+					return chl;
+				iname += chl;
+			}
+		}
+
+		/* unitoupper should be here! */
+
+		chl = out->uni2char(ec, vname, vname_end - vname);
+		if (chl < 0)
+			return chl;
+
+		/* this is wrong... */
+		if (cc) {
+			int chi;
+
+			for (chi = 0; chi < chl; chi++){
+				vname[chi] = ncp_toupper(out, vname[chi]);
+			}
+		}
+		vname += chl;
+	}
+
+	*vname = 0;
+	*vlen = vname - vname_start;
+	return 0;
+}
+
+int
+ncp__vol2io(struct ncp_server *server, unsigned char *iname, unsigned int *ilen,
+		const unsigned char *vname, unsigned int vlen, int cc)
+{
+	struct nls_table *in = server->nls_vol;
+	struct nls_table *out = server->nls_io;
+	const unsigned char *vname_end;
+	unsigned char *iname_start;
+	unsigned char *iname_end;
+	unsigned char *vname_cc;
+	int err;
+
+	vname_cc = NULL;
+
+	if (cc) {
+		int i;
+
+		/* this is wrong! */
+		vname_cc = kmalloc(vlen, GFP_KERNEL);
+		if (!vname_cc)
+			return -ENOMEM;
+		for (i = 0; i < vlen; i++)
+			vname_cc[i] = ncp_tolower(in, vname[i]);
+		vname = vname_cc;
+	}
+
+	iname_start = iname;
+	iname_end = iname + *ilen - 1;
+	vname_end = vname + vlen;
+
+	while (vname < vname_end) {
+		wchar_t ec;
+		int chl;
+
+		if ( (chl = in->char2uni(vname, vname_end - vname, &ec)) < 0) {
+			err = chl;
+			goto quit;
+		}
+		vname += chl;
+
+		/* unitolower should be here! */
+
+		if (NCP_IS_FLAG(server, NCP_FLAG_UTF8)) {
+			int k;
+
+			k = utf8_wctomb(iname, ec, iname_end - iname);
+			if (k < 0) {
+				err = -ENAMETOOLONG;
+				goto quit;
+			}
+			iname += k;
+		} else {
+			if ( (chl = out->uni2char(ec, iname, iname_end - iname)) >= 0) {
+				iname += chl;
+			} else {
+				int k;
+
+				if (iname_end - iname < 5) {
+					err = -ENAMETOOLONG;
+					goto quit;
+				}
+				*iname = NCP_ESC;
+				for (k = 4; k > 0; k--) {
+					unsigned char v;
+					
+					v = (ec & 0xF) + '0';
+					if (v > '9') {
+						v += 'A' - '9' - 1;
+					}
+					iname[k] = v;
+					ec >>= 4;
+				}
+				iname += 5;
+			}
+		}
+	}
+
+	*iname = 0;
+	*ilen = iname - iname_start;
+	err = 0;
+quit:;
+	if (cc)
+		kfree(vname_cc);
+	return err;
+}
+
+#else
+
+int
+ncp__io2vol(unsigned char *vname, unsigned int *vlen,
+		const unsigned char *iname, unsigned int ilen, int cc)
+{
+	int i;
+
+	if (*vlen <= ilen)
+		return -ENAMETOOLONG;
+
+	if (cc)
+		for (i = 0; i < ilen; i++) {
+			*vname = toupper(*iname);
+			vname++;
+			iname++;
+		}
+	else {
+		memmove(vname, iname, ilen);
+		vname += ilen;
+	}
+
+	*vlen = ilen;
+	*vname = 0;
+	return 0;
+}
+
+int
+ncp__vol2io(unsigned char *iname, unsigned int *ilen,
+		const unsigned char *vname, unsigned int vlen, int cc)
+{
+	int i;
+
+	if (*ilen <= vlen)
+		return -ENAMETOOLONG;
+
+	if (cc)
+		for (i = 0; i < vlen; i++) {
+			*iname = tolower(*vname);
+			iname++;
+			vname++;
+		}
+	else {
+		memmove(iname, vname, vlen);
+		iname += vlen;
+	}
+
+	*ilen = vlen;
+	*iname = 0;
+	return 0;
+}
+
+#endif
diff --git a/fs/ncpfs/ncplib_kernel.h b/fs/ncpfs/ncplib_kernel.h
new file mode 100644
index 0000000..05ec2e9
--- /dev/null
+++ b/fs/ncpfs/ncplib_kernel.h
@@ -0,0 +1,259 @@
+/*
+ *  ncplib_kernel.h
+ *
+ *  Copyright (C) 1995, 1996 by Volker Lendecke
+ *  Modified for big endian by J.F. Chadima and David S. Miller
+ *  Modified 1997 Peter Waltenberg, Bill Hawes, David Woodhouse for 2.1 dcache
+ *  Modified 1998, 1999 Wolfram Pienkoss for NLS
+ *  Modified 1999 Wolfram Pienkoss for directory caching
+ *
+ */
+
+#ifndef _NCPLIB_H
+#define _NCPLIB_H
+
+#include <linux/config.h>
+
+#include <linux/fs.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/stat.h>
+#include <linux/fcntl.h>
+#include <linux/pagemap.h>
+
+#include <asm/uaccess.h>
+#include <asm/byteorder.h>
+#include <asm/unaligned.h>
+#include <asm/string.h>
+
+#ifdef CONFIG_NCPFS_NLS
+#include <linux/nls.h>
+#else
+#include <linux/ctype.h>
+#endif /* CONFIG_NCPFS_NLS */
+
+#include <linux/ncp_fs.h>
+
+#define NCP_MIN_SYMLINK_SIZE	8
+#define NCP_MAX_SYMLINK_SIZE	512
+
+#define NCP_BLOCK_SHIFT		9
+#define NCP_BLOCK_SIZE		(1 << (NCP_BLOCK_SHIFT))
+
+int ncp_negotiate_buffersize(struct ncp_server *, int, int *);
+int ncp_negotiate_size_and_options(struct ncp_server *server, int size,
+  			  int options, int *ret_size, int *ret_options);
+
+int ncp_get_volume_info_with_number(struct ncp_server* server, int n,
+				    struct ncp_volume_info *target);
+
+int ncp_get_directory_info(struct ncp_server* server, __u8 dirhandle,
+			   struct ncp_volume_info* target);
+
+int ncp_close_file(struct ncp_server *, const char *);
+static inline int ncp_read_bounce_size(__u32 size) {
+	return sizeof(struct ncp_reply_header) + 2 + 2 + size + 8;
+};
+int ncp_read_bounce(struct ncp_server *, const char *, __u32, __u16, 
+		char __user *, int *, void* bounce, __u32 bouncelen);
+int ncp_read_kernel(struct ncp_server *, const char *, __u32, __u16, 
+		char *, int *);
+int ncp_write_kernel(struct ncp_server *, const char *, __u32, __u16,
+		const char *, int *);
+
+static inline void ncp_inode_close(struct inode *inode) {
+	atomic_dec(&NCP_FINFO(inode)->opened);
+}
+
+void ncp_extract_file_info(void* src, struct nw_info_struct* target);
+int ncp_obtain_info(struct ncp_server *server, struct inode *, char *,
+		struct nw_info_struct *target);
+int ncp_obtain_nfs_info(struct ncp_server *server, struct nw_info_struct *target);
+int ncp_get_volume_root(struct ncp_server *server, const char *volname,
+			__u32 *volume, __le32 *dirent, __le32 *dosdirent);
+int ncp_lookup_volume(struct ncp_server *, const char *, struct nw_info_struct *);
+int ncp_modify_file_or_subdir_dos_info(struct ncp_server *, struct inode *,
+	 __le32, const struct nw_modify_dos_info *info);
+int ncp_modify_file_or_subdir_dos_info_path(struct ncp_server *, struct inode *,
+	 const char* path, __le32, const struct nw_modify_dos_info *info);
+int ncp_modify_nfs_info(struct ncp_server *, __u8 volnum, __le32 dirent,
+			__u32 mode, __u32 rdev);
+
+int ncp_del_file_or_subdir2(struct ncp_server *, struct dentry*);
+int ncp_del_file_or_subdir(struct ncp_server *, struct inode *, char *);
+int ncp_open_create_file_or_subdir(struct ncp_server *, struct inode *, char *,
+				int, __le32, __le16, struct ncp_entry_info *);
+
+int ncp_initialize_search(struct ncp_server *, struct inode *,
+		      struct nw_search_sequence *target);
+int ncp_search_for_file_or_subdir(struct ncp_server *server,
+			      struct nw_search_sequence *seq,
+			      struct nw_info_struct *target);
+int ncp_search_for_fileset(struct ncp_server *server,
+			   struct nw_search_sequence *seq,
+			   int* more, int* cnt,
+			   char* buffer, size_t bufsize,
+			   char** rbuf, size_t* rsize);
+
+int ncp_ren_or_mov_file_or_subdir(struct ncp_server *server,
+			      struct inode *, char *, struct inode *, char *);
+
+
+int
+ncp_LogPhysicalRecord(struct ncp_server *server,
+		      const char *file_id, __u8 locktype,
+		      __u32 offset, __u32 length, __u16 timeout);
+
+#ifdef CONFIG_NCPFS_IOCTL_LOCKING
+int
+ncp_ClearPhysicalRecord(struct ncp_server *server,
+			const char *file_id,
+			__u32 offset, __u32 length);
+#endif	/* CONFIG_NCPFS_IOCTL_LOCKING */
+
+int
+ncp_mount_subdir(struct ncp_server *, __u8, __u8, __le32,
+		 __u32* volume, __le32* dirent, __le32* dosdirent);
+int ncp_dirhandle_alloc(struct ncp_server *, __u8 vol, __le32 dirent, __u8 *dirhandle);
+int ncp_dirhandle_free(struct ncp_server *, __u8 dirhandle);
+
+int ncp_create_new(struct inode *dir, struct dentry *dentry,
+                          int mode, dev_t rdev, __le32 attributes);
+
+static inline int ncp_is_nfs_extras(struct ncp_server* server, unsigned int volnum) {
+#ifdef CONFIG_NCPFS_NFS_NS
+	return (server->m.flags & NCP_MOUNT_NFS_EXTRAS) &&
+	       (server->name_space[volnum] == NW_NS_NFS);
+#else
+	return 0;
+#endif
+}
+
+#ifdef CONFIG_NCPFS_NLS
+
+int ncp__io2vol(struct ncp_server *, unsigned char *, unsigned int *,
+				const unsigned char *, unsigned int, int);
+int ncp__vol2io(struct ncp_server *, unsigned char *, unsigned int *,
+				const unsigned char *, unsigned int, int);
+
+#define NCP_ESC			':'
+#define NCP_IO_TABLE(dentry)	(NCP_SERVER((dentry)->d_inode)->nls_io)
+#define ncp_tolower(t, c)	nls_tolower(t, c)
+#define ncp_toupper(t, c)	nls_toupper(t, c)
+#define ncp_strnicmp(t, s1, s2, len) \
+	nls_strnicmp(t, s1, s2, len)
+#define ncp_io2vol(S,m,i,n,k,U)	ncp__io2vol(S,m,i,n,k,U)
+#define ncp_vol2io(S,m,i,n,k,U)	ncp__vol2io(S,m,i,n,k,U)
+
+#else
+
+int ncp__io2vol(unsigned char *, unsigned int *,
+				const unsigned char *, unsigned int, int);
+int ncp__vol2io(unsigned char *, unsigned int *,
+				const unsigned char *, unsigned int, int);
+
+#define NCP_IO_TABLE(dentry)	NULL
+#define ncp_tolower(t, c)	tolower(c)
+#define ncp_toupper(t, c)	toupper(c)
+#define ncp_io2vol(S,m,i,n,k,U)	ncp__io2vol(m,i,n,k,U)
+#define ncp_vol2io(S,m,i,n,k,U)	ncp__vol2io(m,i,n,k,U)
+
+
+static inline int ncp_strnicmp(struct nls_table *t, const unsigned char *s1,
+		const unsigned char *s2, int len)
+{
+	while (len--) {
+		if (tolower(*s1++) != tolower(*s2++))
+			return 1;
+	}
+
+	return 0;
+}
+
+#endif /* CONFIG_NCPFS_NLS */
+
+#define NCP_GET_AGE(dentry)	(jiffies - (dentry)->d_time)
+#define NCP_MAX_AGE(server)	((server)->dentry_ttl)
+#define NCP_TEST_AGE(server,dentry)	(NCP_GET_AGE(dentry) < NCP_MAX_AGE(server))
+
+static inline void
+ncp_age_dentry(struct ncp_server* server, struct dentry* dentry)
+{
+	dentry->d_time = jiffies - server->dentry_ttl;
+}
+
+static inline void
+ncp_new_dentry(struct dentry* dentry)
+{
+	dentry->d_time = jiffies;
+}
+
+static inline void
+ncp_renew_dentries(struct dentry *parent)
+{
+	struct ncp_server *server = NCP_SERVER(parent->d_inode);
+	struct list_head *next;
+	struct dentry *dentry;
+
+	spin_lock(&dcache_lock);
+	next = parent->d_subdirs.next;
+	while (next != &parent->d_subdirs) {
+		dentry = list_entry(next, struct dentry, d_child);
+
+		if (dentry->d_fsdata == NULL)
+			ncp_age_dentry(server, dentry);
+		else
+			ncp_new_dentry(dentry);
+
+		next = next->next;
+	}
+	spin_unlock(&dcache_lock);
+}
+
+static inline void
+ncp_invalidate_dircache_entries(struct dentry *parent)
+{
+	struct ncp_server *server = NCP_SERVER(parent->d_inode);
+	struct list_head *next;
+	struct dentry *dentry;
+
+	spin_lock(&dcache_lock);
+	next = parent->d_subdirs.next;
+	while (next != &parent->d_subdirs) {
+		dentry = list_entry(next, struct dentry, d_child);
+		dentry->d_fsdata = NULL;
+		ncp_age_dentry(server, dentry);
+		next = next->next;
+	}
+	spin_unlock(&dcache_lock);
+}
+
+struct ncp_cache_head {
+	time_t		mtime;
+	unsigned long	time;	/* cache age */
+	unsigned long	end;	/* last valid fpos in cache */
+	int		eof;
+};
+
+#define NCP_DIRCACHE_SIZE	((int)(PAGE_CACHE_SIZE/sizeof(struct dentry *)))
+union ncp_dir_cache {
+	struct ncp_cache_head	head;
+	struct dentry		*dentry[NCP_DIRCACHE_SIZE];
+};
+
+#define NCP_FIRSTCACHE_SIZE	((int)((NCP_DIRCACHE_SIZE * \
+	sizeof(struct dentry *) - sizeof(struct ncp_cache_head)) / \
+	sizeof(struct dentry *)))
+
+#define NCP_DIRCACHE_START	(NCP_DIRCACHE_SIZE - NCP_FIRSTCACHE_SIZE)
+
+struct ncp_cache_control {
+	struct	ncp_cache_head		head;
+	struct	page			*page;
+	union	ncp_dir_cache		*cache;
+	unsigned long			fpos, ofs;
+	int				filled, valid, idx;
+};
+
+#endif /* _NCPLIB_H */
diff --git a/fs/ncpfs/ncpsign_kernel.c b/fs/ncpfs/ncpsign_kernel.c
new file mode 100644
index 0000000..a6ec90c
--- /dev/null
+++ b/fs/ncpfs/ncpsign_kernel.c
@@ -0,0 +1,127 @@
+/*
+ *  ncpsign_kernel.c
+ *
+ *  Arne de Bruijn (arne@knoware.nl), 1997
+ *
+ */
+
+#include <linux/config.h>
+
+#ifdef CONFIG_NCPFS_PACKET_SIGNING
+
+#include <linux/string.h>
+#include <linux/ncp.h>
+#include <linux/bitops.h>
+#include "ncpsign_kernel.h"
+
+/* i386: 32-bit, little endian, handles mis-alignment */
+#ifdef __i386__
+#define GET_LE32(p) (*(int *)(p))
+#define PUT_LE32(p,v) { *(int *)(p)=v; }
+#else
+/* from include/ncplib.h */
+#define BVAL(buf,pos) (((__u8 *)(buf))[pos])
+#define PVAL(buf,pos) ((unsigned)BVAL(buf,pos))
+#define BSET(buf,pos,val) (BVAL(buf,pos) = (val))
+
+static inline __u16
+WVAL_LH(__u8 * buf, int pos)
+{
+	return PVAL(buf, pos) | PVAL(buf, pos + 1) << 8;
+}
+static inline __u32
+DVAL_LH(__u8 * buf, int pos)
+{
+	return WVAL_LH(buf, pos) | WVAL_LH(buf, pos + 2) << 16;
+}
+static inline void
+WSET_LH(__u8 * buf, int pos, __u16 val)
+{
+	BSET(buf, pos, val & 0xff);
+	BSET(buf, pos + 1, val >> 8);
+}
+static inline void
+DSET_LH(__u8 * buf, int pos, __u32 val)
+{
+	WSET_LH(buf, pos, val & 0xffff);
+	WSET_LH(buf, pos + 2, val >> 16);
+}
+
+#define GET_LE32(p) DVAL_LH(p,0)
+#define PUT_LE32(p,v) DSET_LH(p,0,v)
+#endif
+
+static void nwsign(char *r_data1, char *r_data2, char *outdata) {
+ int i;
+ unsigned int w0,w1,w2,w3;
+ static int rbit[4]={0, 2, 1, 3};
+#ifdef __i386__
+ unsigned int *data2=(int *)r_data2;
+#else
+ unsigned int data2[16];
+ for (i=0;i<16;i++)
+  data2[i]=GET_LE32(r_data2+(i<<2));
+#endif 
+ w0=GET_LE32(r_data1);
+ w1=GET_LE32(r_data1+4);
+ w2=GET_LE32(r_data1+8);
+ w3=GET_LE32(r_data1+12);
+ for (i=0;i<16;i+=4) {
+  w0=rol32(w0 + ((w1 & w2) | ((~w1) & w3)) + data2[i+0],3);
+  w3=rol32(w3 + ((w0 & w1) | ((~w0) & w2)) + data2[i+1],7);
+  w2=rol32(w2 + ((w3 & w0) | ((~w3) & w1)) + data2[i+2],11);
+  w1=rol32(w1 + ((w2 & w3) | ((~w2) & w0)) + data2[i+3],19);
+ }
+ for (i=0;i<4;i++) {
+  w0=rol32(w0 + (((w2 | w3) & w1) | (w2 & w3)) + 0x5a827999 + data2[i+0],3);
+  w3=rol32(w3 + (((w1 | w2) & w0) | (w1 & w2)) + 0x5a827999 + data2[i+4],5);
+  w2=rol32(w2 + (((w0 | w1) & w3) | (w0 & w1)) + 0x5a827999 + data2[i+8],9);
+  w1=rol32(w1 + (((w3 | w0) & w2) | (w3 & w0)) + 0x5a827999 + data2[i+12],13);
+ }
+ for (i=0;i<4;i++) {
+  w0=rol32(w0 + ((w1 ^ w2) ^ w3) + 0x6ed9eba1 + data2[rbit[i]+0],3);
+  w3=rol32(w3 + ((w0 ^ w1) ^ w2) + 0x6ed9eba1 + data2[rbit[i]+8],9);
+  w2=rol32(w2 + ((w3 ^ w0) ^ w1) + 0x6ed9eba1 + data2[rbit[i]+4],11);
+  w1=rol32(w1 + ((w2 ^ w3) ^ w0) + 0x6ed9eba1 + data2[rbit[i]+12],15);
+ }
+ PUT_LE32(outdata,(w0+GET_LE32(r_data1)) & 0xffffffff);
+ PUT_LE32(outdata+4,(w1+GET_LE32(r_data1+4)) & 0xffffffff);
+ PUT_LE32(outdata+8,(w2+GET_LE32(r_data1+8)) & 0xffffffff);
+ PUT_LE32(outdata+12,(w3+GET_LE32(r_data1+12)) & 0xffffffff);
+}
+
+/* Make a signature for the current packet and add it at the end of the */
+/* packet. */
+void __sign_packet(struct ncp_server *server, const char *packet, size_t size, __u32 totalsize, void *sign_buff) {
+	unsigned char data[64];
+
+	memcpy(data, server->sign_root, 8);
+	*(__u32*)(data + 8) = totalsize;
+	if (size < 52) {
+		memcpy(data + 12, packet, size);
+		memset(data + 12 + size, 0, 52 - size);
+	} else {
+		memcpy(data + 12, packet, 52);
+	}
+	nwsign(server->sign_last, data, server->sign_last);
+	memcpy(sign_buff, server->sign_last, 8);
+}
+
+int sign_verify_reply(struct ncp_server *server, const char *packet, size_t size, __u32 totalsize, const void *sign_buff) {
+	unsigned char data[64];
+	unsigned char hash[16];
+
+	memcpy(data, server->sign_root, 8);
+	*(__u32*)(data + 8) = totalsize;
+	if (size < 52) {
+		memcpy(data + 12, packet, size);
+		memset(data + 12 + size, 0, 52 - size);
+	} else {
+		memcpy(data + 12, packet, 52);
+	}
+	nwsign(server->sign_last, data, hash);
+	return memcmp(sign_buff, hash, 8);
+}
+
+#endif	/* CONFIG_NCPFS_PACKET_SIGNING */
+
diff --git a/fs/ncpfs/ncpsign_kernel.h b/fs/ncpfs/ncpsign_kernel.h
new file mode 100644
index 0000000..6451a68
--- /dev/null
+++ b/fs/ncpfs/ncpsign_kernel.h
@@ -0,0 +1,28 @@
+/*
+ *  ncpsign_kernel.h
+ *
+ *  Arne de Bruijn (arne@knoware.nl), 1997
+ *
+ */
+ 
+#ifndef _NCPSIGN_KERNEL_H
+#define _NCPSIGN_KERNEL_H
+
+#include <linux/ncp_fs.h>
+
+#ifdef CONFIG_NCPFS_PACKET_SIGNING
+void __sign_packet(struct ncp_server *server, const char *data, size_t size, __u32 totalsize, void *sign_buff);
+int sign_verify_reply(struct ncp_server *server, const char *data, size_t size, __u32 totalsize, const void *sign_buff);
+#endif
+
+static inline size_t sign_packet(struct ncp_server *server, const char *data, size_t size, __u32 totalsize, void *sign_buff) {
+#ifdef CONFIG_NCPFS_PACKET_SIGNING
+	if (server->sign_active) {
+		__sign_packet(server, data, size, totalsize, sign_buff);
+		return 8;
+	}
+#endif
+	return 0;
+}
+
+#endif
diff --git a/fs/ncpfs/sock.c b/fs/ncpfs/sock.c
new file mode 100644
index 0000000..6593a5c
--- /dev/null
+++ b/fs/ncpfs/sock.c
@@ -0,0 +1,850 @@
+/*
+ *  linux/fs/ncpfs/sock.c
+ *
+ *  Copyright (C) 1992, 1993  Rick Sladkey
+ *
+ *  Modified 1995, 1996 by Volker Lendecke to be usable for ncp
+ *  Modified 1997 Peter Waltenberg, Bill Hawes, David Woodhouse for 2.1 dcache
+ *
+ */
+
+#include <linux/config.h>
+
+#include <linux/time.h>
+#include <linux/errno.h>
+#include <linux/socket.h>
+#include <linux/fcntl.h>
+#include <linux/stat.h>
+#include <asm/uaccess.h>
+#include <linux/in.h>
+#include <linux/net.h>
+#include <linux/mm.h>
+#include <linux/netdevice.h>
+#include <linux/signal.h>
+#include <net/scm.h>
+#include <net/sock.h>
+#include <linux/ipx.h>
+#include <linux/poll.h>
+#include <linux/file.h>
+
+#include <linux/ncp_fs.h>
+
+#include "ncpsign_kernel.h"
+
+static int _recv(struct socket *sock, void *buf, int size, unsigned flags)
+{
+	struct msghdr msg = {NULL, };
+	struct kvec iov = {buf, size};
+	return kernel_recvmsg(sock, &msg, &iov, 1, size, flags);
+}
+
+static inline int do_send(struct socket *sock, struct kvec *vec, int count,
+			  int len, unsigned flags)
+{
+	struct msghdr msg = { .msg_flags = flags };
+	return kernel_sendmsg(sock, &msg, vec, count, len);
+}
+
+static int _send(struct socket *sock, const void *buff, int len)
+{
+	struct kvec vec;
+	vec.iov_base = (void *) buff;
+	vec.iov_len = len;
+	return do_send(sock, &vec, 1, len, 0);
+}
+
+struct ncp_request_reply {
+	struct list_head req;
+	wait_queue_head_t wq;
+	struct ncp_reply_header* reply_buf;
+	size_t datalen;
+	int result;
+	enum { RQ_DONE, RQ_INPROGRESS, RQ_QUEUED, RQ_IDLE } status;
+	struct kvec* tx_ciov;
+	size_t tx_totallen;
+	size_t tx_iovlen;
+	struct kvec tx_iov[3];
+	u_int16_t tx_type;
+	u_int32_t sign[6];
+};
+
+void ncp_tcp_data_ready(struct sock *sk, int len)
+{
+	struct ncp_server *server = sk->sk_user_data;
+
+	server->data_ready(sk, len);
+	schedule_work(&server->rcv.tq);
+}
+
+void ncp_tcp_error_report(struct sock *sk)
+{
+	struct ncp_server *server = sk->sk_user_data;
+	
+	server->error_report(sk);
+	schedule_work(&server->rcv.tq);
+}
+
+void ncp_tcp_write_space(struct sock *sk)
+{
+	struct ncp_server *server = sk->sk_user_data;
+	
+	/* We do not need any locking: we first set tx.creq, and then we do sendmsg,
+	   not vice versa... */
+	server->write_space(sk);
+	if (server->tx.creq)
+		schedule_work(&server->tx.tq);
+}
+
+void ncpdgram_timeout_call(unsigned long v)
+{
+	struct ncp_server *server = (void*)v;
+	
+	schedule_work(&server->timeout_tq);
+}
+
+static inline void ncp_finish_request(struct ncp_request_reply *req, int result)
+{
+	req->result = result;
+	req->status = RQ_DONE;
+	wake_up_all(&req->wq);
+}
+
+static void __abort_ncp_connection(struct ncp_server *server, struct ncp_request_reply *aborted, int err)
+{
+	struct ncp_request_reply *req;
+
+	ncp_invalidate_conn(server);
+	del_timer(&server->timeout_tm);
+	while (!list_empty(&server->tx.requests)) {
+		req = list_entry(server->tx.requests.next, struct ncp_request_reply, req);
+		
+		list_del_init(&req->req);
+		if (req == aborted) {
+			ncp_finish_request(req, err);
+		} else {
+			ncp_finish_request(req, -EIO);
+		}
+	}
+	req = server->rcv.creq;
+	if (req) {
+		server->rcv.creq = NULL;
+		if (req == aborted) {
+			ncp_finish_request(req, err);
+		} else {
+			ncp_finish_request(req, -EIO);
+		}
+		server->rcv.ptr = NULL;
+		server->rcv.state = 0;
+	}
+	req = server->tx.creq;
+	if (req) {
+		server->tx.creq = NULL;
+		if (req == aborted) {
+			ncp_finish_request(req, err);
+		} else {
+			ncp_finish_request(req, -EIO);
+		}
+	}
+}
+
+static inline int get_conn_number(struct ncp_reply_header *rp)
+{
+	return rp->conn_low | (rp->conn_high << 8);
+}
+
+static inline void __ncp_abort_request(struct ncp_server *server, struct ncp_request_reply *req, int err)
+{
+	/* If req is done, we got signal, but we also received answer... */
+	switch (req->status) {
+		case RQ_IDLE:
+		case RQ_DONE:
+			break;
+		case RQ_QUEUED:
+			list_del_init(&req->req);
+			ncp_finish_request(req, err);
+			break;
+		case RQ_INPROGRESS:
+			__abort_ncp_connection(server, req, err);
+			break;
+	}
+}
+
+static inline void ncp_abort_request(struct ncp_server *server, struct ncp_request_reply *req, int err)
+{
+	down(&server->rcv.creq_sem);
+	__ncp_abort_request(server, req, err);
+	up(&server->rcv.creq_sem);
+}
+
+static inline void __ncptcp_abort(struct ncp_server *server)
+{
+	__abort_ncp_connection(server, NULL, 0);
+}
+
+static int ncpdgram_send(struct socket *sock, struct ncp_request_reply *req)
+{
+	struct kvec vec[3];
+	/* sock_sendmsg updates iov pointers for us :-( */
+	memcpy(vec, req->tx_ciov, req->tx_iovlen * sizeof(vec[0]));
+	return do_send(sock, vec, req->tx_iovlen,
+		       req->tx_totallen, MSG_DONTWAIT);
+}
+
+static void __ncptcp_try_send(struct ncp_server *server)
+{
+	struct ncp_request_reply *rq;
+	struct kvec *iov;
+	struct kvec iovc[3];
+	int result;
+
+	rq = server->tx.creq;
+	if (!rq)
+		return;
+
+	/* sock_sendmsg updates iov pointers for us :-( */
+	memcpy(iovc, rq->tx_ciov, rq->tx_iovlen * sizeof(iov[0]));
+	result = do_send(server->ncp_sock, iovc, rq->tx_iovlen,
+			 rq->tx_totallen, MSG_NOSIGNAL | MSG_DONTWAIT);
+
+	if (result == -EAGAIN)
+		return;
+
+	if (result < 0) {
+		printk(KERN_ERR "ncpfs: tcp: Send failed: %d\n", result);
+		__ncp_abort_request(server, rq, result);
+		return;
+	}
+	if (result >= rq->tx_totallen) {
+		server->rcv.creq = rq;
+		server->tx.creq = NULL;
+		return;
+	}
+	rq->tx_totallen -= result;
+	iov = rq->tx_ciov;
+	while (iov->iov_len <= result) {
+		result -= iov->iov_len;
+		iov++;
+		rq->tx_iovlen--;
+	}
+	iov->iov_base += result;
+	iov->iov_len -= result;
+	rq->tx_ciov = iov;
+}
+
+static inline void ncp_init_header(struct ncp_server *server, struct ncp_request_reply *req, struct ncp_request_header *h)
+{
+	req->status = RQ_INPROGRESS;
+	h->conn_low = server->connection;
+	h->conn_high = server->connection >> 8;
+	h->sequence = ++server->sequence;
+}
+	
+static void ncpdgram_start_request(struct ncp_server *server, struct ncp_request_reply *req)
+{
+	size_t signlen;
+	struct ncp_request_header* h;
+	
+	req->tx_ciov = req->tx_iov + 1;
+
+	h = req->tx_iov[1].iov_base;
+	ncp_init_header(server, req, h);
+	signlen = sign_packet(server, req->tx_iov[1].iov_base + sizeof(struct ncp_request_header) - 1, 
+			req->tx_iov[1].iov_len - sizeof(struct ncp_request_header) + 1,
+			cpu_to_le32(req->tx_totallen), req->sign);
+	if (signlen) {
+		req->tx_ciov[1].iov_base = req->sign;
+		req->tx_ciov[1].iov_len = signlen;
+		req->tx_iovlen += 1;
+		req->tx_totallen += signlen;
+	}
+	server->rcv.creq = req;
+	server->timeout_last = server->m.time_out;
+	server->timeout_retries = server->m.retry_count;
+	ncpdgram_send(server->ncp_sock, req);
+	mod_timer(&server->timeout_tm, jiffies + server->m.time_out);
+}
+
+#define NCP_TCP_XMIT_MAGIC	(0x446D6454)
+#define NCP_TCP_XMIT_VERSION	(1)
+#define NCP_TCP_RCVD_MAGIC	(0x744E6350)
+
+static void ncptcp_start_request(struct ncp_server *server, struct ncp_request_reply *req)
+{
+	size_t signlen;
+	struct ncp_request_header* h;
+
+	req->tx_ciov = req->tx_iov;
+	h = req->tx_iov[1].iov_base;
+	ncp_init_header(server, req, h);
+	signlen = sign_packet(server, req->tx_iov[1].iov_base + sizeof(struct ncp_request_header) - 1,
+			req->tx_iov[1].iov_len - sizeof(struct ncp_request_header) + 1,
+			cpu_to_be32(req->tx_totallen + 24), req->sign + 4) + 16;
+
+	req->sign[0] = htonl(NCP_TCP_XMIT_MAGIC);
+	req->sign[1] = htonl(req->tx_totallen + signlen);
+	req->sign[2] = htonl(NCP_TCP_XMIT_VERSION);
+	req->sign[3] = htonl(req->datalen + 8);
+	req->tx_iov[0].iov_base = req->sign;
+	req->tx_iov[0].iov_len = signlen;
+	req->tx_iovlen += 1;
+	req->tx_totallen += signlen;
+
+	server->tx.creq = req;
+	__ncptcp_try_send(server);
+}
+
+static inline void __ncp_start_request(struct ncp_server *server, struct ncp_request_reply *req)
+{
+	if (server->ncp_sock->type == SOCK_STREAM)
+		ncptcp_start_request(server, req);
+	else
+		ncpdgram_start_request(server, req);
+}
+
+static int ncp_add_request(struct ncp_server *server, struct ncp_request_reply *req)
+{
+	down(&server->rcv.creq_sem);
+	if (!ncp_conn_valid(server)) {
+		up(&server->rcv.creq_sem);
+		printk(KERN_ERR "ncpfs: tcp: Server died\n");
+		return -EIO;
+	}
+	if (server->tx.creq || server->rcv.creq) {
+		req->status = RQ_QUEUED;
+		list_add_tail(&req->req, &server->tx.requests);
+		up(&server->rcv.creq_sem);
+		return 0;
+	}
+	__ncp_start_request(server, req);
+	up(&server->rcv.creq_sem);
+	return 0;
+}
+
+static void __ncp_next_request(struct ncp_server *server)
+{
+	struct ncp_request_reply *req;
+
+	server->rcv.creq = NULL;
+	if (list_empty(&server->tx.requests)) {
+		return;
+	}
+	req = list_entry(server->tx.requests.next, struct ncp_request_reply, req);
+	list_del_init(&req->req);
+	__ncp_start_request(server, req);
+}
+
+static void info_server(struct ncp_server *server, unsigned int id, const void * data, size_t len)
+{
+	if (server->info_sock) {
+		struct kvec iov[2];
+		__be32 hdr[2];
+	
+		hdr[0] = cpu_to_be32(len + 8);
+		hdr[1] = cpu_to_be32(id);
+	
+		iov[0].iov_base = hdr;
+		iov[0].iov_len = 8;
+		iov[1].iov_base = (void *) data;
+		iov[1].iov_len = len;
+
+		do_send(server->info_sock, iov, 2, len + 8, MSG_NOSIGNAL);
+	}
+}
+
+void ncpdgram_rcv_proc(void *s)
+{
+	struct ncp_server *server = s;
+	struct socket* sock;
+	
+	sock = server->ncp_sock;
+	
+	while (1) {
+		struct ncp_reply_header reply;
+		int result;
+
+		result = _recv(sock, &reply, sizeof(reply), MSG_PEEK | MSG_DONTWAIT);
+		if (result < 0) {
+			break;
+		}
+		if (result >= sizeof(reply)) {
+			struct ncp_request_reply *req;
+	
+			if (reply.type == NCP_WATCHDOG) {
+				unsigned char buf[10];
+
+				if (server->connection != get_conn_number(&reply)) {
+					goto drop;
+				}
+				result = _recv(sock, buf, sizeof(buf), MSG_DONTWAIT);
+				if (result < 0) {
+					DPRINTK("recv failed with %d\n", result);
+					continue;
+				}
+				if (result < 10) {
+					DPRINTK("too short (%u) watchdog packet\n", result);
+					continue;
+				}
+				if (buf[9] != '?') {
+					DPRINTK("bad signature (%02X) in watchdog packet\n", buf[9]);
+					continue;
+				}
+				buf[9] = 'Y';
+				_send(sock, buf, sizeof(buf));
+				continue;
+			}
+			if (reply.type != NCP_POSITIVE_ACK && reply.type != NCP_REPLY) {
+				result = _recv(sock, server->unexpected_packet.data, sizeof(server->unexpected_packet.data), MSG_DONTWAIT);
+				if (result < 0) {
+					continue;
+				}
+				info_server(server, 0, server->unexpected_packet.data, result);
+				continue;
+			}
+			down(&server->rcv.creq_sem);		
+			req = server->rcv.creq;
+			if (req && (req->tx_type == NCP_ALLOC_SLOT_REQUEST || (server->sequence == reply.sequence && 
+					server->connection == get_conn_number(&reply)))) {
+				if (reply.type == NCP_POSITIVE_ACK) {
+					server->timeout_retries = server->m.retry_count;
+					server->timeout_last = NCP_MAX_RPC_TIMEOUT;
+					mod_timer(&server->timeout_tm, jiffies + NCP_MAX_RPC_TIMEOUT);
+				} else if (reply.type == NCP_REPLY) {
+					result = _recv(sock, (void*)req->reply_buf, req->datalen, MSG_DONTWAIT);
+#ifdef CONFIG_NCPFS_PACKET_SIGNING
+					if (result >= 0 && server->sign_active && req->tx_type != NCP_DEALLOC_SLOT_REQUEST) {
+						if (result < 8 + 8) {
+							result = -EIO;
+						} else {
+							unsigned int hdrl;
+							
+							result -= 8;
+							hdrl = sock->sk->sk_family == AF_INET ? 8 : 6;
+							if (sign_verify_reply(server, ((char*)req->reply_buf) + hdrl, result - hdrl, cpu_to_le32(result), ((char*)req->reply_buf) + result)) {
+								printk(KERN_INFO "ncpfs: Signature violation\n");
+								result = -EIO;
+							}
+						}
+					}
+#endif
+					del_timer(&server->timeout_tm);
+				     	server->rcv.creq = NULL;
+					ncp_finish_request(req, result);
+					__ncp_next_request(server);
+					up(&server->rcv.creq_sem);
+					continue;
+				}
+			}
+			up(&server->rcv.creq_sem);
+		}
+drop:;		
+		_recv(sock, &reply, sizeof(reply), MSG_DONTWAIT);
+	}
+}
+
+static void __ncpdgram_timeout_proc(struct ncp_server *server)
+{
+	/* If timer is pending, we are processing another request... */
+	if (!timer_pending(&server->timeout_tm)) {
+		struct ncp_request_reply* req;
+		
+		req = server->rcv.creq;
+		if (req) {
+			int timeout;
+			
+			if (server->m.flags & NCP_MOUNT_SOFT) {
+				if (server->timeout_retries-- == 0) {
+					__ncp_abort_request(server, req, -ETIMEDOUT);
+					return;
+				}
+			}
+			/* Ignore errors */
+			ncpdgram_send(server->ncp_sock, req);
+			timeout = server->timeout_last << 1;
+			if (timeout > NCP_MAX_RPC_TIMEOUT) {
+				timeout = NCP_MAX_RPC_TIMEOUT;
+			}
+			server->timeout_last = timeout;
+			mod_timer(&server->timeout_tm, jiffies + timeout);
+		}
+	}
+}
+
+void ncpdgram_timeout_proc(void *s)
+{
+	struct ncp_server *server = s;
+	down(&server->rcv.creq_sem);
+	__ncpdgram_timeout_proc(server);
+	up(&server->rcv.creq_sem);
+}
+
+static inline void ncp_init_req(struct ncp_request_reply* req)
+{
+	init_waitqueue_head(&req->wq);
+	req->status = RQ_IDLE;
+}
+
+static int do_tcp_rcv(struct ncp_server *server, void *buffer, size_t len)
+{
+	int result;
+	
+	if (buffer) {
+		result = _recv(server->ncp_sock, buffer, len, MSG_DONTWAIT);
+	} else {
+		static unsigned char dummy[1024];
+			
+		if (len > sizeof(dummy)) {
+			len = sizeof(dummy);
+		}
+		result = _recv(server->ncp_sock, dummy, len, MSG_DONTWAIT);
+	}
+	if (result < 0) {
+		return result;
+	}
+	if (result > len) {
+		printk(KERN_ERR "ncpfs: tcp: bug in recvmsg (%u > %Zu)\n", result, len);
+		return -EIO;			
+	}
+	return result;
+}	
+
+static int __ncptcp_rcv_proc(struct ncp_server *server)
+{
+	/* We have to check the result, so store the complete header */
+	while (1) {
+		int result;
+		struct ncp_request_reply *req;
+		int datalen;
+		int type;
+
+		while (server->rcv.len) {
+			result = do_tcp_rcv(server, server->rcv.ptr, server->rcv.len);
+			if (result == -EAGAIN) {
+				return 0;
+			}
+			if (result <= 0) {
+				req = server->rcv.creq;
+				if (req) {
+					__ncp_abort_request(server, req, -EIO);
+				} else {
+					__ncptcp_abort(server);
+				}
+				if (result < 0) {
+					printk(KERN_ERR "ncpfs: tcp: error in recvmsg: %d\n", result);
+				} else {
+					DPRINTK(KERN_ERR "ncpfs: tcp: EOF\n");
+				}
+				return -EIO;
+			}
+			if (server->rcv.ptr) {
+				server->rcv.ptr += result;
+			}
+			server->rcv.len -= result;
+		}
+		switch (server->rcv.state) {
+			case 0:
+				if (server->rcv.buf.magic != htonl(NCP_TCP_RCVD_MAGIC)) {
+					printk(KERN_ERR "ncpfs: tcp: Unexpected reply type %08X\n", ntohl(server->rcv.buf.magic));
+					__ncptcp_abort(server);
+					return -EIO;
+				}
+				datalen = ntohl(server->rcv.buf.len) & 0x0FFFFFFF;
+				if (datalen < 10) {
+					printk(KERN_ERR "ncpfs: tcp: Unexpected reply len %d\n", datalen);
+					__ncptcp_abort(server);
+					return -EIO;
+				}
+#ifdef CONFIG_NCPFS_PACKET_SIGNING				
+				if (server->sign_active) {
+					if (datalen < 18) {
+						printk(KERN_ERR "ncpfs: tcp: Unexpected reply len %d\n", datalen);
+						__ncptcp_abort(server);
+						return -EIO;
+					}
+					server->rcv.buf.len = datalen - 8;
+					server->rcv.ptr = (unsigned char*)&server->rcv.buf.p1;
+					server->rcv.len = 8;
+					server->rcv.state = 4;
+					break;
+				}
+#endif				
+				type = ntohs(server->rcv.buf.type);
+#ifdef CONFIG_NCPFS_PACKET_SIGNING				
+cont:;				
+#endif
+				if (type != NCP_REPLY) {
+					if (datalen - 8 <= sizeof(server->unexpected_packet.data)) {
+						*(__u16*)(server->unexpected_packet.data) = htons(type);
+						server->unexpected_packet.len = datalen - 8;
+
+						server->rcv.state = 5;
+						server->rcv.ptr = server->unexpected_packet.data + 2;
+						server->rcv.len = datalen - 10;
+						break;
+					}					
+					DPRINTK("ncpfs: tcp: Unexpected NCP type %02X\n", type);
+skipdata2:;
+					server->rcv.state = 2;
+skipdata:;
+					server->rcv.ptr = NULL;
+					server->rcv.len = datalen - 10;
+					break;
+				}
+				req = server->rcv.creq;
+				if (!req) {
+					DPRINTK(KERN_ERR "ncpfs: Reply without appropriate request\n");
+					goto skipdata2;
+				}
+				if (datalen > req->datalen + 8) {
+					printk(KERN_ERR "ncpfs: tcp: Unexpected reply len %d (expected at most %Zd)\n", datalen, req->datalen + 8);
+					server->rcv.state = 3;
+					goto skipdata;
+				}
+				req->datalen = datalen - 8;
+				req->reply_buf->type = NCP_REPLY;
+				server->rcv.ptr = (unsigned char*)(req->reply_buf) + 2;
+				server->rcv.len = datalen - 10;
+				server->rcv.state = 1;
+				break;
+#ifdef CONFIG_NCPFS_PACKET_SIGNING				
+			case 4:
+				datalen = server->rcv.buf.len;
+				type = ntohs(server->rcv.buf.type2);
+				goto cont;
+#endif
+			case 1:
+				req = server->rcv.creq;
+				if (req->tx_type != NCP_ALLOC_SLOT_REQUEST) {
+					if (req->reply_buf->sequence != server->sequence) {
+						printk(KERN_ERR "ncpfs: tcp: Bad sequence number\n");
+						__ncp_abort_request(server, req, -EIO);
+						return -EIO;
+					}
+					if ((req->reply_buf->conn_low | (req->reply_buf->conn_high << 8)) != server->connection) {
+						printk(KERN_ERR "ncpfs: tcp: Connection number mismatch\n");
+						__ncp_abort_request(server, req, -EIO);
+						return -EIO;
+					}
+				}
+#ifdef CONFIG_NCPFS_PACKET_SIGNING				
+				if (server->sign_active && req->tx_type != NCP_DEALLOC_SLOT_REQUEST) {
+					if (sign_verify_reply(server, (unsigned char*)(req->reply_buf) + 6, req->datalen - 6, cpu_to_be32(req->datalen + 16), &server->rcv.buf.type)) {
+						printk(KERN_ERR "ncpfs: tcp: Signature violation\n");
+						__ncp_abort_request(server, req, -EIO);
+						return -EIO;
+					}
+				}
+#endif				
+				ncp_finish_request(req, req->datalen);
+			nextreq:;
+				__ncp_next_request(server);
+			case 2:
+			next:;
+				server->rcv.ptr = (unsigned char*)&server->rcv.buf;
+				server->rcv.len = 10;
+				server->rcv.state = 0;
+				break;
+			case 3:
+				ncp_finish_request(server->rcv.creq, -EIO);
+				goto nextreq;
+			case 5:
+				info_server(server, 0, server->unexpected_packet.data, server->unexpected_packet.len);
+				goto next;
+		}
+	}
+}
+
+void ncp_tcp_rcv_proc(void *s)
+{
+	struct ncp_server *server = s;
+
+	down(&server->rcv.creq_sem);
+	__ncptcp_rcv_proc(server);
+	up(&server->rcv.creq_sem);
+}
+
+void ncp_tcp_tx_proc(void *s)
+{
+	struct ncp_server *server = s;
+	
+	down(&server->rcv.creq_sem);
+	__ncptcp_try_send(server);
+	up(&server->rcv.creq_sem);
+}
+
+static int do_ncp_rpc_call(struct ncp_server *server, int size,
+		struct ncp_reply_header* reply_buf, int max_reply_size)
+{
+	int result;
+	struct ncp_request_reply req;
+
+	ncp_init_req(&req);
+	req.reply_buf = reply_buf;
+	req.datalen = max_reply_size;
+	req.tx_iov[1].iov_base = server->packet;
+	req.tx_iov[1].iov_len = size;
+	req.tx_iovlen = 1;
+	req.tx_totallen = size;
+	req.tx_type = *(u_int16_t*)server->packet;
+
+	result = ncp_add_request(server, &req);
+	if (result < 0) {
+		return result;
+	}
+	if (wait_event_interruptible(req.wq, req.status == RQ_DONE)) {
+		ncp_abort_request(server, &req, -EIO);
+	}
+	return req.result;
+}
+
+/*
+ * We need the server to be locked here, so check!
+ */
+
+static int ncp_do_request(struct ncp_server *server, int size,
+		void* reply, int max_reply_size)
+{
+	int result;
+
+	if (server->lock == 0) {
+		printk(KERN_ERR "ncpfs: Server not locked!\n");
+		return -EIO;
+	}
+	if (!ncp_conn_valid(server)) {
+		printk(KERN_ERR "ncpfs: Connection invalid!\n");
+		return -EIO;
+	}
+	{
+		sigset_t old_set;
+		unsigned long mask, flags;
+
+		spin_lock_irqsave(&current->sighand->siglock, flags);
+		old_set = current->blocked;
+		if (current->flags & PF_EXITING)
+			mask = 0;
+		else
+			mask = sigmask(SIGKILL);
+		if (server->m.flags & NCP_MOUNT_INTR) {
+			/* FIXME: This doesn't seem right at all.  So, like,
+			   we can't handle SIGINT and get whatever to stop?
+			   What if we've blocked it ourselves?  What about
+			   alarms?  Why, in fact, are we mucking with the
+			   sigmask at all? -- r~ */
+			if (current->sighand->action[SIGINT - 1].sa.sa_handler == SIG_DFL)
+				mask |= sigmask(SIGINT);
+			if (current->sighand->action[SIGQUIT - 1].sa.sa_handler == SIG_DFL)
+				mask |= sigmask(SIGQUIT);
+		}
+		siginitsetinv(&current->blocked, mask);
+		recalc_sigpending();
+		spin_unlock_irqrestore(&current->sighand->siglock, flags);
+		
+		result = do_ncp_rpc_call(server, size, reply, max_reply_size);
+
+		spin_lock_irqsave(&current->sighand->siglock, flags);
+		current->blocked = old_set;
+		recalc_sigpending();
+		spin_unlock_irqrestore(&current->sighand->siglock, flags);
+	}
+
+	DDPRINTK("do_ncp_rpc_call returned %d\n", result);
+
+	if (result < 0) {
+		/* There was a problem with I/O, so the connections is
+		 * no longer usable. */
+		ncp_invalidate_conn(server);
+	}
+	return result;
+}
+
+/* ncp_do_request assures that at least a complete reply header is
+ * received. It assumes that server->current_size contains the ncp
+ * request size
+ */
+int ncp_request2(struct ncp_server *server, int function, 
+		void* rpl, int size)
+{
+	struct ncp_request_header *h;
+	struct ncp_reply_header* reply = rpl;
+	int result;
+
+	h = (struct ncp_request_header *) (server->packet);
+	if (server->has_subfunction != 0) {
+		*(__u16 *) & (h->data[0]) = htons(server->current_size - sizeof(*h) - 2);
+	}
+	h->type = NCP_REQUEST;
+	/*
+	 * The server shouldn't know or care what task is making a
+	 * request, so we always use the same task number.
+	 */
+	h->task = 2; /* (current->pid) & 0xff; */
+	h->function = function;
+
+	result = ncp_do_request(server, server->current_size, reply, size);
+	if (result < 0) {
+		DPRINTK("ncp_request_error: %d\n", result);
+		goto out;
+	}
+	server->completion = reply->completion_code;
+	server->conn_status = reply->connection_state;
+	server->reply_size = result;
+	server->ncp_reply_size = result - sizeof(struct ncp_reply_header);
+
+	result = reply->completion_code;
+
+	if (result != 0)
+		PPRINTK("ncp_request: completion code=%x\n", result);
+out:
+	return result;
+}
+
+int ncp_connect(struct ncp_server *server)
+{
+	struct ncp_request_header *h;
+	int result;
+
+	server->connection = 0xFFFF;
+	server->sequence = 255;
+
+	h = (struct ncp_request_header *) (server->packet);
+	h->type = NCP_ALLOC_SLOT_REQUEST;
+	h->task		= 2; /* see above */
+	h->function	= 0;
+
+	result = ncp_do_request(server, sizeof(*h), server->packet, server->packet_size);
+	if (result < 0)
+		goto out;
+	server->connection = h->conn_low + (h->conn_high * 256);
+	result = 0;
+out:
+	return result;
+}
+
+int ncp_disconnect(struct ncp_server *server)
+{
+	struct ncp_request_header *h;
+
+	h = (struct ncp_request_header *) (server->packet);
+	h->type = NCP_DEALLOC_SLOT_REQUEST;
+	h->task		= 2; /* see above */
+	h->function	= 0;
+
+	return ncp_do_request(server, sizeof(*h), server->packet, server->packet_size);
+}
+
+void ncp_lock_server(struct ncp_server *server)
+{
+	down(&server->sem);
+	if (server->lock)
+		printk(KERN_WARNING "ncp_lock_server: was locked!\n");
+	server->lock = 1;
+}
+
+void ncp_unlock_server(struct ncp_server *server)
+{
+	if (!server->lock) {
+		printk(KERN_WARNING "ncp_unlock_server: was not locked!\n");
+		return;
+	}
+	server->lock = 0;
+	up(&server->sem);
+}
diff --git a/fs/ncpfs/symlink.c b/fs/ncpfs/symlink.c
new file mode 100644
index 0000000..e935f1b
--- /dev/null
+++ b/fs/ncpfs/symlink.c
@@ -0,0 +1,183 @@
+/*
+ *  linux/fs/ncpfs/symlink.c
+ *
+ *  Code for allowing symbolic links on NCPFS (i.e. NetWare)
+ *  Symbolic links are not supported on native NetWare, so we use an
+ *  infrequently-used flag (Sh) and store a two-word magic header in
+ *  the file to make sure we don't accidentally use a non-link file
+ *  as a link.
+ *
+ *  When using the NFS namespace, we set the mode to indicate a symlink and
+ *  don't bother with the magic numbers.
+ *
+ *  from linux/fs/ext2/symlink.c
+ *
+ *  Copyright (C) 1998-99, Frank A. Vorstenbosch
+ *
+ *  ncpfs symlink handling code
+ *  NLS support (c) 1999 Petr Vandrovec
+ *  Modified 2000 Ben Harris, University of Cambridge for NFS NS meta-info
+ *
+ */
+
+#include <linux/config.h>
+
+#include <asm/uaccess.h>
+
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/ncp_fs.h>
+#include <linux/time.h>
+#include <linux/mm.h>
+#include <linux/stat.h>
+#include "ncplib_kernel.h"
+
+
+/* these magic numbers must appear in the symlink file -- this makes it a bit
+   more resilient against the magic attributes being set on random files. */
+
+#define NCP_SYMLINK_MAGIC0	cpu_to_le32(0x6c6d7973)     /* "symlnk->" */
+#define NCP_SYMLINK_MAGIC1	cpu_to_le32(0x3e2d6b6e)
+
+/* ----- read a symbolic link ------------------------------------------ */
+
+static int ncp_symlink_readpage(struct file *file, struct page *page)
+{
+	struct inode *inode = page->mapping->host;
+	int error, length, len;
+	char *link, *rawlink;
+	char *buf = kmap(page);
+
+	error = -ENOMEM;
+	rawlink=(char *)kmalloc(NCP_MAX_SYMLINK_SIZE, GFP_KERNEL);
+	if (!rawlink)
+		goto fail;
+
+	if (ncp_make_open(inode,O_RDONLY))
+		goto failEIO;
+
+	error=ncp_read_kernel(NCP_SERVER(inode),NCP_FINFO(inode)->file_handle,
+                         0,NCP_MAX_SYMLINK_SIZE,rawlink,&length);
+
+	ncp_inode_close(inode);
+	/* Close file handle if no other users... */
+	ncp_make_closed(inode);
+	if (error)
+		goto failEIO;
+
+	if (NCP_FINFO(inode)->flags & NCPI_KLUDGE_SYMLINK) {
+		if (length<NCP_MIN_SYMLINK_SIZE || 
+		    ((__le32 *)rawlink)[0]!=NCP_SYMLINK_MAGIC0 ||
+		    ((__le32 *)rawlink)[1]!=NCP_SYMLINK_MAGIC1)
+		    	goto failEIO;
+		link = rawlink + 8;
+		length -= 8;
+	} else {
+		link = rawlink;
+	}
+
+	len = NCP_MAX_SYMLINK_SIZE;
+	error = ncp_vol2io(NCP_SERVER(inode), buf, &len, link, length, 0);
+	kfree(rawlink);
+	if (error)
+		goto fail;
+	SetPageUptodate(page);
+	kunmap(page);
+	unlock_page(page);
+	return 0;
+
+failEIO:
+	error = -EIO;
+	kfree(rawlink);
+fail:
+	SetPageError(page);
+	kunmap(page);
+	unlock_page(page);
+	return error;
+}
+
+/*
+ * symlinks can't do much...
+ */
+struct address_space_operations ncp_symlink_aops = {
+	.readpage	= ncp_symlink_readpage,
+};
+	
+/* ----- create a new symbolic link -------------------------------------- */
+ 
+int ncp_symlink(struct inode *dir, struct dentry *dentry, const char *symname) {
+	struct inode *inode;
+	char *rawlink;
+	int length, err, i, outlen;
+	int kludge;
+	int mode;
+	__le32 attr;
+	unsigned int hdr;
+
+	DPRINTK("ncp_symlink(dir=%p,dentry=%p,symname=%s)\n",dir,dentry,symname);
+
+	if (ncp_is_nfs_extras(NCP_SERVER(dir), NCP_FINFO(dir)->volNumber))
+		kludge = 0;
+	else
+#ifdef CONFIG_NCPFS_EXTRAS
+	if (NCP_SERVER(dir)->m.flags & NCP_MOUNT_SYMLINKS)
+		kludge = 1;
+	else
+#endif
+	/* EPERM is returned by VFS if symlink procedure does not exist */
+		return -EPERM;
+  
+	rawlink=(char *)kmalloc(NCP_MAX_SYMLINK_SIZE, GFP_KERNEL);
+	if (!rawlink)
+		return -ENOMEM;
+
+	if (kludge) {
+		mode = 0;
+		attr = aSHARED | aHIDDEN;
+		((__le32 *)rawlink)[0]=NCP_SYMLINK_MAGIC0;
+		((__le32 *)rawlink)[1]=NCP_SYMLINK_MAGIC1;
+		hdr = 8;
+	} else {
+		mode = S_IFLNK | S_IRWXUGO;
+		attr = 0;
+		hdr = 0;
+	}			
+
+	length = strlen(symname);
+	/* map to/from server charset, do not touch upper/lower case as
+	   symlink can point out of ncp filesystem */
+	outlen = NCP_MAX_SYMLINK_SIZE - hdr;
+	err = ncp_io2vol(NCP_SERVER(dir), rawlink + hdr, &outlen, symname, length, 0);
+	if (err)
+		goto failfree;
+
+	outlen += hdr;
+
+	err = -EIO;
+	if (ncp_create_new(dir,dentry,mode,0,attr)) {
+		goto failfree;
+	}
+
+	inode=dentry->d_inode;
+
+	if (ncp_make_open(inode, O_WRONLY))
+		goto failfree;
+
+	if (ncp_write_kernel(NCP_SERVER(inode), NCP_FINFO(inode)->file_handle, 
+			     0, outlen, rawlink, &i) || i!=outlen) {
+		goto fail;
+	}
+
+	ncp_inode_close(inode);
+	ncp_make_closed(inode);
+	kfree(rawlink);
+	return 0;
+fail:;
+	ncp_inode_close(inode);
+	ncp_make_closed(inode);
+failfree:;
+	kfree(rawlink);
+	return err;
+}
+
+/* ----- EOF ----- */