summaryrefslogtreecommitdiff
path: root/fs/ncpfs
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /fs/ncpfs
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!
Diffstat (limited to 'fs/ncpfs')
-rw-r--r--fs/ncpfs/Kconfig87
-rw-r--r--fs/ncpfs/Makefile16
-rw-r--r--fs/ncpfs/dir.c1260
-rw-r--r--fs/ncpfs/file.c300
-rw-r--r--fs/ncpfs/getopt.c75
-rw-r--r--fs/ncpfs/getopt.h16
-rw-r--r--fs/ncpfs/inode.c1012
-rw-r--r--fs/ncpfs/ioctl.c649
-rw-r--r--fs/ncpfs/mmap.c128
-rw-r--r--fs/ncpfs/ncplib_kernel.c1355
-rw-r--r--fs/ncpfs/ncplib_kernel.h259
-rw-r--r--fs/ncpfs/ncpsign_kernel.c127
-rw-r--r--fs/ncpfs/ncpsign_kernel.h28
-rw-r--r--fs/ncpfs/sock.c850
-rw-r--r--fs/ncpfs/symlink.c183
15 files changed, 6345 insertions, 0 deletions
diff --git a/fs/ncpfs/Kconfig b/fs/ncpfs/Kconfig
new file mode 100644
index 00000000000..142808427b2
--- /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 00000000000..68ea095100a
--- /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 00000000000..2dc2d869396
--- /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 00000000000..4947d9b11fc
--- /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 00000000000..335b003dddf
--- /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 00000000000..cccc007dcaf
--- /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 00000000000..44795d2f4b3
--- /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 00000000000..88df79356a1
--- /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 00000000000..52d60c3d899
--- /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 00000000000..e4eb5ed4bee
--- /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 00000000000..05ec2e9d90c
--- /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 00000000000..a6ec90cd889
--- /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 00000000000..6451a68381c
--- /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 00000000000..6593a5ca88b
--- /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 00000000000..e935f1b34bc
--- /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 ----- */