diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /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/Kconfig | 87 | ||||
-rw-r--r-- | fs/ncpfs/Makefile | 16 | ||||
-rw-r--r-- | fs/ncpfs/dir.c | 1260 | ||||
-rw-r--r-- | fs/ncpfs/file.c | 300 | ||||
-rw-r--r-- | fs/ncpfs/getopt.c | 75 | ||||
-rw-r--r-- | fs/ncpfs/getopt.h | 16 | ||||
-rw-r--r-- | fs/ncpfs/inode.c | 1012 | ||||
-rw-r--r-- | fs/ncpfs/ioctl.c | 649 | ||||
-rw-r--r-- | fs/ncpfs/mmap.c | 128 | ||||
-rw-r--r-- | fs/ncpfs/ncplib_kernel.c | 1355 | ||||
-rw-r--r-- | fs/ncpfs/ncplib_kernel.h | 259 | ||||
-rw-r--r-- | fs/ncpfs/ncpsign_kernel.c | 127 | ||||
-rw-r--r-- | fs/ncpfs/ncpsign_kernel.h | 28 | ||||
-rw-r--r-- | fs/ncpfs/sock.c | 850 | ||||
-rw-r--r-- | fs/ncpfs/symlink.c | 183 |
15 files changed, 6345 insertions, 0 deletions
diff --git a/fs/ncpfs/Kconfig b/fs/ncpfs/Kconfig new file mode 100644 index 000000000000..142808427b25 --- /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 000000000000..68ea095100a8 --- /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 000000000000..2dc2d8693968 --- /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 000000000000..4947d9b11fc1 --- /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 000000000000..335b003dddf9 --- /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 000000000000..cccc007dcaf9 --- /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 000000000000..44795d2f4b30 --- /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 000000000000..88df79356a1f --- /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 000000000000..52d60c3d8996 --- /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 000000000000..e4eb5ed4bee4 --- /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 000000000000..05ec2e9d90c6 --- /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 000000000000..a6ec90cd8894 --- /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 000000000000..6451a68381cc --- /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 000000000000..6593a5ca88ba --- /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(¤t->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(¤t->blocked, mask); + recalc_sigpending(); + spin_unlock_irqrestore(¤t->sighand->siglock, flags); + + result = do_ncp_rpc_call(server, size, reply, max_reply_size); + + spin_lock_irqsave(¤t->sighand->siglock, flags); + current->blocked = old_set; + recalc_sigpending(); + spin_unlock_irqrestore(¤t->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 000000000000..e935f1b34bc2 --- /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 ----- */ |