diff options
Diffstat (limited to 'fs/smbfs')
-rw-r--r-- | fs/smbfs/Makefile | 39 | ||||
-rw-r--r-- | fs/smbfs/cache.c | 209 | ||||
-rw-r--r-- | fs/smbfs/dir.c | 693 | ||||
-rw-r--r-- | fs/smbfs/file.c | 423 | ||||
-rw-r--r-- | fs/smbfs/getopt.c | 64 | ||||
-rw-r--r-- | fs/smbfs/getopt.h | 14 | ||||
-rw-r--r-- | fs/smbfs/inode.c | 849 | ||||
-rw-r--r-- | fs/smbfs/ioctl.c | 67 | ||||
-rw-r--r-- | fs/smbfs/proc.c | 3509 | ||||
-rw-r--r-- | fs/smbfs/proto.h | 87 | ||||
-rw-r--r-- | fs/smbfs/request.c | 823 | ||||
-rw-r--r-- | fs/smbfs/request.h | 70 | ||||
-rw-r--r-- | fs/smbfs/smb_debug.h | 34 | ||||
-rw-r--r-- | fs/smbfs/smbiod.c | 341 | ||||
-rw-r--r-- | fs/smbfs/sock.c | 388 | ||||
-rw-r--r-- | fs/smbfs/symlink.c | 70 |
16 files changed, 7680 insertions, 0 deletions
diff --git a/fs/smbfs/Makefile b/fs/smbfs/Makefile new file mode 100644 index 00000000000..93246b7dd6f --- /dev/null +++ b/fs/smbfs/Makefile @@ -0,0 +1,39 @@ +# +# Makefile for the linux smb-filesystem routines. +# + +obj-$(CONFIG_SMB_FS) += smbfs.o + +smbfs-objs := proc.o dir.o cache.o sock.o inode.o file.o ioctl.o getopt.o \ + symlink.o smbiod.o request.o + +# If you want debugging output, you may add these flags to the EXTRA_CFLAGS +# SMBFS_PARANOIA should normally be enabled. + +EXTRA_CFLAGS += -DSMBFS_PARANOIA +#EXTRA_CFLAGS += -DSMBFS_DEBUG +#EXTRA_CFLAGS += -DSMBFS_DEBUG_VERBOSE +#EXTRA_CFLAGS += -DDEBUG_SMB_MALLOC +#EXTRA_CFLAGS += -DDEBUG_SMB_TIMESTAMP +#EXTRA_CFLAGS += -Werror + +# +# Maintainer rules +# + +# getopt.c not included. It is intentionally separate +SRC = proc.c dir.c cache.c sock.c inode.c file.c ioctl.c smbiod.c request.c \ + symlink.c + +proto: + -rm -f proto.h + @echo > proto2.h "/*" + @echo >> proto2.h " * Autogenerated with cproto on: " `date` + @echo >> proto2.h " */" + @echo >> proto2.h "" + @echo >> proto2.h "struct smb_request;" + @echo >> proto2.h "struct sock;" + @echo >> proto2.h "struct statfs;" + @echo >> proto2.h "" + cproto -E "gcc -E" -e -v -I $(TOPDIR)/include -DMAKING_PROTO -D__KERNEL__ $(SRC) >> proto2.h + mv proto2.h proto.h diff --git a/fs/smbfs/cache.c b/fs/smbfs/cache.c new file mode 100644 index 00000000000..f3e6b81288a --- /dev/null +++ b/fs/smbfs/cache.c @@ -0,0 +1,209 @@ +/* + * cache.c + * + * Copyright (C) 1997 by Bill Hawes + * + * Routines to support directory cacheing using the page cache. + * This cache code is almost directly taken from ncpfs. + * + * Please add a note about your changes to smbfs in the ChangeLog file. + */ + +#include <linux/time.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/dirent.h> +#include <linux/smb_fs.h> +#include <linux/pagemap.h> +#include <linux/net.h> + +#include <asm/page.h> + +#include "smb_debug.h" +#include "proto.h" + +/* + * Force the next attempt to use the cache to be a timeout. + * If we can't find the page that's fine, it will cause a refresh. + */ +void +smb_invalid_dir_cache(struct inode * dir) +{ + struct smb_sb_info *server = server_from_inode(dir); + union smb_dir_cache *cache = NULL; + struct page *page = NULL; + + page = grab_cache_page(&dir->i_data, 0); + if (!page) + goto out; + + if (!PageUptodate(page)) + goto out_unlock; + + cache = kmap(page); + cache->head.time = jiffies - SMB_MAX_AGE(server); + + kunmap(page); + SetPageUptodate(page); +out_unlock: + unlock_page(page); + page_cache_release(page); +out: + return; +} + +/* + * Mark all dentries for 'parent' as invalid, forcing them to be re-read + */ +void +smb_invalidate_dircache_entries(struct dentry *parent) +{ + struct smb_sb_info *server = server_from_dentry(parent); + 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; + smb_age_dentry(server, dentry); + next = next->next; + } + spin_unlock(&dcache_lock); +} + +/* + * dget, but require that fpos and parent matches what the dentry contains. + * dentry is not known to be a valid pointer at entry. + */ +struct dentry * +smb_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 <= SMB_MAXNAMELEN && + (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; + goto out_unlock; + } + next = next->next; + } + dent = NULL; +out_unlock: + spin_unlock(&dcache_lock); + return dent; +} + + +/* + * Create dentry/inode for this file and add it to the dircache. + */ +int +smb_fill_cache(struct file *filp, void *dirent, filldir_t filldir, + struct smb_cache_control *ctrl, struct qstr *qname, + struct smb_fattr *entry) +{ + struct dentry *newdent, *dentry = filp->f_dentry; + struct inode *newino, *inode = dentry->d_inode; + struct smb_cache_control ctl = *ctrl; + int valid = 0; + int hashed = 0; + ino_t ino = 0; + + 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) { + smb_renew_times(newdent); + entry->f_ino = iunique(inode->i_sb, 2); + newino = smb_iget(inode->i_sb, entry); + if (newino) { + smb_new_dentry(newdent); + d_instantiate(newdent, newino); + if (!hashed) + d_rehash(newdent); + } + } else + smb_set_inode_attr(newdent->d_inode, entry); + + if (newdent->d_inode) { + ino = newdent->d_inode->i_ino; + newdent->d_fsdata = (void *) ctl.fpos; + smb_new_dentry(newdent); + } + + if (ctl.idx >= SMB_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 -= SMB_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); +} diff --git a/fs/smbfs/dir.c b/fs/smbfs/dir.c new file mode 100644 index 00000000000..c6c33e15143 --- /dev/null +++ b/fs/smbfs/dir.c @@ -0,0 +1,693 @@ +/* + * dir.c + * + * Copyright (C) 1995, 1996 by Paal-Kr. Engstad and Volker Lendecke + * Copyright (C) 1997 by Volker Lendecke + * + * Please add a note about your changes to smbfs in the ChangeLog file. + */ + +#include <linux/time.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/smp_lock.h> +#include <linux/ctype.h> +#include <linux/net.h> + +#include <linux/smb_fs.h> +#include <linux/smb_mount.h> +#include <linux/smbno.h> + +#include "smb_debug.h" +#include "proto.h" + +static int smb_readdir(struct file *, void *, filldir_t); +static int smb_dir_open(struct inode *, struct file *); + +static struct dentry *smb_lookup(struct inode *, struct dentry *, struct nameidata *); +static int smb_create(struct inode *, struct dentry *, int, struct nameidata *); +static int smb_mkdir(struct inode *, struct dentry *, int); +static int smb_rmdir(struct inode *, struct dentry *); +static int smb_unlink(struct inode *, struct dentry *); +static int smb_rename(struct inode *, struct dentry *, + struct inode *, struct dentry *); +static int smb_make_node(struct inode *,struct dentry *,int,dev_t); +static int smb_link(struct dentry *, struct inode *, struct dentry *); + +struct file_operations smb_dir_operations = +{ + .read = generic_read_dir, + .readdir = smb_readdir, + .ioctl = smb_ioctl, + .open = smb_dir_open, +}; + +struct inode_operations smb_dir_inode_operations = +{ + .create = smb_create, + .lookup = smb_lookup, + .unlink = smb_unlink, + .mkdir = smb_mkdir, + .rmdir = smb_rmdir, + .rename = smb_rename, + .getattr = smb_getattr, + .setattr = smb_notify_change, +}; + +struct inode_operations smb_dir_inode_operations_unix = +{ + .create = smb_create, + .lookup = smb_lookup, + .unlink = smb_unlink, + .mkdir = smb_mkdir, + .rmdir = smb_rmdir, + .rename = smb_rename, + .getattr = smb_getattr, + .setattr = smb_notify_change, + .symlink = smb_symlink, + .mknod = smb_make_node, + .link = smb_link, +}; + +/* + * Read a directory, using filldir to fill the dirent memory. + * smb_proc_readdir does the actual reading from the smb server. + * + * The cache code is almost directly taken from ncpfs + */ +static int +smb_readdir(struct file *filp, void *dirent, filldir_t filldir) +{ + struct dentry *dentry = filp->f_dentry; + struct inode *dir = dentry->d_inode; + struct smb_sb_info *server = server_from_dentry(dentry); + union smb_dir_cache *cache = NULL; + struct smb_cache_control ctl; + struct page *page = NULL; + int result; + + ctl.page = NULL; + ctl.cache = NULL; + + VERBOSE("reading %s/%s, f_pos=%d\n", + DENTRY_PATH(dentry), (int) filp->f_pos); + + result = 0; + + lock_kernel(); + + switch ((unsigned int) filp->f_pos) { + case 0: + if (filldir(dirent, ".", 1, 0, dir->i_ino, DT_DIR) < 0) + goto out; + filp->f_pos = 1; + /* fallthrough */ + case 1: + if (filldir(dirent, "..", 2, 1, parent_ino(dentry), DT_DIR) < 0) + goto out; + filp->f_pos = 2; + } + + /* + * Make sure our inode is up-to-date. + */ + result = smb_revalidate_inode(dentry); + if (result) + goto out; + + + page = grab_cache_page(&dir->i_data, 0); + if (!page) + goto read_really; + + ctl.cache = cache = kmap(page); + ctl.head = cache->head; + + if (!PageUptodate(page) || !ctl.head.eof) { + VERBOSE("%s/%s, page uptodate=%d, eof=%d\n", + DENTRY_PATH(dentry), PageUptodate(page),ctl.head.eof); + goto init_cache; + } + + if (filp->f_pos == 2) { + if (jiffies - ctl.head.time >= SMB_MAX_AGE(server)) + goto init_cache; + + /* + * N.B. ncpfs checks mtime of dentry too here, we don't. + * 1. common smb servers do not update mtime on dir changes + * 2. it requires an extra smb request + * (revalidate has the same timeout as ctl.head.time) + * + * Instead smbfs invalidates its own cache on local changes + * and remote changes are not seen until timeout. + */ + } + + if (filp->f_pos > ctl.head.end) + goto finished; + + ctl.fpos = filp->f_pos + (SMB_DIRCACHE_START - 2); + ctl.ofs = ctl.fpos / SMB_DIRCACHE_SIZE; + ctl.idx = ctl.fpos % SMB_DIRCACHE_SIZE; + + for (;;) { + if (ctl.ofs != 0) { + ctl.page = find_lock_page(&dir->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 < SMB_DIRCACHE_SIZE) { + struct dentry *dent; + int res; + + dent = smb_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: + smb_invalidate_dircache_entries(dentry); + ctl.head.time = jiffies; + ctl.head.eof = 0; + ctl.fpos = 2; + ctl.ofs = 0; + ctl.idx = SMB_DIRCACHE_START; + ctl.filled = 0; + ctl.valid = 1; +read_really: + result = server->ops->readdir(filp, dirent, filldir, &ctl); + if (ctl.idx == -1) + goto invalid_cache; /* retry */ + 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 +smb_dir_open(struct inode *dir, struct file *file) +{ + struct dentry *dentry = file->f_dentry; + struct smb_sb_info *server; + int error = 0; + + VERBOSE("(%s/%s)\n", dentry->d_parent->d_name.name, + file->f_dentry->d_name.name); + + /* + * Directory timestamps in the core protocol aren't updated + * when a file is added, so we give them a very short TTL. + */ + lock_kernel(); + server = server_from_dentry(dentry); + if (server->opt.protocol < SMB_PROTOCOL_LANMAN2) { + unsigned long age = jiffies - SMB_I(dir)->oldmtime; + if (age > 2*HZ) + smb_invalid_dir_cache(dir); + } + + /* + * Note: in order to allow the smbmount process to open the + * mount point, we only revalidate if the connection is valid or + * if the process is trying to access something other than the root. + */ + if (server->state == CONN_VALID || !IS_ROOT(dentry)) + error = smb_revalidate_inode(dentry); + unlock_kernel(); + return error; +} + +/* + * Dentry operations routines + */ +static int smb_lookup_validate(struct dentry *, struct nameidata *); +static int smb_hash_dentry(struct dentry *, struct qstr *); +static int smb_compare_dentry(struct dentry *, struct qstr *, struct qstr *); +static int smb_delete_dentry(struct dentry *); + +static struct dentry_operations smbfs_dentry_operations = +{ + .d_revalidate = smb_lookup_validate, + .d_hash = smb_hash_dentry, + .d_compare = smb_compare_dentry, + .d_delete = smb_delete_dentry, +}; + +static struct dentry_operations smbfs_dentry_operations_case = +{ + .d_revalidate = smb_lookup_validate, + .d_delete = smb_delete_dentry, +}; + + +/* + * This is the callback when the dcache has a lookup hit. + */ +static int +smb_lookup_validate(struct dentry * dentry, struct nameidata *nd) +{ + struct smb_sb_info *server = server_from_dentry(dentry); + struct inode * inode = dentry->d_inode; + unsigned long age = jiffies - dentry->d_time; + int valid; + + /* + * The default validation is based on dentry age: + * we believe in dentries for a few seconds. (But each + * successful server lookup renews the timestamp.) + */ + valid = (age <= SMB_MAX_AGE(server)); +#ifdef SMBFS_DEBUG_VERBOSE + if (!valid) + VERBOSE("%s/%s not valid, age=%lu\n", + DENTRY_PATH(dentry), age); +#endif + + if (inode) { + lock_kernel(); + if (is_bad_inode(inode)) { + PARANOIA("%s/%s has dud inode\n", DENTRY_PATH(dentry)); + valid = 0; + } else if (!valid) + valid = (smb_revalidate_inode(dentry) == 0); + unlock_kernel(); + } else { + /* + * What should we do for negative dentries? + */ + } + return valid; +} + +static int +smb_hash_dentry(struct dentry *dir, struct qstr *this) +{ + unsigned long hash; + int i; + + hash = init_name_hash(); + for (i=0; i < this->len ; i++) + hash = partial_name_hash(tolower(this->name[i]), hash); + this->hash = end_name_hash(hash); + + return 0; +} + +static int +smb_compare_dentry(struct dentry *dir, struct qstr *a, struct qstr *b) +{ + int i, result = 1; + + if (a->len != b->len) + goto out; + for (i=0; i < a->len; i++) { + if (tolower(a->name[i]) != tolower(b->name[i])) + goto out; + } + result = 0; +out: + return result; +} + +/* + * This is the callback from dput() when d_count is going to 0. + * We use this to unhash dentries with bad inodes. + */ +static int +smb_delete_dentry(struct dentry * dentry) +{ + if (dentry->d_inode) { + if (is_bad_inode(dentry->d_inode)) { + PARANOIA("bad inode, unhashing %s/%s\n", + DENTRY_PATH(dentry)); + return 1; + } + } else { + /* N.B. Unhash negative dentries? */ + } + return 0; +} + +/* + * Initialize a new dentry + */ +void +smb_new_dentry(struct dentry *dentry) +{ + struct smb_sb_info *server = server_from_dentry(dentry); + + if (server->mnt->flags & SMB_MOUNT_CASE) + dentry->d_op = &smbfs_dentry_operations_case; + else + dentry->d_op = &smbfs_dentry_operations; + dentry->d_time = jiffies; +} + + +/* + * Whenever a lookup succeeds, we know the parent directories + * are all valid, so we want to update the dentry timestamps. + * N.B. Move this to dcache? + */ +void +smb_renew_times(struct dentry * dentry) +{ + dget(dentry); + spin_lock(&dentry->d_lock); + for (;;) { + struct dentry *parent; + + dentry->d_time = jiffies; + if (IS_ROOT(dentry)) + break; + parent = dentry->d_parent; + dget(parent); + spin_unlock(&dentry->d_lock); + dput(dentry); + dentry = parent; + spin_lock(&dentry->d_lock); + } + spin_unlock(&dentry->d_lock); + dput(dentry); +} + +static struct dentry * +smb_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) +{ + struct smb_fattr finfo; + struct inode *inode; + int error; + struct smb_sb_info *server; + + error = -ENAMETOOLONG; + if (dentry->d_name.len > SMB_MAXNAMELEN) + goto out; + + lock_kernel(); + error = smb_proc_getattr(dentry, &finfo); +#ifdef SMBFS_PARANOIA + if (error && error != -ENOENT) + PARANOIA("find %s/%s failed, error=%d\n", + DENTRY_PATH(dentry), error); +#endif + + inode = NULL; + if (error == -ENOENT) + goto add_entry; + if (!error) { + error = -EACCES; + finfo.f_ino = iunique(dentry->d_sb, 2); + inode = smb_iget(dir->i_sb, &finfo); + if (inode) { + add_entry: + server = server_from_dentry(dentry); + if (server->mnt->flags & SMB_MOUNT_CASE) + dentry->d_op = &smbfs_dentry_operations_case; + else + dentry->d_op = &smbfs_dentry_operations; + + d_add(dentry, inode); + smb_renew_times(dentry); + error = 0; + } + } + unlock_kernel(); +out: + return ERR_PTR(error); +} + +/* + * This code is common to all routines creating a new inode. + */ +static int +smb_instantiate(struct dentry *dentry, __u16 fileid, int have_id) +{ + struct smb_sb_info *server = server_from_dentry(dentry); + struct inode *inode; + int error; + struct smb_fattr fattr; + + VERBOSE("file %s/%s, fileid=%u\n", DENTRY_PATH(dentry), fileid); + + error = smb_proc_getattr(dentry, &fattr); + if (error) + goto out_close; + + smb_renew_times(dentry); + fattr.f_ino = iunique(dentry->d_sb, 2); + inode = smb_iget(dentry->d_sb, &fattr); + if (!inode) + goto out_no_inode; + + if (have_id) { + struct smb_inode_info *ei = SMB_I(inode); + ei->fileid = fileid; + ei->access = SMB_O_RDWR; + ei->open = server->generation; + } + d_instantiate(dentry, inode); +out: + return error; + +out_no_inode: + error = -EACCES; +out_close: + if (have_id) { + PARANOIA("%s/%s failed, error=%d, closing %u\n", + DENTRY_PATH(dentry), error, fileid); + smb_close_fileid(dentry, fileid); + } + goto out; +} + +/* N.B. How should the mode argument be used? */ +static int +smb_create(struct inode *dir, struct dentry *dentry, int mode, + struct nameidata *nd) +{ + struct smb_sb_info *server = server_from_dentry(dentry); + __u16 fileid; + int error; + struct iattr attr; + + VERBOSE("creating %s/%s, mode=%d\n", DENTRY_PATH(dentry), mode); + + lock_kernel(); + smb_invalid_dir_cache(dir); + error = smb_proc_create(dentry, 0, get_seconds(), &fileid); + if (!error) { + if (server->opt.capabilities & SMB_CAP_UNIX) { + /* Set attributes for new file */ + attr.ia_valid = ATTR_MODE; + attr.ia_mode = mode; + error = smb_proc_setattr_unix(dentry, &attr, 0, 0); + } + error = smb_instantiate(dentry, fileid, 1); + } else { + PARANOIA("%s/%s failed, error=%d\n", + DENTRY_PATH(dentry), error); + } + unlock_kernel(); + return error; +} + +/* N.B. How should the mode argument be used? */ +static int +smb_mkdir(struct inode *dir, struct dentry *dentry, int mode) +{ + struct smb_sb_info *server = server_from_dentry(dentry); + int error; + struct iattr attr; + + lock_kernel(); + smb_invalid_dir_cache(dir); + error = smb_proc_mkdir(dentry); + if (!error) { + if (server->opt.capabilities & SMB_CAP_UNIX) { + /* Set attributes for new directory */ + attr.ia_valid = ATTR_MODE; + attr.ia_mode = mode; + error = smb_proc_setattr_unix(dentry, &attr, 0, 0); + } + error = smb_instantiate(dentry, 0, 0); + } + unlock_kernel(); + return error; +} + +static int +smb_rmdir(struct inode *dir, struct dentry *dentry) +{ + struct inode *inode = dentry->d_inode; + int error; + + /* + * Close the directory if it's open. + */ + lock_kernel(); + smb_close(inode); + + /* + * Check that nobody else is using the directory.. + */ + error = -EBUSY; + if (!d_unhashed(dentry)) + goto out; + + smb_invalid_dir_cache(dir); + error = smb_proc_rmdir(dentry); + +out: + unlock_kernel(); + return error; +} + +static int +smb_unlink(struct inode *dir, struct dentry *dentry) +{ + int error; + + /* + * Close the file if it's open. + */ + lock_kernel(); + smb_close(dentry->d_inode); + + smb_invalid_dir_cache(dir); + error = smb_proc_unlink(dentry); + if (!error) + smb_renew_times(dentry); + unlock_kernel(); + return error; +} + +static int +smb_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry) +{ + int error; + + /* + * Close any open files, and check whether to delete the + * target before attempting the rename. + */ + lock_kernel(); + if (old_dentry->d_inode) + smb_close(old_dentry->d_inode); + if (new_dentry->d_inode) { + smb_close(new_dentry->d_inode); + error = smb_proc_unlink(new_dentry); + if (error) { + VERBOSE("unlink %s/%s, error=%d\n", + DENTRY_PATH(new_dentry), error); + goto out; + } + /* FIXME */ + d_delete(new_dentry); + } + + smb_invalid_dir_cache(old_dir); + smb_invalid_dir_cache(new_dir); + error = smb_proc_mv(old_dentry, new_dentry); + if (!error) { + smb_renew_times(old_dentry); + smb_renew_times(new_dentry); + } +out: + unlock_kernel(); + return error; +} + +/* + * FIXME: samba servers won't let you create device nodes unless uid/gid + * matches the connection credentials (and we don't know which those are ...) + */ +static int +smb_make_node(struct inode *dir, struct dentry *dentry, int mode, dev_t dev) +{ + int error; + struct iattr attr; + + attr.ia_valid = ATTR_MODE | ATTR_UID | ATTR_GID; + attr.ia_mode = mode; + attr.ia_uid = current->euid; + attr.ia_gid = current->egid; + + if (!new_valid_dev(dev)) + return -EINVAL; + + smb_invalid_dir_cache(dir); + error = smb_proc_setattr_unix(dentry, &attr, MAJOR(dev), MINOR(dev)); + if (!error) { + error = smb_instantiate(dentry, 0, 0); + } + return error; +} + +/* + * dentry = existing file + * new_dentry = new file + */ +static int +smb_link(struct dentry *dentry, struct inode *dir, struct dentry *new_dentry) +{ + int error; + + DEBUG1("smb_link old=%s/%s new=%s/%s\n", + DENTRY_PATH(dentry), DENTRY_PATH(new_dentry)); + smb_invalid_dir_cache(dir); + error = smb_proc_link(server_from_dentry(dentry), dentry, new_dentry); + if (!error) { + smb_renew_times(dentry); + error = smb_instantiate(new_dentry, 0, 0); + } + return error; +} diff --git a/fs/smbfs/file.c b/fs/smbfs/file.c new file mode 100644 index 00000000000..b4fcfa8b55a --- /dev/null +++ b/fs/smbfs/file.c @@ -0,0 +1,423 @@ +/* + * file.c + * + * Copyright (C) 1995, 1996, 1997 by Paal-Kr. Engstad and Volker Lendecke + * Copyright (C) 1997 by Volker Lendecke + * + * Please add a note about your changes to smbfs in the ChangeLog file. + */ + +#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/pagemap.h> +#include <linux/smp_lock.h> +#include <linux/net.h> + +#include <asm/uaccess.h> +#include <asm/system.h> + +#include <linux/smbno.h> +#include <linux/smb_fs.h> + +#include "smb_debug.h" +#include "proto.h" + +static int +smb_fsync(struct file *file, struct dentry * dentry, int datasync) +{ + struct smb_sb_info *server = server_from_dentry(dentry); + int result; + + VERBOSE("sync file %s/%s\n", DENTRY_PATH(dentry)); + + /* + * The VFS will writepage() all dirty pages for us, but we + * should send a SMBflush to the server, letting it know that + * we want things synchronized with actual storage. + * + * Note: this function requires all pages to have been written already + * (should be ok with writepage_sync) + */ + result = smb_proc_flush(server, SMB_I(dentry->d_inode)->fileid); + return result; +} + +/* + * Read a page synchronously. + */ +static int +smb_readpage_sync(struct dentry *dentry, struct page *page) +{ + char *buffer = kmap(page); + loff_t offset = (loff_t)page->index << PAGE_CACHE_SHIFT; + struct smb_sb_info *server = server_from_dentry(dentry); + unsigned int rsize = smb_get_rsize(server); + int count = PAGE_SIZE; + int result; + + VERBOSE("file %s/%s, count=%d@%Ld, rsize=%d\n", + DENTRY_PATH(dentry), count, offset, rsize); + + result = smb_open(dentry, SMB_O_RDONLY); + if (result < 0) + goto io_error; + + do { + if (count < rsize) + rsize = count; + + result = server->ops->read(dentry->d_inode,offset,rsize,buffer); + if (result < 0) + goto io_error; + + count -= result; + offset += result; + buffer += result; + dentry->d_inode->i_atime = + current_fs_time(dentry->d_inode->i_sb); + if (result < rsize) + break; + } while (count); + + memset(buffer, 0, count); + flush_dcache_page(page); + SetPageUptodate(page); + result = 0; + +io_error: + kunmap(page); + unlock_page(page); + return result; +} + +/* + * We are called with the page locked and we unlock it when done. + */ +static int +smb_readpage(struct file *file, struct page *page) +{ + int error; + struct dentry *dentry = file->f_dentry; + + page_cache_get(page); + error = smb_readpage_sync(dentry, page); + page_cache_release(page); + return error; +} + +/* + * Write a page synchronously. + * Offset is the data offset within the page. + */ +static int +smb_writepage_sync(struct inode *inode, struct page *page, + unsigned long pageoffset, unsigned int count) +{ + loff_t offset; + char *buffer = kmap(page) + pageoffset; + struct smb_sb_info *server = server_from_inode(inode); + unsigned int wsize = smb_get_wsize(server); + int ret = 0; + + offset = ((loff_t)page->index << PAGE_CACHE_SHIFT) + pageoffset; + VERBOSE("file ino=%ld, fileid=%d, count=%d@%Ld, wsize=%d\n", + inode->i_ino, SMB_I(inode)->fileid, count, offset, wsize); + + do { + int write_ret; + + if (count < wsize) + wsize = count; + + write_ret = server->ops->write(inode, offset, wsize, buffer); + if (write_ret < 0) { + PARANOIA("failed write, wsize=%d, write_ret=%d\n", + wsize, write_ret); + ret = write_ret; + break; + } + /* N.B. what if result < wsize?? */ +#ifdef SMBFS_PARANOIA + if (write_ret < wsize) + PARANOIA("short write, wsize=%d, write_ret=%d\n", + wsize, write_ret); +#endif + buffer += wsize; + offset += wsize; + count -= wsize; + /* + * Update the inode now rather than waiting for a refresh. + */ + inode->i_mtime = inode->i_atime = current_fs_time(inode->i_sb); + SMB_I(inode)->flags |= SMB_F_LOCALWRITE; + if (offset > inode->i_size) + inode->i_size = offset; + } while (count); + + kunmap(page); + return ret; +} + +/* + * Write a page to the server. This will be used for NFS swapping only + * (for now), and we currently do this synchronously only. + * + * We are called with the page locked and we unlock it when done. + */ +static int +smb_writepage(struct page *page, struct writeback_control *wbc) +{ + struct address_space *mapping = page->mapping; + struct inode *inode; + unsigned long end_index; + unsigned offset = PAGE_CACHE_SIZE; + int err; + + if (!mapping) + BUG(); + inode = mapping->host; + if (!inode) + BUG(); + + end_index = inode->i_size >> PAGE_CACHE_SHIFT; + + /* easy case */ + if (page->index < end_index) + goto do_it; + /* things got complicated... */ + offset = inode->i_size & (PAGE_CACHE_SIZE-1); + /* OK, are we completely out? */ + if (page->index >= end_index+1 || !offset) + return 0; /* truncated - don't care */ +do_it: + page_cache_get(page); + err = smb_writepage_sync(inode, page, 0, offset); + SetPageUptodate(page); + unlock_page(page); + page_cache_release(page); + return err; +} + +static int +smb_updatepage(struct file *file, struct page *page, unsigned long offset, + unsigned int count) +{ + struct dentry *dentry = file->f_dentry; + + DEBUG1("(%s/%s %d@%ld)\n", DENTRY_PATH(dentry), + count, (page->index << PAGE_CACHE_SHIFT)+offset); + + return smb_writepage_sync(dentry->d_inode, page, offset, count); +} + +static ssize_t +smb_file_read(struct file * file, char __user * buf, size_t count, loff_t *ppos) +{ + struct dentry * dentry = file->f_dentry; + ssize_t status; + + VERBOSE("file %s/%s, count=%lu@%lu\n", DENTRY_PATH(dentry), + (unsigned long) count, (unsigned long) *ppos); + + status = smb_revalidate_inode(dentry); + if (status) { + PARANOIA("%s/%s validation failed, error=%Zd\n", + DENTRY_PATH(dentry), status); + goto out; + } + + VERBOSE("before read, size=%ld, flags=%x, atime=%ld\n", + (long)dentry->d_inode->i_size, + dentry->d_inode->i_flags, dentry->d_inode->i_atime); + + status = generic_file_read(file, buf, count, ppos); +out: + return status; +} + +static int +smb_file_mmap(struct file * file, struct vm_area_struct * vma) +{ + struct dentry * dentry = file->f_dentry; + int status; + + VERBOSE("file %s/%s, address %lu - %lu\n", + DENTRY_PATH(dentry), vma->vm_start, vma->vm_end); + + status = smb_revalidate_inode(dentry); + if (status) { + PARANOIA("%s/%s validation failed, error=%d\n", + DENTRY_PATH(dentry), status); + goto out; + } + status = generic_file_mmap(file, vma); +out: + return status; +} + +static ssize_t +smb_file_sendfile(struct file *file, loff_t *ppos, + size_t count, read_actor_t actor, void *target) +{ + struct dentry *dentry = file->f_dentry; + ssize_t status; + + VERBOSE("file %s/%s, pos=%Ld, count=%d\n", + DENTRY_PATH(dentry), *ppos, count); + + status = smb_revalidate_inode(dentry); + if (status) { + PARANOIA("%s/%s validation failed, error=%Zd\n", + DENTRY_PATH(dentry), status); + goto out; + } + status = generic_file_sendfile(file, ppos, count, actor, target); +out: + return status; +} + +/* + * This does the "real" work of the write. The generic routine has + * allocated the page, locked it, done all the page alignment stuff + * calculations etc. Now we should just copy the data from user + * space and write it back to the real medium.. + * + * If the writer ends up delaying the write, the writer needs to + * increment the page use counts until he is done with the page. + */ +static int smb_prepare_write(struct file *file, struct page *page, + unsigned offset, unsigned to) +{ + return 0; +} + +static int smb_commit_write(struct file *file, struct page *page, + unsigned offset, unsigned to) +{ + int status; + + status = -EFAULT; + lock_kernel(); + status = smb_updatepage(file, page, offset, to-offset); + unlock_kernel(); + return status; +} + +struct address_space_operations smb_file_aops = { + .readpage = smb_readpage, + .writepage = smb_writepage, + .prepare_write = smb_prepare_write, + .commit_write = smb_commit_write +}; + +/* + * Write to a file (through the page cache). + */ +static ssize_t +smb_file_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) +{ + struct dentry * dentry = file->f_dentry; + ssize_t result; + + VERBOSE("file %s/%s, count=%lu@%lu\n", + DENTRY_PATH(dentry), + (unsigned long) count, (unsigned long) *ppos); + + result = smb_revalidate_inode(dentry); + if (result) { + PARANOIA("%s/%s validation failed, error=%Zd\n", + DENTRY_PATH(dentry), result); + goto out; + } + + result = smb_open(dentry, SMB_O_WRONLY); + if (result) + goto out; + + if (count > 0) { + result = generic_file_write(file, buf, count, ppos); + VERBOSE("pos=%ld, size=%ld, mtime=%ld, atime=%ld\n", + (long) file->f_pos, (long) dentry->d_inode->i_size, + dentry->d_inode->i_mtime, dentry->d_inode->i_atime); + } +out: + return result; +} + +static int +smb_file_open(struct inode *inode, struct file * file) +{ + int result; + struct dentry *dentry = file->f_dentry; + int smb_mode = (file->f_mode & O_ACCMODE) - 1; + + lock_kernel(); + result = smb_open(dentry, smb_mode); + if (result) + goto out; + SMB_I(inode)->openers++; +out: + unlock_kernel(); + return result; +} + +static int +smb_file_release(struct inode *inode, struct file * file) +{ + lock_kernel(); + if (!--SMB_I(inode)->openers) { + /* We must flush any dirty pages now as we won't be able to + write anything after close. mmap can trigger this. + "openers" should perhaps include mmap'ers ... */ + filemap_fdatawrite(inode->i_mapping); + filemap_fdatawait(inode->i_mapping); + smb_close(inode); + } + unlock_kernel(); + return 0; +} + +/* + * Check whether the required access is compatible with + * an inode's permission. SMB doesn't recognize superuser + * privileges, so we need our own check for this. + */ +static int +smb_file_permission(struct inode *inode, int mask, struct nameidata *nd) +{ + int mode = inode->i_mode; + int error = 0; + + VERBOSE("mode=%x, mask=%x\n", mode, mask); + + /* Look at user permissions */ + mode >>= 6; + if ((mode & 7 & mask) != mask) + error = -EACCES; + return error; +} + +struct file_operations smb_file_operations = +{ + .llseek = remote_llseek, + .read = smb_file_read, + .write = smb_file_write, + .ioctl = smb_ioctl, + .mmap = smb_file_mmap, + .open = smb_file_open, + .release = smb_file_release, + .fsync = smb_fsync, + .sendfile = smb_file_sendfile, +}; + +struct inode_operations smb_file_inode_operations = +{ + .permission = smb_file_permission, + .getattr = smb_getattr, + .setattr = smb_notify_change, +}; diff --git a/fs/smbfs/getopt.c b/fs/smbfs/getopt.c new file mode 100644 index 00000000000..7ae0f5273ab --- /dev/null +++ b/fs/smbfs/getopt.c @@ -0,0 +1,64 @@ +/* + * getopt.c + */ + +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/net.h> + +#include "getopt.h" + +/** + * smb_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 smb_getopt(char *caller, char **options, struct option *opts, + char **optopt, char **optarg, unsigned long *flag, + unsigned long *value) +{ + char *token; + char *val; + int i; + + do { + if ((token = strsep(options, ",")) == NULL) + return 0; + } while (*token == '\0'); + *optopt = token; + + *optarg = NULL; + if ((val = strchr (token, '=')) != NULL) { + *val++ = 0; + if (value) + *value = simple_strtoul(val, NULL, 0); + *optarg = val; + } + + for (i = 0; opts[i].name != NULL; i++) { + if (!strcmp(opts[i].name, token)) { + if (!opts[i].flag && (!val || !*val)) { + printk("%s: the %s option requires an argument\n", + caller, token); + return -1; + } + + if (flag && opts[i].flag) + *flag |= opts[i].flag; + + return opts[i].val; + } + } + printk("%s: Unrecognized mount option %s\n", caller, token); + return -1; +} diff --git a/fs/smbfs/getopt.h b/fs/smbfs/getopt.h new file mode 100644 index 00000000000..146219ac7c4 --- /dev/null +++ b/fs/smbfs/getopt.h @@ -0,0 +1,14 @@ +#ifndef _LINUX_GETOPT_H +#define _LINUX_GETOPT_H + +struct option { + const char *name; + unsigned long flag; + int val; +}; + +extern int smb_getopt(char *caller, char **options, struct option *opts, + char **optopt, char **optarg, unsigned long *flag, + unsigned long *value); + +#endif /* _LINUX_GETOPT_H */ diff --git a/fs/smbfs/inode.c b/fs/smbfs/inode.c new file mode 100644 index 00000000000..4765aaac9fd --- /dev/null +++ b/fs/smbfs/inode.c @@ -0,0 +1,849 @@ +/* + * inode.c + * + * Copyright (C) 1995, 1996 by Paal-Kr. Engstad and Volker Lendecke + * Copyright (C) 1997 by Volker Lendecke + * + * Please add a note about your changes to smbfs in the ChangeLog file. + */ + +#include <linux/config.h> +#include <linux/module.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/slab.h> +#include <linux/init.h> +#include <linux/file.h> +#include <linux/dcache.h> +#include <linux/smp_lock.h> +#include <linux/nls.h> +#include <linux/seq_file.h> +#include <linux/mount.h> +#include <linux/net.h> +#include <linux/vfs.h> +#include <linux/highuid.h> +#include <linux/smb_fs.h> +#include <linux/smbno.h> +#include <linux/smb_mount.h> + +#include <asm/system.h> +#include <asm/uaccess.h> + +#include "smb_debug.h" +#include "getopt.h" +#include "proto.h" + +/* Always pick a default string */ +#ifdef CONFIG_SMB_NLS_REMOTE +#define SMB_NLS_REMOTE CONFIG_SMB_NLS_REMOTE +#else +#define SMB_NLS_REMOTE "" +#endif + +#define SMB_TTL_DEFAULT 1000 + +static void smb_delete_inode(struct inode *); +static void smb_put_super(struct super_block *); +static int smb_statfs(struct super_block *, struct kstatfs *); +static int smb_show_options(struct seq_file *, struct vfsmount *); + +static kmem_cache_t *smb_inode_cachep; + +static struct inode *smb_alloc_inode(struct super_block *sb) +{ + struct smb_inode_info *ei; + ei = (struct smb_inode_info *)kmem_cache_alloc(smb_inode_cachep, SLAB_KERNEL); + if (!ei) + return NULL; + return &ei->vfs_inode; +} + +static void smb_destroy_inode(struct inode *inode) +{ + kmem_cache_free(smb_inode_cachep, SMB_I(inode)); +} + +static void init_once(void * foo, kmem_cache_t * cachep, unsigned long flags) +{ + struct smb_inode_info *ei = (struct smb_inode_info *) foo; + unsigned long flagmask = SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR; + + if ((flags & flagmask) == SLAB_CTOR_CONSTRUCTOR) + inode_init_once(&ei->vfs_inode); +} + +static int init_inodecache(void) +{ + smb_inode_cachep = kmem_cache_create("smb_inode_cache", + sizeof(struct smb_inode_info), + 0, SLAB_RECLAIM_ACCOUNT, + init_once, NULL); + if (smb_inode_cachep == NULL) + return -ENOMEM; + return 0; +} + +static void destroy_inodecache(void) +{ + if (kmem_cache_destroy(smb_inode_cachep)) + printk(KERN_INFO "smb_inode_cache: not all structures were freed\n"); +} + +static int smb_remount(struct super_block *sb, int *flags, char *data) +{ + *flags |= MS_NODIRATIME; + return 0; +} + +static struct super_operations smb_sops = +{ + .alloc_inode = smb_alloc_inode, + .destroy_inode = smb_destroy_inode, + .drop_inode = generic_delete_inode, + .delete_inode = smb_delete_inode, + .put_super = smb_put_super, + .statfs = smb_statfs, + .show_options = smb_show_options, + .remount_fs = smb_remount, +}; + + +/* We are always generating a new inode here */ +struct inode * +smb_iget(struct super_block *sb, struct smb_fattr *fattr) +{ + struct smb_sb_info *server = SMB_SB(sb); + struct inode *result; + + DEBUG1("smb_iget: %p\n", fattr); + + result = new_inode(sb); + if (!result) + return result; + result->i_ino = fattr->f_ino; + SMB_I(result)->open = 0; + SMB_I(result)->fileid = 0; + SMB_I(result)->access = 0; + SMB_I(result)->flags = 0; + SMB_I(result)->closed = 0; + SMB_I(result)->openers = 0; + smb_set_inode_attr(result, fattr); + if (S_ISREG(result->i_mode)) { + result->i_op = &smb_file_inode_operations; + result->i_fop = &smb_file_operations; + result->i_data.a_ops = &smb_file_aops; + } else if (S_ISDIR(result->i_mode)) { + if (server->opt.capabilities & SMB_CAP_UNIX) + result->i_op = &smb_dir_inode_operations_unix; + else + result->i_op = &smb_dir_inode_operations; + result->i_fop = &smb_dir_operations; + } else if (S_ISLNK(result->i_mode)) { + result->i_op = &smb_link_inode_operations; + } else { + init_special_inode(result, result->i_mode, fattr->f_rdev); + } + insert_inode_hash(result); + return result; +} + +/* + * Copy the inode data to a smb_fattr structure. + */ +void +smb_get_inode_attr(struct inode *inode, struct smb_fattr *fattr) +{ + memset(fattr, 0, sizeof(struct smb_fattr)); + fattr->f_mode = inode->i_mode; + fattr->f_nlink = inode->i_nlink; + fattr->f_ino = inode->i_ino; + fattr->f_uid = inode->i_uid; + fattr->f_gid = inode->i_gid; + fattr->f_size = inode->i_size; + fattr->f_mtime = inode->i_mtime; + fattr->f_ctime = inode->i_ctime; + fattr->f_atime = inode->i_atime; + fattr->f_blksize= inode->i_blksize; + fattr->f_blocks = inode->i_blocks; + + fattr->attr = SMB_I(inode)->attr; + /* + * Keep the attributes in sync with the inode permissions. + */ + if (fattr->f_mode & S_IWUSR) + fattr->attr &= ~aRONLY; + else + fattr->attr |= aRONLY; +} + +/* + * Update the inode, possibly causing it to invalidate its pages if mtime/size + * is different from last time. + */ +void +smb_set_inode_attr(struct inode *inode, struct smb_fattr *fattr) +{ + struct smb_inode_info *ei = SMB_I(inode); + + /* + * A size change should have a different mtime, or same mtime + * but different size. + */ + time_t last_time = inode->i_mtime.tv_sec; + loff_t last_sz = inode->i_size; + + inode->i_mode = fattr->f_mode; + inode->i_nlink = fattr->f_nlink; + inode->i_uid = fattr->f_uid; + inode->i_gid = fattr->f_gid; + inode->i_ctime = fattr->f_ctime; + inode->i_blksize= fattr->f_blksize; + inode->i_blocks = fattr->f_blocks; + inode->i_size = fattr->f_size; + inode->i_mtime = fattr->f_mtime; + inode->i_atime = fattr->f_atime; + ei->attr = fattr->attr; + + /* + * Update the "last time refreshed" field for revalidation. + */ + ei->oldmtime = jiffies; + + if (inode->i_mtime.tv_sec != last_time || inode->i_size != last_sz) { + VERBOSE("%ld changed, old=%ld, new=%ld, oz=%ld, nz=%ld\n", + inode->i_ino, + (long) last_time, (long) inode->i_mtime, + (long) last_sz, (long) inode->i_size); + + if (!S_ISDIR(inode->i_mode)) + invalidate_remote_inode(inode); + } +} + +/* + * This is called if the connection has gone bad ... + * try to kill off all the current inodes. + */ +void +smb_invalidate_inodes(struct smb_sb_info *server) +{ + VERBOSE("\n"); + shrink_dcache_sb(SB_of(server)); + invalidate_inodes(SB_of(server)); +} + +/* + * This is called to update the inode attributes after + * we've made changes to a file or directory. + */ +static int +smb_refresh_inode(struct dentry *dentry) +{ + struct inode *inode = dentry->d_inode; + int error; + struct smb_fattr fattr; + + error = smb_proc_getattr(dentry, &fattr); + if (!error) { + smb_renew_times(dentry); + /* + * Check whether the type part of the mode changed, + * and don't update the attributes if it did. + * + * And don't dick with the root inode + */ + if (inode->i_ino == 2) + return error; + if (S_ISLNK(inode->i_mode)) + return error; /* VFS will deal with it */ + + if ((inode->i_mode & S_IFMT) == (fattr.f_mode & S_IFMT)) { + smb_set_inode_attr(inode, &fattr); + } else { + /* + * Big trouble! The inode has become a new object, + * so any operations attempted on it are invalid. + * + * To limit damage, mark the inode as bad so that + * subsequent lookup validations will fail. + */ + PARANOIA("%s/%s changed mode, %07o to %07o\n", + DENTRY_PATH(dentry), + inode->i_mode, fattr.f_mode); + + fattr.f_mode = inode->i_mode; /* save mode */ + make_bad_inode(inode); + inode->i_mode = fattr.f_mode; /* restore mode */ + /* + * No need to worry about unhashing the dentry: the + * lookup validation will see that the inode is bad. + * But we do want to invalidate the caches ... + */ + if (!S_ISDIR(inode->i_mode)) + invalidate_remote_inode(inode); + else + smb_invalid_dir_cache(inode); + error = -EIO; + } + } + return error; +} + +/* + * This is called when we want to check whether the inode + * has changed on the server. If it has changed, we must + * invalidate our local caches. + */ +int +smb_revalidate_inode(struct dentry *dentry) +{ + struct smb_sb_info *s = server_from_dentry(dentry); + struct inode *inode = dentry->d_inode; + int error = 0; + + DEBUG1("smb_revalidate_inode\n"); + lock_kernel(); + + /* + * Check whether we've recently refreshed the inode. + */ + if (time_before(jiffies, SMB_I(inode)->oldmtime + SMB_MAX_AGE(s))) { + VERBOSE("up-to-date, ino=%ld, jiffies=%lu, oldtime=%lu\n", + inode->i_ino, jiffies, SMB_I(inode)->oldmtime); + goto out; + } + + error = smb_refresh_inode(dentry); +out: + unlock_kernel(); + return error; +} + +/* + * This routine is called when i_nlink == 0 and i_count goes to 0. + * All blocking cleanup operations need to go here to avoid races. + */ +static void +smb_delete_inode(struct inode *ino) +{ + DEBUG1("ino=%ld\n", ino->i_ino); + lock_kernel(); + if (smb_close(ino)) + PARANOIA("could not close inode %ld\n", ino->i_ino); + unlock_kernel(); + clear_inode(ino); +} + +static struct option opts[] = { + { "version", 0, 'v' }, + { "win95", SMB_MOUNT_WIN95, 1 }, + { "oldattr", SMB_MOUNT_OLDATTR, 1 }, + { "dirattr", SMB_MOUNT_DIRATTR, 1 }, + { "case", SMB_MOUNT_CASE, 1 }, + { "uid", 0, 'u' }, + { "gid", 0, 'g' }, + { "file_mode", 0, 'f' }, + { "dir_mode", 0, 'd' }, + { "iocharset", 0, 'i' }, + { "codepage", 0, 'c' }, + { "ttl", 0, 't' }, + { NULL, 0, 0} +}; + +static int +parse_options(struct smb_mount_data_kernel *mnt, char *options) +{ + int c; + unsigned long flags; + unsigned long value; + char *optarg; + char *optopt; + + flags = 0; + while ( (c = smb_getopt("smbfs", &options, opts, + &optopt, &optarg, &flags, &value)) > 0) { + + VERBOSE("'%s' -> '%s'\n", optopt, optarg ? optarg : "<none>"); + switch (c) { + case 1: + /* got a "flag" option */ + break; + case 'v': + if (value != SMB_MOUNT_VERSION) { + printk ("smbfs: Bad mount version %ld, expected %d\n", + value, SMB_MOUNT_VERSION); + return 0; + } + mnt->version = value; + break; + case 'u': + mnt->uid = value; + flags |= SMB_MOUNT_UID; + break; + case 'g': + mnt->gid = value; + flags |= SMB_MOUNT_GID; + break; + case 'f': + mnt->file_mode = (value & S_IRWXUGO) | S_IFREG; + flags |= SMB_MOUNT_FMODE; + break; + case 'd': + mnt->dir_mode = (value & S_IRWXUGO) | S_IFDIR; + flags |= SMB_MOUNT_DMODE; + break; + case 'i': + strlcpy(mnt->codepage.local_name, optarg, + SMB_NLS_MAXNAMELEN); + break; + case 'c': + strlcpy(mnt->codepage.remote_name, optarg, + SMB_NLS_MAXNAMELEN); + break; + case 't': + mnt->ttl = value; + break; + default: + printk ("smbfs: Unrecognized mount option %s\n", + optopt); + return -1; + } + } + mnt->flags = flags; + return c; +} + +/* + * smb_show_options() is for displaying mount options in /proc/mounts. + * It tries to avoid showing settings that were not changed from their + * defaults. + */ +static int +smb_show_options(struct seq_file *s, struct vfsmount *m) +{ + struct smb_mount_data_kernel *mnt = SMB_SB(m->mnt_sb)->mnt; + int i; + + for (i = 0; opts[i].name != NULL; i++) + if (mnt->flags & opts[i].flag) + seq_printf(s, ",%s", opts[i].name); + + if (mnt->flags & SMB_MOUNT_UID) + seq_printf(s, ",uid=%d", mnt->uid); + if (mnt->flags & SMB_MOUNT_GID) + seq_printf(s, ",gid=%d", mnt->gid); + if (mnt->mounted_uid != 0) + seq_printf(s, ",mounted_uid=%d", mnt->mounted_uid); + + /* + * Defaults for file_mode and dir_mode are unknown to us; they + * depend on the current umask of the user doing the mount. + */ + if (mnt->flags & SMB_MOUNT_FMODE) + seq_printf(s, ",file_mode=%04o", mnt->file_mode & S_IRWXUGO); + if (mnt->flags & SMB_MOUNT_DMODE) + seq_printf(s, ",dir_mode=%04o", mnt->dir_mode & S_IRWXUGO); + + if (strcmp(mnt->codepage.local_name, CONFIG_NLS_DEFAULT)) + seq_printf(s, ",iocharset=%s", mnt->codepage.local_name); + if (strcmp(mnt->codepage.remote_name, SMB_NLS_REMOTE)) + seq_printf(s, ",codepage=%s", mnt->codepage.remote_name); + + if (mnt->ttl != SMB_TTL_DEFAULT) + seq_printf(s, ",ttl=%d", mnt->ttl); + + return 0; +} + +static void +smb_unload_nls(struct smb_sb_info *server) +{ + if (server->remote_nls) { + unload_nls(server->remote_nls); + server->remote_nls = NULL; + } + if (server->local_nls) { + unload_nls(server->local_nls); + server->local_nls = NULL; + } +} + +static void +smb_put_super(struct super_block *sb) +{ + struct smb_sb_info *server = SMB_SB(sb); + + smb_lock_server(server); + server->state = CONN_INVALID; + smbiod_unregister_server(server); + + smb_close_socket(server); + + if (server->conn_pid) + kill_proc(server->conn_pid, SIGTERM, 1); + + smb_kfree(server->ops); + smb_unload_nls(server); + sb->s_fs_info = NULL; + smb_unlock_server(server); + smb_kfree(server); +} + +static int smb_fill_super(struct super_block *sb, void *raw_data, int silent) +{ + struct smb_sb_info *server; + struct smb_mount_data_kernel *mnt; + struct smb_mount_data *oldmnt; + struct inode *root_inode; + struct smb_fattr root; + int ver; + void *mem; + + if (!raw_data) + goto out_no_data; + + oldmnt = (struct smb_mount_data *) raw_data; + ver = oldmnt->version; + if (ver != SMB_MOUNT_OLDVERSION && cpu_to_be32(ver) != SMB_MOUNT_ASCII) + goto out_wrong_data; + + sb->s_flags |= MS_NODIRATIME; + sb->s_blocksize = 1024; /* Eh... Is this correct? */ + sb->s_blocksize_bits = 10; + sb->s_magic = SMB_SUPER_MAGIC; + sb->s_op = &smb_sops; + sb->s_time_gran = 100; + + server = smb_kmalloc(sizeof(struct smb_sb_info), GFP_KERNEL); + if (!server) + goto out_no_server; + sb->s_fs_info = server; + memset(server, 0, sizeof(struct smb_sb_info)); + + server->super_block = sb; + server->mnt = NULL; + server->sock_file = NULL; + init_waitqueue_head(&server->conn_wq); + init_MUTEX(&server->sem); + INIT_LIST_HEAD(&server->entry); + INIT_LIST_HEAD(&server->xmitq); + INIT_LIST_HEAD(&server->recvq); + server->conn_error = 0; + server->conn_pid = 0; + server->state = CONN_INVALID; /* no connection yet */ + server->generation = 0; + + /* Allocate the global temp buffer and some superblock helper structs */ + /* FIXME: move these to the smb_sb_info struct */ + VERBOSE("alloc chunk = %d\n", sizeof(struct smb_ops) + + sizeof(struct smb_mount_data_kernel)); + mem = smb_kmalloc(sizeof(struct smb_ops) + + sizeof(struct smb_mount_data_kernel), GFP_KERNEL); + if (!mem) + goto out_no_mem; + + server->ops = mem; + smb_install_null_ops(server->ops); + server->mnt = mem + sizeof(struct smb_ops); + + /* Setup NLS stuff */ + server->remote_nls = NULL; + server->local_nls = NULL; + + mnt = server->mnt; + + memset(mnt, 0, sizeof(struct smb_mount_data_kernel)); + strlcpy(mnt->codepage.local_name, CONFIG_NLS_DEFAULT, + SMB_NLS_MAXNAMELEN); + strlcpy(mnt->codepage.remote_name, SMB_NLS_REMOTE, + SMB_NLS_MAXNAMELEN); + + mnt->ttl = SMB_TTL_DEFAULT; + if (ver == SMB_MOUNT_OLDVERSION) { + mnt->version = oldmnt->version; + + SET_UID(mnt->uid, oldmnt->uid); + SET_GID(mnt->gid, oldmnt->gid); + + mnt->file_mode = (oldmnt->file_mode & S_IRWXUGO) | S_IFREG; + mnt->dir_mode = (oldmnt->dir_mode & S_IRWXUGO) | S_IFDIR; + + mnt->flags = (oldmnt->file_mode >> 9) | SMB_MOUNT_UID | + SMB_MOUNT_GID | SMB_MOUNT_FMODE | SMB_MOUNT_DMODE; + } else { + mnt->file_mode = S_IRWXU | S_IRGRP | S_IXGRP | + S_IROTH | S_IXOTH | S_IFREG; + mnt->dir_mode = S_IRWXU | S_IRGRP | S_IXGRP | + S_IROTH | S_IXOTH | S_IFDIR; + if (parse_options(mnt, raw_data)) + goto out_bad_option; + } + mnt->mounted_uid = current->uid; + smb_setcodepage(server, &mnt->codepage); + + /* + * Display the enabled options + * Note: smb_proc_getattr uses these in 2.4 (but was changed in 2.2) + */ + if (mnt->flags & SMB_MOUNT_OLDATTR) + printk("SMBFS: Using core getattr (Win 95 speedup)\n"); + else if (mnt->flags & SMB_MOUNT_DIRATTR) + printk("SMBFS: Using dir ff getattr\n"); + + if (smbiod_register_server(server) < 0) { + printk(KERN_ERR "smbfs: failed to start smbiod\n"); + goto out_no_smbiod; + } + + /* + * Keep the super block locked while we get the root inode. + */ + smb_init_root_dirent(server, &root, sb); + root_inode = smb_iget(sb, &root); + if (!root_inode) + goto out_no_root; + + sb->s_root = d_alloc_root(root_inode); + if (!sb->s_root) + goto out_no_root; + + smb_new_dentry(sb->s_root); + + return 0; + +out_no_root: + iput(root_inode); +out_no_smbiod: + smb_unload_nls(server); +out_bad_option: + smb_kfree(mem); +out_no_mem: + if (!server->mnt) + printk(KERN_ERR "smb_fill_super: allocation failure\n"); + sb->s_fs_info = NULL; + smb_kfree(server); + goto out_fail; +out_wrong_data: + printk(KERN_ERR "smbfs: mount_data version %d is not supported\n", ver); + goto out_fail; +out_no_data: + printk(KERN_ERR "smb_fill_super: missing data argument\n"); +out_fail: + return -EINVAL; +out_no_server: + printk(KERN_ERR "smb_fill_super: cannot allocate struct smb_sb_info\n"); + return -ENOMEM; +} + +static int +smb_statfs(struct super_block *sb, struct kstatfs *buf) +{ + int result; + + lock_kernel(); + + result = smb_proc_dskattr(sb, buf); + + unlock_kernel(); + + buf->f_type = SMB_SUPER_MAGIC; + buf->f_namelen = SMB_MAXPATHLEN; + return result; +} + +int smb_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat) +{ + int err = smb_revalidate_inode(dentry); + if (!err) + generic_fillattr(dentry->d_inode, stat); + return err; +} + +int +smb_notify_change(struct dentry *dentry, struct iattr *attr) +{ + struct inode *inode = dentry->d_inode; + struct smb_sb_info *server = server_from_dentry(dentry); + unsigned int mask = (S_IFREG | S_IFDIR | S_IRWXUGO); + int error, changed, refresh = 0; + struct smb_fattr fattr; + + lock_kernel(); + + error = smb_revalidate_inode(dentry); + if (error) + goto out; + + if ((error = inode_change_ok(inode, attr)) < 0) + goto out; + + error = -EPERM; + if ((attr->ia_valid & ATTR_UID) && (attr->ia_uid != server->mnt->uid)) + goto out; + + if ((attr->ia_valid & ATTR_GID) && (attr->ia_uid != server->mnt->gid)) + goto out; + + if ((attr->ia_valid & ATTR_MODE) && (attr->ia_mode & ~mask)) + goto out; + + if ((attr->ia_valid & ATTR_SIZE) != 0) { + VERBOSE("changing %s/%s, old size=%ld, new size=%ld\n", + DENTRY_PATH(dentry), + (long) inode->i_size, (long) attr->ia_size); + + filemap_fdatawrite(inode->i_mapping); + filemap_fdatawait(inode->i_mapping); + + error = smb_open(dentry, O_WRONLY); + if (error) + goto out; + error = server->ops->truncate(inode, attr->ia_size); + if (error) + goto out; + error = vmtruncate(inode, attr->ia_size); + if (error) + goto out; + refresh = 1; + } + + if (server->opt.capabilities & SMB_CAP_UNIX) { + /* For now we don't want to set the size with setattr_unix */ + attr->ia_valid &= ~ATTR_SIZE; + /* FIXME: only call if we actually want to set something? */ + error = smb_proc_setattr_unix(dentry, attr, 0, 0); + if (!error) + refresh = 1; + + goto out; + } + + /* + * Initialize the fattr and check for changed fields. + * Note: CTIME under SMB is creation time rather than + * change time, so we don't attempt to change it. + */ + smb_get_inode_attr(inode, &fattr); + + changed = 0; + if ((attr->ia_valid & ATTR_MTIME) != 0) { + fattr.f_mtime = attr->ia_mtime; + changed = 1; + } + if ((attr->ia_valid & ATTR_ATIME) != 0) { + fattr.f_atime = attr->ia_atime; + /* Earlier protocols don't have an access time */ + if (server->opt.protocol >= SMB_PROTOCOL_LANMAN2) + changed = 1; + } + if (changed) { + error = smb_proc_settime(dentry, &fattr); + if (error) + goto out; + refresh = 1; + } + + /* + * Check for mode changes ... we're extremely limited in + * what can be set for SMB servers: just the read-only bit. + */ + if ((attr->ia_valid & ATTR_MODE) != 0) { + VERBOSE("%s/%s mode change, old=%x, new=%x\n", + DENTRY_PATH(dentry), fattr.f_mode, attr->ia_mode); + changed = 0; + if (attr->ia_mode & S_IWUSR) { + if (fattr.attr & aRONLY) { + fattr.attr &= ~aRONLY; + changed = 1; + } + } else { + if (!(fattr.attr & aRONLY)) { + fattr.attr |= aRONLY; + changed = 1; + } + } + if (changed) { + error = smb_proc_setattr(dentry, &fattr); + if (error) + goto out; + refresh = 1; + } + } + error = 0; + +out: + if (refresh) + smb_refresh_inode(dentry); + unlock_kernel(); + return error; +} + +#ifdef DEBUG_SMB_MALLOC +int smb_malloced; +int smb_current_kmalloced; +int smb_current_vmalloced; +#endif + +static struct super_block *smb_get_sb(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data) +{ + return get_sb_nodev(fs_type, flags, data, smb_fill_super); +} + +static struct file_system_type smb_fs_type = { + .owner = THIS_MODULE, + .name = "smbfs", + .get_sb = smb_get_sb, + .kill_sb = kill_anon_super, + .fs_flags = FS_BINARY_MOUNTDATA, +}; + +static int __init init_smb_fs(void) +{ + int err; + DEBUG1("registering ...\n"); + +#ifdef DEBUG_SMB_MALLOC + smb_malloced = 0; + smb_current_kmalloced = 0; + smb_current_vmalloced = 0; +#endif + + err = init_inodecache(); + if (err) + goto out_inode; + err = smb_init_request_cache(); + if (err) + goto out_request; + err = register_filesystem(&smb_fs_type); + if (err) + goto out; + return 0; +out: + smb_destroy_request_cache(); +out_request: + destroy_inodecache(); +out_inode: + return err; +} + +static void __exit exit_smb_fs(void) +{ + DEBUG1("unregistering ...\n"); + unregister_filesystem(&smb_fs_type); + smb_destroy_request_cache(); + destroy_inodecache(); +#ifdef DEBUG_SMB_MALLOC + printk(KERN_DEBUG "smb_malloced: %d\n", smb_malloced); + printk(KERN_DEBUG "smb_current_kmalloced: %d\n",smb_current_kmalloced); + printk(KERN_DEBUG "smb_current_vmalloced: %d\n",smb_current_vmalloced); +#endif +} + +module_init(init_smb_fs) +module_exit(exit_smb_fs) +MODULE_LICENSE("GPL"); diff --git a/fs/smbfs/ioctl.c b/fs/smbfs/ioctl.c new file mode 100644 index 00000000000..dbae1f8ea26 --- /dev/null +++ b/fs/smbfs/ioctl.c @@ -0,0 +1,67 @@ +/* + * ioctl.c + * + * Copyright (C) 1995, 1996 by Volker Lendecke + * Copyright (C) 1997 by Volker Lendecke + * + * Please add a note about your changes to smbfs in the ChangeLog file. + */ + +#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/net.h> + +#include <linux/smb_fs.h> +#include <linux/smb_mount.h> + +#include <asm/uaccess.h> + +#include "proto.h" + +int +smb_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + struct smb_sb_info *server = server_from_inode(inode); + struct smb_conn_opt opt; + int result = -EINVAL; + + switch (cmd) { + uid16_t uid16; + uid_t uid32; + case SMB_IOC_GETMOUNTUID: + SET_UID(uid16, server->mnt->mounted_uid); + result = put_user(uid16, (uid16_t __user *) arg); + break; + case SMB_IOC_GETMOUNTUID32: + SET_UID(uid32, server->mnt->mounted_uid); + result = put_user(uid32, (uid_t __user *) arg); + break; + + case SMB_IOC_NEWCONN: + /* arg is smb_conn_opt, or NULL if no connection was made */ + if (!arg) { + result = 0; + smb_lock_server(server); + server->state = CONN_RETRIED; + printk(KERN_ERR "Connection attempt failed! [%d]\n", + server->conn_error); + smbiod_flush(server); + smb_unlock_server(server); + break; + } + + result = -EFAULT; + if (!copy_from_user(&opt, (void __user *)arg, sizeof(opt))) + result = smb_newconn(server, &opt); + break; + default: + break; + } + + return result; +} diff --git a/fs/smbfs/proc.c b/fs/smbfs/proc.c new file mode 100644 index 00000000000..220babe91ef --- /dev/null +++ b/fs/smbfs/proc.c @@ -0,0 +1,3509 @@ +/* + * proc.c + * + * Copyright (C) 1995, 1996 by Paal-Kr. Engstad and Volker Lendecke + * Copyright (C) 1997 by Volker Lendecke + * + * Please add a note about your changes to smbfs in the ChangeLog file. + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/file.h> +#include <linux/stat.h> +#include <linux/fcntl.h> +#include <linux/dcache.h> +#include <linux/dirent.h> +#include <linux/nls.h> +#include <linux/smp_lock.h> +#include <linux/net.h> +#include <linux/vfs.h> +#include <linux/smb_fs.h> +#include <linux/smbno.h> +#include <linux/smb_mount.h> + +#include <net/sock.h> + +#include <asm/string.h> +#include <asm/div64.h> + +#include "smb_debug.h" +#include "proto.h" +#include "request.h" + + +/* Features. Undefine if they cause problems, this should perhaps be a + config option. */ +#define SMBFS_POSIX_UNLINK 1 + +/* Allow smb_retry to be interrupted. */ +#define SMB_RETRY_INTR + +#define SMB_VWV(packet) ((packet) + SMB_HEADER_LEN) +#define SMB_CMD(packet) (*(packet+8)) +#define SMB_WCT(packet) (*(packet+SMB_HEADER_LEN - 1)) + +#define SMB_DIRINFO_SIZE 43 +#define SMB_STATUS_SIZE 21 + +#define SMB_ST_BLKSIZE (PAGE_SIZE) +#define SMB_ST_BLKSHIFT (PAGE_SHIFT) + +static struct smb_ops smb_ops_core; +static struct smb_ops smb_ops_os2; +static struct smb_ops smb_ops_win95; +static struct smb_ops smb_ops_winNT; +static struct smb_ops smb_ops_unix; +static struct smb_ops smb_ops_null; + +static void +smb_init_dirent(struct smb_sb_info *server, struct smb_fattr *fattr); +static void +smb_finish_dirent(struct smb_sb_info *server, struct smb_fattr *fattr); +static int +smb_proc_getattr_core(struct smb_sb_info *server, struct dentry *dir, + struct smb_fattr *fattr); +static int +smb_proc_getattr_ff(struct smb_sb_info *server, struct dentry *dentry, + struct smb_fattr *fattr); +static int +smb_proc_setattr_core(struct smb_sb_info *server, struct dentry *dentry, + u16 attr); +static int +smb_proc_setattr_ext(struct smb_sb_info *server, + struct inode *inode, struct smb_fattr *fattr); +static int +smb_proc_query_cifsunix(struct smb_sb_info *server); +static void +install_ops(struct smb_ops *dst, struct smb_ops *src); + + +static void +str_upper(char *name, int len) +{ + while (len--) + { + if (*name >= 'a' && *name <= 'z') + *name -= ('a' - 'A'); + name++; + } +} + +#if 0 +static void +str_lower(char *name, int len) +{ + while (len--) + { + if (*name >= 'A' && *name <= 'Z') + *name += ('a' - 'A'); + name++; + } +} +#endif + +/* reverse a string inline. This is used by the dircache walking routines */ +static void reverse_string(char *buf, int len) +{ + char c; + char *end = buf+len-1; + + while(buf < end) { + c = *buf; + *(buf++) = *end; + *(end--) = c; + } +} + +/* no conversion, just a wrapper for memcpy. */ +static int convert_memcpy(unsigned char *output, int olen, + const unsigned char *input, int ilen, + struct nls_table *nls_from, + struct nls_table *nls_to) +{ + if (olen < ilen) + return -ENAMETOOLONG; + memcpy(output, input, ilen); + return ilen; +} + +static inline int write_char(unsigned char ch, char *output, int olen) +{ + if (olen < 4) + return -ENAMETOOLONG; + sprintf(output, ":x%02x", ch); + return 4; +} + +static inline int write_unichar(wchar_t ch, char *output, int olen) +{ + if (olen < 5) + return -ENAMETOOLONG; + sprintf(output, ":%04x", ch); + return 5; +} + +/* convert from one "codepage" to another (possibly being utf8). */ +static int convert_cp(unsigned char *output, int olen, + const unsigned char *input, int ilen, + struct nls_table *nls_from, + struct nls_table *nls_to) +{ + int len = 0; + int n; + wchar_t ch; + + while (ilen > 0) { + /* convert by changing to unicode and back to the new cp */ + n = nls_from->char2uni(input, ilen, &ch); + if (n == -EINVAL) { + ilen--; + n = write_char(*input++, output, olen); + if (n < 0) + goto fail; + output += n; + olen -= n; + len += n; + continue; + } else if (n < 0) + goto fail; + input += n; + ilen -= n; + + n = nls_to->uni2char(ch, output, olen); + if (n == -EINVAL) + n = write_unichar(ch, output, olen); + if (n < 0) + goto fail; + output += n; + olen -= n; + + len += n; + } + return len; +fail: + return n; +} + +/* ----------------------------------------------------------- */ + +/* + * nls_unicode + * + * This encodes/decodes little endian unicode format + */ + +static int uni2char(wchar_t uni, unsigned char *out, int boundlen) +{ + if (boundlen < 2) + return -EINVAL; + *out++ = uni & 0xff; + *out++ = uni >> 8; + return 2; +} + +static int char2uni(const unsigned char *rawstring, int boundlen, wchar_t *uni) +{ + if (boundlen < 2) + return -EINVAL; + *uni = (rawstring[1] << 8) | rawstring[0]; + return 2; +} + +static struct nls_table unicode_table = { + .charset = "unicode", + .uni2char = uni2char, + .char2uni = char2uni, +}; + +/* ----------------------------------------------------------- */ + +static int setcodepage(struct nls_table **p, char *name) +{ + struct nls_table *nls; + + if (!name || !*name) { + nls = NULL; + } else if ( (nls = load_nls(name)) == NULL) { + printk (KERN_ERR "smbfs: failed to load nls '%s'\n", name); + return -EINVAL; + } + + /* if already set, unload the previous one. */ + if (*p && *p != &unicode_table) + unload_nls(*p); + *p = nls; + + return 0; +} + +/* Handles all changes to codepage settings. */ +int smb_setcodepage(struct smb_sb_info *server, struct smb_nls_codepage *cp) +{ + int n = 0; + + smb_lock_server(server); + + /* Don't load any nls_* at all, if no remote is requested */ + if (!*cp->remote_name) + goto out; + + /* local */ + n = setcodepage(&server->local_nls, cp->local_name); + if (n != 0) + goto out; + + /* remote */ + if (!strcmp(cp->remote_name, "unicode")) { + server->remote_nls = &unicode_table; + } else { + n = setcodepage(&server->remote_nls, cp->remote_name); + if (n != 0) + setcodepage(&server->local_nls, NULL); + } + +out: + if (server->local_nls != NULL && server->remote_nls != NULL) + server->ops->convert = convert_cp; + else + server->ops->convert = convert_memcpy; + + smb_unlock_server(server); + return n; +} + + +/*****************************************************************************/ +/* */ +/* Encoding/Decoding section */ +/* */ +/*****************************************************************************/ + +static __u8 * +smb_encode_smb_length(__u8 * p, __u32 len) +{ + *p = 0; + *(p+1) = 0; + *(p+2) = (len & 0xFF00) >> 8; + *(p+3) = (len & 0xFF); + if (len > 0xFFFF) + { + *(p+1) = 1; + } + return p + 4; +} + +/* + * smb_build_path: build the path to entry and name storing it in buf. + * The path returned will have the trailing '\0'. + */ +static int smb_build_path(struct smb_sb_info *server, unsigned char *buf, + int maxlen, + struct dentry *entry, struct qstr *name) +{ + unsigned char *path = buf; + int len; + int unicode = (server->mnt->flags & SMB_MOUNT_UNICODE) != 0; + + if (maxlen < (2<<unicode)) + return -ENAMETOOLONG; + + if (maxlen > SMB_MAXPATHLEN + 1) + maxlen = SMB_MAXPATHLEN + 1; + + if (entry == NULL) + goto test_name_and_out; + + /* + * If IS_ROOT, we have to do no walking at all. + */ + if (IS_ROOT(entry) && !name) { + *path++ = '\\'; + if (unicode) *path++ = '\0'; + *path++ = '\0'; + if (unicode) *path++ = '\0'; + return path-buf; + } + + /* + * Build the path string walking the tree backward from end to ROOT + * and store it in reversed order [see reverse_string()] + */ + dget(entry); + spin_lock(&entry->d_lock); + while (!IS_ROOT(entry)) { + struct dentry *parent; + + if (maxlen < (3<<unicode)) { + spin_unlock(&entry->d_lock); + dput(entry); + return -ENAMETOOLONG; + } + + len = server->ops->convert(path, maxlen-2, + entry->d_name.name, entry->d_name.len, + server->local_nls, server->remote_nls); + if (len < 0) { + spin_unlock(&entry->d_lock); + dput(entry); + return len; + } + reverse_string(path, len); + path += len; + if (unicode) { + /* Note: reverse order */ + *path++ = '\0'; + maxlen--; + } + *path++ = '\\'; + maxlen -= len+1; + + parent = entry->d_parent; + dget(parent); + spin_unlock(&entry->d_lock); + dput(entry); + entry = parent; + spin_lock(&entry->d_lock); + } + spin_unlock(&entry->d_lock); + dput(entry); + reverse_string(buf, path-buf); + + /* maxlen has space for at least one char */ +test_name_and_out: + if (name) { + if (maxlen < (3<<unicode)) + return -ENAMETOOLONG; + *path++ = '\\'; + if (unicode) { + *path++ = '\0'; + maxlen--; + } + len = server->ops->convert(path, maxlen-2, + name->name, name->len, + server->local_nls, server->remote_nls); + if (len < 0) + return len; + path += len; + maxlen -= len+1; + } + /* maxlen has space for at least one char */ + *path++ = '\0'; + if (unicode) *path++ = '\0'; + return path-buf; +} + +static int smb_encode_path(struct smb_sb_info *server, char *buf, int maxlen, + struct dentry *dir, struct qstr *name) +{ + int result; + + result = smb_build_path(server, buf, maxlen, dir, name); + if (result < 0) + goto out; + if (server->opt.protocol <= SMB_PROTOCOL_COREPLUS) + str_upper(buf, result); +out: + return result; +} + +/* encode_path for non-trans2 request SMBs */ +static int smb_simple_encode_path(struct smb_request *req, char **p, + struct dentry * entry, struct qstr * name) +{ + struct smb_sb_info *server = req->rq_server; + char *s = *p; + int res; + int maxlen = ((char *)req->rq_buffer + req->rq_bufsize) - s; + int unicode = (server->mnt->flags & SMB_MOUNT_UNICODE); + + if (!maxlen) + return -ENAMETOOLONG; + *s++ = 4; /* ASCII data format */ + + /* + * SMB Unicode strings must be 16bit aligned relative the start of the + * packet. If they are not they must be padded with 0. + */ + if (unicode) { + int align = s - (char *)req->rq_buffer; + if (!(align & 1)) { + *s++ = '\0'; + maxlen--; + } + } + + res = smb_encode_path(server, s, maxlen-1, entry, name); + if (res < 0) + return res; + *p = s + res; + return 0; +} + +/* The following 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}; + /* JanFebMarApr May Jun Jul Aug Sep Oct Nov Dec */ + + +static time_t +utc2local(struct smb_sb_info *server, time_t time) +{ + return time - server->opt.serverzone*60; +} + +static time_t +local2utc(struct smb_sb_info *server, time_t time) +{ + return time + server->opt.serverzone*60; +} + +/* Convert a MS-DOS time/date pair to a UNIX date (seconds since 1 1 70). */ + +static time_t +date_dos2unix(struct smb_sb_info *server, __u16 date, __u16 time) +{ + int month, year; + time_t 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(server, secs); +} + + +/* Convert linear UNIX date to a MS-DOS time/date pair. */ + +static void +date_unix2dos(struct smb_sb_info *server, + int unix_date, __u16 *date, __u16 *time) +{ + int day, year, nl_day, month; + + unix_date = utc2local(server, unix_date); + if (unix_date < 315532800) + unix_date = 315532800; + + *time = (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 = nl_day - day_n[month - 1] + 1 + (month << 5) + (year << 9); +} + +/* The following are taken from fs/ntfs/util.c */ + +#define NTFS_TIME_OFFSET ((u64)(369*365 + 89) * 24 * 3600 * 10000000) + +/* + * Convert the NT UTC (based 1601-01-01, in hundred nanosecond units) + * into Unix UTC (based 1970-01-01, in seconds). + */ +static struct timespec +smb_ntutc2unixutc(u64 ntutc) +{ + struct timespec ts; + /* FIXME: what about the timezone difference? */ + /* Subtract the NTFS time offset, then convert to 1s intervals. */ + u64 t = ntutc - NTFS_TIME_OFFSET; + ts.tv_nsec = do_div(t, 10000000) * 100; + ts.tv_sec = t; + return ts; +} + +/* Convert the Unix UTC into NT time */ +static u64 +smb_unixutc2ntutc(struct timespec ts) +{ + /* Note: timezone conversion is probably wrong. */ + /* return ((u64)utc2local(server, t)) * 10000000 + NTFS_TIME_OFFSET; */ + return ((u64)ts.tv_sec) * 10000000 + ts.tv_nsec/100 + NTFS_TIME_OFFSET; +} + +#define MAX_FILE_MODE 6 +static mode_t file_mode[] = { + S_IFREG, S_IFDIR, S_IFLNK, S_IFCHR, S_IFBLK, S_IFIFO, S_IFSOCK +}; + +static int smb_filetype_to_mode(u32 filetype) +{ + if (filetype > MAX_FILE_MODE) { + PARANOIA("Filetype out of range: %d\n", filetype); + return S_IFREG; + } + return file_mode[filetype]; +} + +static u32 smb_filetype_from_mode(int mode) +{ + if (S_ISREG(mode)) + return UNIX_TYPE_FILE; + if (S_ISDIR(mode)) + return UNIX_TYPE_DIR; + if (S_ISLNK(mode)) + return UNIX_TYPE_SYMLINK; + if (S_ISCHR(mode)) + return UNIX_TYPE_CHARDEV; + if (S_ISBLK(mode)) + return UNIX_TYPE_BLKDEV; + if (S_ISFIFO(mode)) + return UNIX_TYPE_FIFO; + if (S_ISSOCK(mode)) + return UNIX_TYPE_SOCKET; + return UNIX_TYPE_UNKNOWN; +} + + +/*****************************************************************************/ +/* */ +/* Support section. */ +/* */ +/*****************************************************************************/ + +__u32 +smb_len(__u8 * p) +{ + return ((*(p+1) & 0x1) << 16L) | (*(p+2) << 8L) | *(p+3); +} + +static __u16 +smb_bcc(__u8 * packet) +{ + int pos = SMB_HEADER_LEN + SMB_WCT(packet) * sizeof(__u16); + return WVAL(packet, pos); +} + +/* smb_valid_packet: We check if packet fulfills the basic + requirements of a smb packet */ + +static int +smb_valid_packet(__u8 * packet) +{ + return (packet[4] == 0xff + && packet[5] == 'S' + && packet[6] == 'M' + && packet[7] == 'B' + && (smb_len(packet) + 4 == SMB_HEADER_LEN + + SMB_WCT(packet) * 2 + smb_bcc(packet))); +} + +/* smb_verify: We check if we got the answer we expected, and if we + got enough data. If bcc == -1, we don't care. */ + +static int +smb_verify(__u8 * packet, int command, int wct, int bcc) +{ + if (SMB_CMD(packet) != command) + goto bad_command; + if (SMB_WCT(packet) < wct) + goto bad_wct; + if (bcc != -1 && smb_bcc(packet) < bcc) + goto bad_bcc; + return 0; + +bad_command: + printk(KERN_ERR "smb_verify: command=%x, SMB_CMD=%x??\n", + command, SMB_CMD(packet)); + goto fail; +bad_wct: + printk(KERN_ERR "smb_verify: command=%x, wct=%d, SMB_WCT=%d??\n", + command, wct, SMB_WCT(packet)); + goto fail; +bad_bcc: + printk(KERN_ERR "smb_verify: command=%x, bcc=%d, SMB_BCC=%d??\n", + command, bcc, smb_bcc(packet)); +fail: + return -EIO; +} + +/* + * Returns the maximum read or write size for the "payload". Making all of the + * packet fit within the negotiated max_xmit size. + * + * N.B. Since this value is usually computed before locking the server, + * the server's packet size must never be decreased! + */ +static inline int +smb_get_xmitsize(struct smb_sb_info *server, int overhead) +{ + return server->opt.max_xmit - overhead; +} + +/* + * Calculate the maximum read size + */ +int +smb_get_rsize(struct smb_sb_info *server) +{ + /* readX has 12 parameters, read has 5 */ + int overhead = SMB_HEADER_LEN + 12 * sizeof(__u16) + 2 + 1 + 2; + int size = smb_get_xmitsize(server, overhead); + + VERBOSE("xmit=%d, size=%d\n", server->opt.max_xmit, size); + + return size; +} + +/* + * Calculate the maximum write size + */ +int +smb_get_wsize(struct smb_sb_info *server) +{ + /* writeX has 14 parameters, write has 5 */ + int overhead = SMB_HEADER_LEN + 14 * sizeof(__u16) + 2 + 1 + 2; + int size = smb_get_xmitsize(server, overhead); + + VERBOSE("xmit=%d, size=%d\n", server->opt.max_xmit, size); + + return size; +} + +/* + * Convert SMB error codes to -E... errno values. + */ +int +smb_errno(struct smb_request *req) +{ + int errcls = req->rq_rcls; + int error = req->rq_err; + char *class = "Unknown"; + + VERBOSE("errcls %d code %d from command 0x%x\n", + errcls, error, SMB_CMD(req->rq_header)); + + if (errcls == ERRDOS) { + switch (error) { + case ERRbadfunc: + return -EINVAL; + case ERRbadfile: + case ERRbadpath: + return -ENOENT; + case ERRnofids: + return -EMFILE; + case ERRnoaccess: + return -EACCES; + case ERRbadfid: + return -EBADF; + case ERRbadmcb: + return -EREMOTEIO; + case ERRnomem: + return -ENOMEM; + case ERRbadmem: + return -EFAULT; + case ERRbadenv: + case ERRbadformat: + return -EREMOTEIO; + case ERRbadaccess: + return -EACCES; + case ERRbaddata: + return -E2BIG; + case ERRbaddrive: + return -ENXIO; + case ERRremcd: + return -EREMOTEIO; + case ERRdiffdevice: + return -EXDEV; + case ERRnofiles: + return -ENOENT; + case ERRbadshare: + return -ETXTBSY; + case ERRlock: + return -EDEADLK; + case ERRfilexists: + return -EEXIST; + case ERROR_INVALID_PARAMETER: + return -EINVAL; + case ERROR_DISK_FULL: + return -ENOSPC; + case ERROR_INVALID_NAME: + return -ENOENT; + case ERROR_DIR_NOT_EMPTY: + return -ENOTEMPTY; + case ERROR_NOT_LOCKED: + return -ENOLCK; + case ERROR_ALREADY_EXISTS: + return -EEXIST; + default: + class = "ERRDOS"; + goto err_unknown; + } + } else if (errcls == ERRSRV) { + switch (error) { + /* N.B. This is wrong ... EIO ? */ + case ERRerror: + return -ENFILE; + case ERRbadpw: + return -EINVAL; + case ERRbadtype: + case ERRtimeout: + return -EIO; + case ERRaccess: + return -EACCES; + /* + * This is a fatal error, as it means the "tree ID" + * for this connection is no longer valid. We map + * to a special error code and get a new connection. + */ + case ERRinvnid: + return -EBADSLT; + default: + class = "ERRSRV"; + goto err_unknown; + } + } else if (errcls == ERRHRD) { + switch (error) { + case ERRnowrite: + return -EROFS; + case ERRbadunit: + return -ENODEV; + case ERRnotready: + return -EUCLEAN; + case ERRbadcmd: + case ERRdata: + return -EIO; + case ERRbadreq: + return -ERANGE; + case ERRbadshare: + return -ETXTBSY; + case ERRlock: + return -EDEADLK; + case ERRdiskfull: + return -ENOSPC; + default: + class = "ERRHRD"; + goto err_unknown; + } + } else if (errcls == ERRCMD) { + class = "ERRCMD"; + } else if (errcls == SUCCESS) { + return 0; /* This is the only valid 0 return */ + } + +err_unknown: + printk(KERN_ERR "smb_errno: class %s, code %d from command 0x%x\n", + class, error, SMB_CMD(req->rq_header)); + return -EIO; +} + +/* smb_request_ok: We expect the server to be locked. Then we do the + request and check the answer completely. When smb_request_ok + returns 0, you can be quite sure that everything went well. When + the answer is <=0, the returned number is a valid unix errno. */ + +static int +smb_request_ok(struct smb_request *req, int command, int wct, int bcc) +{ + int result; + + req->rq_resp_wct = wct; + req->rq_resp_bcc = bcc; + + result = smb_add_request(req); + if (result != 0) { + DEBUG1("smb_request failed\n"); + goto out; + } + + if (smb_valid_packet(req->rq_header) != 0) { + PARANOIA("invalid packet!\n"); + goto out; + } + + result = smb_verify(req->rq_header, command, wct, bcc); + +out: + return result; +} + +/* + * This implements the NEWCONN ioctl. It installs the server pid, + * sets server->state to CONN_VALID, and wakes up the waiting process. + */ +int +smb_newconn(struct smb_sb_info *server, struct smb_conn_opt *opt) +{ + struct file *filp; + struct sock *sk; + int error; + + VERBOSE("fd=%d, pid=%d\n", opt->fd, current->pid); + + smb_lock_server(server); + + /* + * Make sure we don't already have a valid connection ... + */ + error = -EINVAL; + if (server->state == CONN_VALID) + goto out; + + error = -EACCES; + if (current->uid != server->mnt->mounted_uid && + !capable(CAP_SYS_ADMIN)) + goto out; + + error = -EBADF; + filp = fget(opt->fd); + if (!filp) + goto out; + if (!smb_valid_socket(filp->f_dentry->d_inode)) + goto out_putf; + + server->sock_file = filp; + server->conn_pid = current->pid; + server->opt = *opt; + server->generation += 1; + server->state = CONN_VALID; + error = 0; + + if (server->conn_error) { + /* + * conn_error is the returncode we originally decided to + * drop the old connection on. This message should be positive + * and not make people ask questions on why smbfs is printing + * error messages ... + */ + printk(KERN_INFO "SMB connection re-established (%d)\n", + server->conn_error); + server->conn_error = 0; + } + + /* + * Store the server in sock user_data (Only used by sunrpc) + */ + sk = SOCKET_I(filp->f_dentry->d_inode)->sk; + sk->sk_user_data = server; + + /* chain into the data_ready callback */ + server->data_ready = xchg(&sk->sk_data_ready, smb_data_ready); + + /* check if we have an old smbmount that uses seconds for the + serverzone */ + if (server->opt.serverzone > 12*60 || server->opt.serverzone < -12*60) + server->opt.serverzone /= 60; + + /* now that we have an established connection we can detect the server + type and enable bug workarounds */ + if (server->opt.protocol < SMB_PROTOCOL_LANMAN2) + install_ops(server->ops, &smb_ops_core); + else if (server->opt.protocol == SMB_PROTOCOL_LANMAN2) + install_ops(server->ops, &smb_ops_os2); + else if (server->opt.protocol == SMB_PROTOCOL_NT1 && + (server->opt.max_xmit < 0x1000) && + !(server->opt.capabilities & SMB_CAP_NT_SMBS)) { + /* FIXME: can we kill the WIN95 flag now? */ + server->mnt->flags |= SMB_MOUNT_WIN95; + VERBOSE("detected WIN95 server\n"); + install_ops(server->ops, &smb_ops_win95); + } else { + /* + * Samba has max_xmit 65535 + * NT4spX has max_xmit 4536 (or something like that) + * win2k has ... + */ + VERBOSE("detected NT1 (Samba, NT4/5) server\n"); + install_ops(server->ops, &smb_ops_winNT); + } + + /* FIXME: the win9x code wants to modify these ... (seek/trunc bug) */ + if (server->mnt->flags & SMB_MOUNT_OLDATTR) { + server->ops->getattr = smb_proc_getattr_core; + } else if (server->mnt->flags & SMB_MOUNT_DIRATTR) { + server->ops->getattr = smb_proc_getattr_ff; + } + + /* Decode server capabilities */ + if (server->opt.capabilities & SMB_CAP_LARGE_FILES) { + /* Should be ok to set this now, as no one can access the + mount until the connection has been established. */ + SB_of(server)->s_maxbytes = ~0ULL >> 1; + VERBOSE("LFS enabled\n"); + } + if (server->opt.capabilities & SMB_CAP_UNICODE) { + server->mnt->flags |= SMB_MOUNT_UNICODE; + VERBOSE("Unicode enabled\n"); + } else { + server->mnt->flags &= ~SMB_MOUNT_UNICODE; + } +#if 0 + /* flags we may test for other patches ... */ + if (server->opt.capabilities & SMB_CAP_LARGE_READX) { + VERBOSE("Large reads enabled\n"); + } + if (server->opt.capabilities & SMB_CAP_LARGE_WRITEX) { + VERBOSE("Large writes enabled\n"); + } +#endif + if (server->opt.capabilities & SMB_CAP_UNIX) { + struct inode *inode; + VERBOSE("Using UNIX CIFS extensions\n"); + install_ops(server->ops, &smb_ops_unix); + inode = SB_of(server)->s_root->d_inode; + if (inode) + inode->i_op = &smb_dir_inode_operations_unix; + } + + VERBOSE("protocol=%d, max_xmit=%d, pid=%d capabilities=0x%x\n", + server->opt.protocol, server->opt.max_xmit, server->conn_pid, + server->opt.capabilities); + + /* FIXME: this really should be done by smbmount. */ + if (server->opt.max_xmit > SMB_MAX_PACKET_SIZE) { + server->opt.max_xmit = SMB_MAX_PACKET_SIZE; + } + + smb_unlock_server(server); + smbiod_wake_up(); + if (server->opt.capabilities & SMB_CAP_UNIX) + smb_proc_query_cifsunix(server); + + server->conn_complete++; + wake_up_interruptible_all(&server->conn_wq); + return error; + +out: + smb_unlock_server(server); + smbiod_wake_up(); + return error; + +out_putf: + fput(filp); + goto out; +} + +/* smb_setup_header: We completely set up the packet. You only have to + insert the command-specific fields */ + +__u8 * +smb_setup_header(struct smb_request *req, __u8 command, __u16 wct, __u16 bcc) +{ + __u32 xmit_len = SMB_HEADER_LEN + wct * sizeof(__u16) + bcc + 2; + __u8 *p = req->rq_header; + struct smb_sb_info *server = req->rq_server; + + p = smb_encode_smb_length(p, xmit_len - 4); + + *p++ = 0xff; + *p++ = 'S'; + *p++ = 'M'; + *p++ = 'B'; + *p++ = command; + + memset(p, '\0', 19); + p += 19; + p += 8; + + if (server->opt.protocol > SMB_PROTOCOL_CORE) { + int flags = SMB_FLAGS_CASELESS_PATHNAMES; + int flags2 = SMB_FLAGS2_LONG_PATH_COMPONENTS | + SMB_FLAGS2_EXTENDED_ATTRIBUTES; /* EA? not really ... */ + + *(req->rq_header + smb_flg) = flags; + if (server->mnt->flags & SMB_MOUNT_UNICODE) + flags2 |= SMB_FLAGS2_UNICODE_STRINGS; + WSET(req->rq_header, smb_flg2, flags2); + } + *p++ = wct; /* wct */ + p += 2 * wct; + WSET(p, 0, bcc); + + /* Include the header in the data to send */ + req->rq_iovlen = 1; + req->rq_iov[0].iov_base = req->rq_header; + req->rq_iov[0].iov_len = xmit_len - bcc; + + return req->rq_buffer; +} + +static void +smb_setup_bcc(struct smb_request *req, __u8 *p) +{ + u16 bcc = p - req->rq_buffer; + u8 *pbcc = req->rq_header + SMB_HEADER_LEN + 2*SMB_WCT(req->rq_header); + + WSET(pbcc, 0, bcc); + + smb_encode_smb_length(req->rq_header, SMB_HEADER_LEN + + 2*SMB_WCT(req->rq_header) - 2 + bcc); + + /* Include the "bytes" in the data to send */ + req->rq_iovlen = 2; + req->rq_iov[1].iov_base = req->rq_buffer; + req->rq_iov[1].iov_len = bcc; +} + +static int +smb_proc_seek(struct smb_sb_info *server, __u16 fileid, + __u16 mode, off_t offset) +{ + int result; + struct smb_request *req; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, 0))) + goto out; + + smb_setup_header(req, SMBlseek, 4, 0); + WSET(req->rq_header, smb_vwv0, fileid); + WSET(req->rq_header, smb_vwv1, mode); + DSET(req->rq_header, smb_vwv2, offset); + req->rq_flags |= SMB_REQ_NORETRY; + + result = smb_request_ok(req, SMBlseek, 2, 0); + if (result < 0) { + result = 0; + goto out_free; + } + + result = DVAL(req->rq_header, smb_vwv0); +out_free: + smb_rput(req); +out: + return result; +} + +static int +smb_proc_open(struct smb_sb_info *server, struct dentry *dentry, int wish) +{ + struct inode *ino = dentry->d_inode; + struct smb_inode_info *ei = SMB_I(ino); + int mode, read_write = 0x42, read_only = 0x40; + int res; + char *p; + struct smb_request *req; + + /* + * Attempt to open r/w, unless there are no write privileges. + */ + mode = read_write; + if (!(ino->i_mode & (S_IWUSR | S_IWGRP | S_IWOTH))) + mode = read_only; +#if 0 + /* FIXME: why is this code not in? below we fix it so that a caller + wanting RO doesn't get RW. smb_revalidate_inode does some + optimization based on access mode. tail -f needs it to be correct. + + We must open rw since we don't do the open if called a second time + with different 'wish'. Is that not supported by smb servers? */ + if (!(wish & (O_WRONLY | O_RDWR))) + mode = read_only; +#endif + + res = -ENOMEM; + if (! (req = smb_alloc_request(server, PAGE_SIZE))) + goto out; + + retry: + p = smb_setup_header(req, SMBopen, 2, 0); + WSET(req->rq_header, smb_vwv0, mode); + WSET(req->rq_header, smb_vwv1, aSYSTEM | aHIDDEN | aDIR); + res = smb_simple_encode_path(req, &p, dentry, NULL); + if (res < 0) + goto out_free; + smb_setup_bcc(req, p); + + res = smb_request_ok(req, SMBopen, 7, 0); + if (res != 0) { + if (mode == read_write && + (res == -EACCES || res == -ETXTBSY || res == -EROFS)) + { + VERBOSE("%s/%s R/W failed, error=%d, retrying R/O\n", + DENTRY_PATH(dentry), res); + mode = read_only; + req->rq_flags = 0; + goto retry; + } + goto out_free; + } + /* We should now have data in vwv[0..6]. */ + + ei->fileid = WVAL(req->rq_header, smb_vwv0); + ei->attr = WVAL(req->rq_header, smb_vwv1); + /* smb_vwv2 has mtime */ + /* smb_vwv4 has size */ + ei->access = (WVAL(req->rq_header, smb_vwv6) & SMB_ACCMASK); + ei->open = server->generation; + +out_free: + smb_rput(req); +out: + return res; +} + +/* + * Make sure the file is open, and check that the access + * is compatible with the desired access. + */ +int +smb_open(struct dentry *dentry, int wish) +{ + struct inode *inode = dentry->d_inode; + int result; + __u16 access; + + result = -ENOENT; + if (!inode) { + printk(KERN_ERR "smb_open: no inode for dentry %s/%s\n", + DENTRY_PATH(dentry)); + goto out; + } + + if (!smb_is_open(inode)) { + struct smb_sb_info *server = server_from_inode(inode); + result = 0; + if (!smb_is_open(inode)) + result = smb_proc_open(server, dentry, wish); + if (result) + goto out; + /* + * A successful open means the path is still valid ... + */ + smb_renew_times(dentry); + } + + /* + * Check whether the access is compatible with the desired mode. + */ + result = 0; + access = SMB_I(inode)->access; + if (access != wish && access != SMB_O_RDWR) { + PARANOIA("%s/%s access denied, access=%x, wish=%x\n", + DENTRY_PATH(dentry), access, wish); + result = -EACCES; + } +out: + return result; +} + +static int +smb_proc_close(struct smb_sb_info *server, __u16 fileid, __u32 mtime) +{ + struct smb_request *req; + int result = -ENOMEM; + + if (! (req = smb_alloc_request(server, 0))) + goto out; + + smb_setup_header(req, SMBclose, 3, 0); + WSET(req->rq_header, smb_vwv0, fileid); + DSET(req->rq_header, smb_vwv1, utc2local(server, mtime)); + req->rq_flags |= SMB_REQ_NORETRY; + result = smb_request_ok(req, SMBclose, 0, 0); + + smb_rput(req); +out: + return result; +} + +/* + * Win NT 4.0 has an apparent bug in that it fails to update the + * modify time when writing to a file. As a workaround, we update + * both modify and access time locally, and post the times to the + * server when closing the file. + */ +static int +smb_proc_close_inode(struct smb_sb_info *server, struct inode * ino) +{ + struct smb_inode_info *ei = SMB_I(ino); + int result = 0; + if (smb_is_open(ino)) + { + /* + * We clear the open flag in advance, in case another + * process observes the value while we block below. + */ + ei->open = 0; + + /* + * Kludge alert: SMB timestamps are accurate only to + * two seconds ... round the times to avoid needless + * cache invalidations! + */ + if (ino->i_mtime.tv_sec & 1) { + ino->i_mtime.tv_sec--; + ino->i_mtime.tv_nsec = 0; + } + if (ino->i_atime.tv_sec & 1) { + ino->i_atime.tv_sec--; + ino->i_atime.tv_nsec = 0; + } + /* + * If the file is open with write permissions, + * update the time stamps to sync mtime and atime. + */ + if ((server->opt.capabilities & SMB_CAP_UNIX) == 0 && + (server->opt.protocol >= SMB_PROTOCOL_LANMAN2) && + !(ei->access == SMB_O_RDONLY)) + { + struct smb_fattr fattr; + smb_get_inode_attr(ino, &fattr); + smb_proc_setattr_ext(server, ino, &fattr); + } + + result = smb_proc_close(server, ei->fileid, ino->i_mtime.tv_sec); + /* + * Force a revalidation after closing ... some servers + * don't post the size until the file has been closed. + */ + if (server->opt.protocol < SMB_PROTOCOL_NT1) + ei->oldmtime = 0; + ei->closed = jiffies; + } + return result; +} + +int +smb_close(struct inode *ino) +{ + int result = 0; + + if (smb_is_open(ino)) { + struct smb_sb_info *server = server_from_inode(ino); + result = smb_proc_close_inode(server, ino); + } + return result; +} + +/* + * This is used to close a file following a failed instantiate. + * Since we don't have an inode, we can't use any of the above. + */ +int +smb_close_fileid(struct dentry *dentry, __u16 fileid) +{ + struct smb_sb_info *server = server_from_dentry(dentry); + int result; + + result = smb_proc_close(server, fileid, get_seconds()); + return result; +} + +/* In smb_proc_read and smb_proc_write we do not retry, because the + file-id would not be valid after a reconnection. */ + +static void +smb_proc_read_data(struct smb_request *req) +{ + req->rq_iov[0].iov_base = req->rq_buffer; + req->rq_iov[0].iov_len = 3; + + req->rq_iov[1].iov_base = req->rq_page; + req->rq_iov[1].iov_len = req->rq_rsize; + req->rq_iovlen = 2; + + req->rq_rlen = smb_len(req->rq_header) + 4 - req->rq_bytes_recvd; +} + +static int +smb_proc_read(struct inode *inode, loff_t offset, int count, char *data) +{ + struct smb_sb_info *server = server_from_inode(inode); + __u16 returned_count, data_len; + unsigned char *buf; + int result; + struct smb_request *req; + u8 rbuf[4]; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, 0))) + goto out; + + smb_setup_header(req, SMBread, 5, 0); + buf = req->rq_header; + WSET(buf, smb_vwv0, SMB_I(inode)->fileid); + WSET(buf, smb_vwv1, count); + DSET(buf, smb_vwv2, offset); + WSET(buf, smb_vwv4, 0); + + req->rq_page = data; + req->rq_rsize = count; + req->rq_callback = smb_proc_read_data; + req->rq_buffer = rbuf; + req->rq_flags |= SMB_REQ_NORETRY | SMB_REQ_STATIC; + + result = smb_request_ok(req, SMBread, 5, -1); + if (result < 0) + goto out_free; + returned_count = WVAL(req->rq_header, smb_vwv0); + + data_len = WVAL(rbuf, 1); + + if (returned_count != data_len) { + printk(KERN_NOTICE "smb_proc_read: returned != data_len\n"); + printk(KERN_NOTICE "smb_proc_read: ret_c=%d, data_len=%d\n", + returned_count, data_len); + } + result = data_len; + +out_free: + smb_rput(req); +out: + VERBOSE("ino=%ld, fileid=%d, count=%d, result=%d\n", + inode->i_ino, SMB_I(inode)->fileid, count, result); + return result; +} + +static int +smb_proc_write(struct inode *inode, loff_t offset, int count, const char *data) +{ + struct smb_sb_info *server = server_from_inode(inode); + int result; + u16 fileid = SMB_I(inode)->fileid; + u8 buf[4]; + struct smb_request *req; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, 0))) + goto out; + + VERBOSE("ino=%ld, fileid=%d, count=%d@%Ld\n", + inode->i_ino, fileid, count, offset); + + smb_setup_header(req, SMBwrite, 5, count + 3); + WSET(req->rq_header, smb_vwv0, fileid); + WSET(req->rq_header, smb_vwv1, count); + DSET(req->rq_header, smb_vwv2, offset); + WSET(req->rq_header, smb_vwv4, 0); + + buf[0] = 1; + WSET(buf, 1, count); /* yes, again ... */ + req->rq_iov[1].iov_base = buf; + req->rq_iov[1].iov_len = 3; + req->rq_iov[2].iov_base = (char *) data; + req->rq_iov[2].iov_len = count; + req->rq_iovlen = 3; + req->rq_flags |= SMB_REQ_NORETRY; + + result = smb_request_ok(req, SMBwrite, 1, 0); + if (result >= 0) + result = WVAL(req->rq_header, smb_vwv0); + + smb_rput(req); +out: + return result; +} + +/* + * In smb_proc_readX and smb_proc_writeX we do not retry, because the + * file-id would not be valid after a reconnection. + */ + +#define SMB_READX_MAX_PAD 64 +static void +smb_proc_readX_data(struct smb_request *req) +{ + /* header length, excluding the netbios length (-4) */ + int hdrlen = SMB_HEADER_LEN + req->rq_resp_wct*2 - 2; + int data_off = WVAL(req->rq_header, smb_vwv6); + + /* + * Some genius made the padding to the data bytes arbitrary. + * So we must first calculate the amount of padding used by the server. + */ + data_off -= hdrlen; + if (data_off > SMB_READX_MAX_PAD || data_off < 0) { + PARANOIA("offset is larger than SMB_READX_MAX_PAD or negative!\n"); + PARANOIA("%d > %d || %d < 0\n", data_off, SMB_READX_MAX_PAD, data_off); + req->rq_rlen = req->rq_bufsize + 1; + return; + } + req->rq_iov[0].iov_base = req->rq_buffer; + req->rq_iov[0].iov_len = data_off; + + req->rq_iov[1].iov_base = req->rq_page; + req->rq_iov[1].iov_len = req->rq_rsize; + req->rq_iovlen = 2; + + req->rq_rlen = smb_len(req->rq_header) + 4 - req->rq_bytes_recvd; +} + +static int +smb_proc_readX(struct inode *inode, loff_t offset, int count, char *data) +{ + struct smb_sb_info *server = server_from_inode(inode); + unsigned char *buf; + int result; + struct smb_request *req; + static char pad[SMB_READX_MAX_PAD]; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, 0))) + goto out; + + smb_setup_header(req, SMBreadX, 12, 0); + buf = req->rq_header; + WSET(buf, smb_vwv0, 0x00ff); + WSET(buf, smb_vwv1, 0); + WSET(buf, smb_vwv2, SMB_I(inode)->fileid); + DSET(buf, smb_vwv3, (u32)offset); /* low 32 bits */ + WSET(buf, smb_vwv5, count); + WSET(buf, smb_vwv6, 0); + DSET(buf, smb_vwv7, 0); + WSET(buf, smb_vwv9, 0); + DSET(buf, smb_vwv10, (u32)(offset >> 32)); /* high 32 bits */ + WSET(buf, smb_vwv11, 0); + + req->rq_page = data; + req->rq_rsize = count; + req->rq_callback = smb_proc_readX_data; + req->rq_buffer = pad; + req->rq_bufsize = SMB_READX_MAX_PAD; + req->rq_flags |= SMB_REQ_STATIC | SMB_REQ_NORETRY; + + result = smb_request_ok(req, SMBreadX, 12, -1); + if (result < 0) + goto out_free; + result = WVAL(req->rq_header, smb_vwv5); + +out_free: + smb_rput(req); +out: + VERBOSE("ino=%ld, fileid=%d, count=%d, result=%d\n", + inode->i_ino, SMB_I(inode)->fileid, count, result); + return result; +} + +static int +smb_proc_writeX(struct inode *inode, loff_t offset, int count, const char *data) +{ + struct smb_sb_info *server = server_from_inode(inode); + int result; + u8 *p; + static u8 pad[4]; + struct smb_request *req; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, 0))) + goto out; + + VERBOSE("ino=%ld, fileid=%d, count=%d@%Ld\n", + inode->i_ino, SMB_I(inode)->fileid, count, offset); + + p = smb_setup_header(req, SMBwriteX, 14, count + 1); + WSET(req->rq_header, smb_vwv0, 0x00ff); + WSET(req->rq_header, smb_vwv1, 0); + WSET(req->rq_header, smb_vwv2, SMB_I(inode)->fileid); + DSET(req->rq_header, smb_vwv3, (u32)offset); /* low 32 bits */ + DSET(req->rq_header, smb_vwv5, 0); + WSET(req->rq_header, smb_vwv7, 0); /* write mode */ + WSET(req->rq_header, smb_vwv8, 0); + WSET(req->rq_header, smb_vwv9, 0); + WSET(req->rq_header, smb_vwv10, count); /* data length */ + WSET(req->rq_header, smb_vwv11, smb_vwv12 + 2 + 1); + DSET(req->rq_header, smb_vwv12, (u32)(offset >> 32)); + + req->rq_iov[1].iov_base = pad; + req->rq_iov[1].iov_len = 1; + req->rq_iov[2].iov_base = (char *) data; + req->rq_iov[2].iov_len = count; + req->rq_iovlen = 3; + req->rq_flags |= SMB_REQ_NORETRY; + + result = smb_request_ok(req, SMBwriteX, 6, 0); + if (result >= 0) + result = WVAL(req->rq_header, smb_vwv2); + + smb_rput(req); +out: + return result; +} + +int +smb_proc_create(struct dentry *dentry, __u16 attr, time_t ctime, __u16 *fileid) +{ + struct smb_sb_info *server = server_from_dentry(dentry); + char *p; + int result; + struct smb_request *req; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, PAGE_SIZE))) + goto out; + + p = smb_setup_header(req, SMBcreate, 3, 0); + WSET(req->rq_header, smb_vwv0, attr); + DSET(req->rq_header, smb_vwv1, utc2local(server, ctime)); + result = smb_simple_encode_path(req, &p, dentry, NULL); + if (result < 0) + goto out_free; + smb_setup_bcc(req, p); + + result = smb_request_ok(req, SMBcreate, 1, 0); + if (result < 0) + goto out_free; + + *fileid = WVAL(req->rq_header, smb_vwv0); + result = 0; + +out_free: + smb_rput(req); +out: + return result; +} + +int +smb_proc_mv(struct dentry *old_dentry, struct dentry *new_dentry) +{ + struct smb_sb_info *server = server_from_dentry(old_dentry); + char *p; + int result; + struct smb_request *req; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, PAGE_SIZE))) + goto out; + + p = smb_setup_header(req, SMBmv, 1, 0); + WSET(req->rq_header, smb_vwv0, aSYSTEM | aHIDDEN | aDIR); + result = smb_simple_encode_path(req, &p, old_dentry, NULL); + if (result < 0) + goto out_free; + result = smb_simple_encode_path(req, &p, new_dentry, NULL); + if (result < 0) + goto out_free; + smb_setup_bcc(req, p); + + if ((result = smb_request_ok(req, SMBmv, 0, 0)) < 0) + goto out_free; + result = 0; + +out_free: + smb_rput(req); +out: + return result; +} + +/* + * Code common to mkdir and rmdir. + */ +static int +smb_proc_generic_command(struct dentry *dentry, __u8 command) +{ + struct smb_sb_info *server = server_from_dentry(dentry); + char *p; + int result; + struct smb_request *req; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, PAGE_SIZE))) + goto out; + + p = smb_setup_header(req, command, 0, 0); + result = smb_simple_encode_path(req, &p, dentry, NULL); + if (result < 0) + goto out_free; + smb_setup_bcc(req, p); + + result = smb_request_ok(req, command, 0, 0); + if (result < 0) + goto out_free; + result = 0; + +out_free: + smb_rput(req); +out: + return result; +} + +int +smb_proc_mkdir(struct dentry *dentry) +{ + return smb_proc_generic_command(dentry, SMBmkdir); +} + +int +smb_proc_rmdir(struct dentry *dentry) +{ + return smb_proc_generic_command(dentry, SMBrmdir); +} + +#if SMBFS_POSIX_UNLINK +/* + * Removes readonly attribute from a file. Used by unlink to give posix + * semantics. + */ +static int +smb_set_rw(struct dentry *dentry,struct smb_sb_info *server) +{ + int result; + struct smb_fattr fattr; + + /* FIXME: cifsUE should allow removing a readonly file. */ + + /* first get current attribute */ + smb_init_dirent(server, &fattr); + result = server->ops->getattr(server, dentry, &fattr); + smb_finish_dirent(server, &fattr); + if (result < 0) + return result; + + /* if RONLY attribute is set, remove it */ + if (fattr.attr & aRONLY) { /* read only attribute is set */ + fattr.attr &= ~aRONLY; + result = smb_proc_setattr_core(server, dentry, fattr.attr); + } + return result; +} +#endif + +int +smb_proc_unlink(struct dentry *dentry) +{ + struct smb_sb_info *server = server_from_dentry(dentry); + int flag = 0; + char *p; + int result; + struct smb_request *req; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, PAGE_SIZE))) + goto out; + + retry: + p = smb_setup_header(req, SMBunlink, 1, 0); + WSET(req->rq_header, smb_vwv0, aSYSTEM | aHIDDEN); + result = smb_simple_encode_path(req, &p, dentry, NULL); + if (result < 0) + goto out_free; + smb_setup_bcc(req, p); + + if ((result = smb_request_ok(req, SMBunlink, 0, 0)) < 0) { +#if SMBFS_POSIX_UNLINK + if (result == -EACCES && !flag) { + /* Posix semantics is for the read-only state + of a file to be ignored in unlink(). In the + SMB world a unlink() is refused on a + read-only file. To make things easier for + unix users we try to override the files + permission if the unlink fails with the + right error. + This introduces a race condition that could + lead to a file being written by someone who + shouldn't have access, but as far as I can + tell that is unavoidable */ + + /* remove RONLY attribute and try again */ + result = smb_set_rw(dentry,server); + if (result == 0) { + flag = 1; + req->rq_flags = 0; + goto retry; + } + } +#endif + goto out_free; + } + result = 0; + +out_free: + smb_rput(req); +out: + return result; +} + +int +smb_proc_flush(struct smb_sb_info *server, __u16 fileid) +{ + int result; + struct smb_request *req; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, 0))) + goto out; + + smb_setup_header(req, SMBflush, 1, 0); + WSET(req->rq_header, smb_vwv0, fileid); + req->rq_flags |= SMB_REQ_NORETRY; + result = smb_request_ok(req, SMBflush, 0, 0); + + smb_rput(req); +out: + return result; +} + +static int +smb_proc_trunc32(struct inode *inode, loff_t length) +{ + /* + * Writing 0bytes is old-SMB magic for truncating files. + * MAX_NON_LFS should prevent this from being called with a too + * large offset. + */ + return smb_proc_write(inode, length, 0, NULL); +} + +static int +smb_proc_trunc64(struct inode *inode, loff_t length) +{ + struct smb_sb_info *server = server_from_inode(inode); + int result; + char *param; + char *data; + struct smb_request *req; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, 14))) + goto out; + + param = req->rq_buffer; + data = req->rq_buffer + 6; + + /* FIXME: must we also set allocation size? winNT seems to do that */ + WSET(param, 0, SMB_I(inode)->fileid); + WSET(param, 2, SMB_SET_FILE_END_OF_FILE_INFO); + WSET(param, 4, 0); + LSET(data, 0, length); + + req->rq_trans2_command = TRANSACT2_SETFILEINFO; + req->rq_ldata = 8; + req->rq_data = data; + req->rq_lparm = 6; + req->rq_parm = param; + req->rq_flags |= SMB_REQ_NORETRY; + result = smb_add_request(req); + if (result < 0) + goto out_free; + + result = 0; + if (req->rq_rcls != 0) + result = smb_errno(req); + +out_free: + smb_rput(req); +out: + return result; +} + +static int +smb_proc_trunc95(struct inode *inode, loff_t length) +{ + struct smb_sb_info *server = server_from_inode(inode); + int result = smb_proc_trunc32(inode, length); + + /* + * win9x doesn't appear to update the size immediately. + * It will return the old file size after the truncate, + * confusing smbfs. So we force an update. + * + * FIXME: is this still necessary? + */ + smb_proc_flush(server, SMB_I(inode)->fileid); + return result; +} + +static void +smb_init_dirent(struct smb_sb_info *server, struct smb_fattr *fattr) +{ + memset(fattr, 0, sizeof(*fattr)); + + fattr->f_nlink = 1; + fattr->f_uid = server->mnt->uid; + fattr->f_gid = server->mnt->gid; + fattr->f_blksize = SMB_ST_BLKSIZE; + fattr->f_unix = 0; +} + +static void +smb_finish_dirent(struct smb_sb_info *server, struct smb_fattr *fattr) +{ + if (fattr->f_unix) + return; + + fattr->f_mode = server->mnt->file_mode; + if (fattr->attr & aDIR) { + fattr->f_mode = server->mnt->dir_mode; + fattr->f_size = SMB_ST_BLKSIZE; + } + /* Check the read-only flag */ + if (fattr->attr & aRONLY) + fattr->f_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); + + /* How many 512 byte blocks do we need for this file? */ + fattr->f_blocks = 0; + if (fattr->f_size != 0) + fattr->f_blocks = 1 + ((fattr->f_size-1) >> 9); + return; +} + +void +smb_init_root_dirent(struct smb_sb_info *server, struct smb_fattr *fattr, + struct super_block *sb) +{ + smb_init_dirent(server, fattr); + fattr->attr = aDIR; + fattr->f_ino = 2; /* traditional root inode number */ + fattr->f_mtime = current_fs_time(sb); + smb_finish_dirent(server, fattr); +} + +/* + * Decode a dirent for old protocols + * + * qname is filled with the decoded, and possibly translated, name. + * fattr receives decoded attributes + * + * Bugs Noted: + * (1) Pathworks servers may pad the name with extra spaces. + */ +static char * +smb_decode_short_dirent(struct smb_sb_info *server, char *p, + struct qstr *qname, struct smb_fattr *fattr, + unsigned char *name_buf) +{ + int len; + + /* + * SMB doesn't have a concept of inode numbers ... + */ + smb_init_dirent(server, fattr); + fattr->f_ino = 0; /* FIXME: do we need this? */ + + p += SMB_STATUS_SIZE; /* reserved (search_status) */ + fattr->attr = *p; + fattr->f_mtime.tv_sec = date_dos2unix(server, WVAL(p, 3), WVAL(p, 1)); + fattr->f_mtime.tv_nsec = 0; + fattr->f_size = DVAL(p, 5); + fattr->f_ctime = fattr->f_mtime; + fattr->f_atime = fattr->f_mtime; + qname->name = p + 9; + len = strnlen(qname->name, 12); + + /* + * Trim trailing blanks for Pathworks servers + */ + while (len > 2 && qname->name[len-1] == ' ') + len--; + + smb_finish_dirent(server, fattr); + +#if 0 + /* FIXME: These only work for ascii chars, and recent smbmount doesn't + allow the flag to be set anyway. It kills const. Remove? */ + switch (server->opt.case_handling) { + case SMB_CASE_UPPER: + str_upper(entry->name, len); + break; + case SMB_CASE_LOWER: + str_lower(entry->name, len); + break; + default: + break; + } +#endif + + qname->len = 0; + len = server->ops->convert(name_buf, SMB_MAXNAMELEN, + qname->name, len, + server->remote_nls, server->local_nls); + if (len > 0) { + qname->len = len; + qname->name = name_buf; + DEBUG1("len=%d, name=%.*s\n",qname->len,qname->len,qname->name); + } + + return p + 22; +} + +/* + * This routine is used to read in directory entries from the network. + * Note that it is for short directory name seeks, i.e.: protocol < + * SMB_PROTOCOL_LANMAN2 + */ +static int +smb_proc_readdir_short(struct file *filp, void *dirent, filldir_t filldir, + struct smb_cache_control *ctl) +{ + struct dentry *dir = filp->f_dentry; + struct smb_sb_info *server = server_from_dentry(dir); + struct qstr qname; + struct smb_fattr fattr; + char *p; + int result; + int i, first, entries_seen, entries; + int entries_asked = (server->opt.max_xmit - 100) / SMB_DIRINFO_SIZE; + __u16 bcc; + __u16 count; + char status[SMB_STATUS_SIZE]; + static struct qstr mask = { + .name = "*.*", + .len = 3, + }; + unsigned char *last_status; + struct smb_request *req; + unsigned char *name_buf; + + VERBOSE("%s/%s\n", DENTRY_PATH(dir)); + + lock_kernel(); + + result = -ENOMEM; + if (! (name_buf = kmalloc(SMB_MAXNAMELEN, GFP_KERNEL))) + goto out; + + first = 1; + entries = 0; + entries_seen = 2; /* implicit . and .. */ + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, server->opt.max_xmit))) + goto out_name; + + while (1) { + p = smb_setup_header(req, SMBsearch, 2, 0); + WSET(req->rq_header, smb_vwv0, entries_asked); + WSET(req->rq_header, smb_vwv1, aDIR); + if (first == 1) { + result = smb_simple_encode_path(req, &p, dir, &mask); + if (result < 0) + goto out_free; + if (p + 3 > (char *)req->rq_buffer + req->rq_bufsize) { + result = -ENAMETOOLONG; + goto out_free; + } + *p++ = 5; + WSET(p, 0, 0); + p += 2; + first = 0; + } else { + if (p + 5 + SMB_STATUS_SIZE > + (char *)req->rq_buffer + req->rq_bufsize) { + result = -ENAMETOOLONG; + goto out_free; + } + + *p++ = 4; + *p++ = 0; + *p++ = 5; + WSET(p, 0, SMB_STATUS_SIZE); + p += 2; + memcpy(p, status, SMB_STATUS_SIZE); + p += SMB_STATUS_SIZE; + } + + smb_setup_bcc(req, p); + + result = smb_request_ok(req, SMBsearch, 1, -1); + if (result < 0) { + if ((req->rq_rcls == ERRDOS) && + (req->rq_err == ERRnofiles)) + break; + goto out_free; + } + count = WVAL(req->rq_header, smb_vwv0); + if (count <= 0) + break; + + result = -EIO; + bcc = smb_bcc(req->rq_header); + if (bcc != count * SMB_DIRINFO_SIZE + 3) + goto out_free; + p = req->rq_buffer + 3; + + + /* Make sure the response fits in the buffer. Fixed sized + entries means we don't have to check in the decode loop. */ + + last_status = req->rq_buffer + 3 + (count-1) * SMB_DIRINFO_SIZE; + + if (last_status + SMB_DIRINFO_SIZE >= + req->rq_buffer + req->rq_bufsize) { + printk(KERN_ERR "smb_proc_readdir_short: " + "last dir entry outside buffer! " + "%d@%p %d@%p\n", SMB_DIRINFO_SIZE, last_status, + req->rq_bufsize, req->rq_buffer); + goto out_free; + } + + /* Read the last entry into the status field. */ + memcpy(status, last_status, SMB_STATUS_SIZE); + + + /* Now we are ready to parse smb directory entries. */ + + for (i = 0; i < count; i++) { + p = smb_decode_short_dirent(server, p, + &qname, &fattr, name_buf); + if (qname.len == 0) + continue; + + if (entries_seen == 2 && qname.name[0] == '.') { + if (qname.len == 1) + continue; + if (qname.name[1] == '.' && qname.len == 2) + continue; + } + if (!smb_fill_cache(filp, dirent, filldir, ctl, + &qname, &fattr)) + ; /* stop reading? */ + entries_seen++; + } + } + result = entries; + +out_free: + smb_rput(req); +out_name: + kfree(name_buf); +out: + unlock_kernel(); + return result; +} + +static void smb_decode_unix_basic(struct smb_fattr *fattr, struct smb_sb_info *server, char *p) +{ + u64 size, disk_bytes; + + /* FIXME: verify nls support. all is sent as utf8? */ + + fattr->f_unix = 1; + fattr->f_mode = 0; + + /* FIXME: use the uniqueID from the remote instead? */ + /* 0 L file size in bytes */ + /* 8 L file size on disk in bytes (block count) */ + /* 40 L uid */ + /* 48 L gid */ + /* 56 W file type */ + /* 60 L devmajor */ + /* 68 L devminor */ + /* 76 L unique ID (inode) */ + /* 84 L permissions */ + /* 92 L link count */ + + size = LVAL(p, 0); + disk_bytes = LVAL(p, 8); + + /* + * Some samba versions round up on-disk byte usage + * to 1MB boundaries, making it useless. When seeing + * that, use the size instead. + */ + if (!(disk_bytes & 0xfffff)) + disk_bytes = size+511; + + fattr->f_size = size; + fattr->f_blocks = disk_bytes >> 9; + fattr->f_ctime = smb_ntutc2unixutc(LVAL(p, 16)); + fattr->f_atime = smb_ntutc2unixutc(LVAL(p, 24)); + fattr->f_mtime = smb_ntutc2unixutc(LVAL(p, 32)); + + if (server->mnt->flags & SMB_MOUNT_UID) + fattr->f_uid = server->mnt->uid; + else + fattr->f_uid = LVAL(p, 40); + + if (server->mnt->flags & SMB_MOUNT_GID) + fattr->f_gid = server->mnt->gid; + else + fattr->f_gid = LVAL(p, 48); + + fattr->f_mode |= smb_filetype_to_mode(WVAL(p, 56)); + + if (S_ISBLK(fattr->f_mode) || S_ISCHR(fattr->f_mode)) { + __u64 major = LVAL(p, 60); + __u64 minor = LVAL(p, 68); + + fattr->f_rdev = MKDEV(major & 0xffffffff, minor & 0xffffffff); + if (MAJOR(fattr->f_rdev) != (major & 0xffffffff) || + MINOR(fattr->f_rdev) != (minor & 0xffffffff)) + fattr->f_rdev = 0; + } + + fattr->f_mode |= LVAL(p, 84); + + if ( (server->mnt->flags & SMB_MOUNT_DMODE) && + (S_ISDIR(fattr->f_mode)) ) + fattr->f_mode = (server->mnt->dir_mode & S_IRWXUGO) | S_IFDIR; + else if ( (server->mnt->flags & SMB_MOUNT_FMODE) && + !(S_ISDIR(fattr->f_mode)) ) + fattr->f_mode = (server->mnt->file_mode & S_IRWXUGO) | + (fattr->f_mode & S_IFMT); + +} + +/* + * Interpret a long filename structure using the specified info level: + * level 1 for anything below NT1 protocol + * level 260 for NT1 protocol + * + * qname is filled with the decoded, and possibly translated, name + * fattr receives decoded attributes. + * + * Bugs Noted: + * (1) Win NT 4.0 appends a null byte to names and counts it in the length! + */ +static char * +smb_decode_long_dirent(struct smb_sb_info *server, char *p, int level, + struct qstr *qname, struct smb_fattr *fattr, + unsigned char *name_buf) +{ + char *result; + unsigned int len = 0; + int n; + __u16 date, time; + int unicode = (server->mnt->flags & SMB_MOUNT_UNICODE); + + /* + * SMB doesn't have a concept of inode numbers ... + */ + smb_init_dirent(server, fattr); + fattr->f_ino = 0; /* FIXME: do we need this? */ + + switch (level) { + case 1: + len = *((unsigned char *) p + 22); + qname->name = p + 23; + result = p + 24 + len; + + date = WVAL(p, 0); + time = WVAL(p, 2); + fattr->f_ctime.tv_sec = date_dos2unix(server, date, time); + fattr->f_ctime.tv_nsec = 0; + + date = WVAL(p, 4); + time = WVAL(p, 6); + fattr->f_atime.tv_sec = date_dos2unix(server, date, time); + fattr->f_atime.tv_nsec = 0; + + date = WVAL(p, 8); + time = WVAL(p, 10); + fattr->f_mtime.tv_sec = date_dos2unix(server, date, time); + fattr->f_mtime.tv_nsec = 0; + fattr->f_size = DVAL(p, 12); + /* ULONG allocation size */ + fattr->attr = WVAL(p, 20); + + VERBOSE("info 1 at %p, len=%d, name=%.*s\n", + p, len, len, qname->name); + break; + case 260: + result = p + WVAL(p, 0); + len = DVAL(p, 60); + if (len > 255) len = 255; + /* NT4 null terminates, unless we are using unicode ... */ + qname->name = p + 94; + if (!unicode && len && qname->name[len-1] == '\0') + len--; + + fattr->f_ctime = smb_ntutc2unixutc(LVAL(p, 8)); + fattr->f_atime = smb_ntutc2unixutc(LVAL(p, 16)); + fattr->f_mtime = smb_ntutc2unixutc(LVAL(p, 24)); + /* change time (32) */ + fattr->f_size = LVAL(p, 40); + /* alloc size (48) */ + fattr->attr = DVAL(p, 56); + + VERBOSE("info 260 at %p, len=%d, name=%.*s\n", + p, len, len, qname->name); + break; + case SMB_FIND_FILE_UNIX: + result = p + WVAL(p, 0); + qname->name = p + 108; + + len = strlen(qname->name); + /* FIXME: should we check the length?? */ + + p += 8; + smb_decode_unix_basic(fattr, server, p); + VERBOSE("info SMB_FIND_FILE_UNIX at %p, len=%d, name=%.*s\n", + p, len, len, qname->name); + break; + default: + PARANOIA("Unknown info level %d\n", level); + result = p + WVAL(p, 0); + goto out; + } + + smb_finish_dirent(server, fattr); + +#if 0 + /* FIXME: These only work for ascii chars, and recent smbmount doesn't + allow the flag to be set anyway. Remove? */ + switch (server->opt.case_handling) { + case SMB_CASE_UPPER: + str_upper(qname->name, len); + break; + case SMB_CASE_LOWER: + str_lower(qname->name, len); + break; + default: + break; + } +#endif + + qname->len = 0; + n = server->ops->convert(name_buf, SMB_MAXNAMELEN, + qname->name, len, + server->remote_nls, server->local_nls); + if (n > 0) { + qname->len = n; + qname->name = name_buf; + } + +out: + return result; +} + +/* findfirst/findnext flags */ +#define SMB_CLOSE_AFTER_FIRST (1<<0) +#define SMB_CLOSE_IF_END (1<<1) +#define SMB_REQUIRE_RESUME_KEY (1<<2) +#define SMB_CONTINUE_BIT (1<<3) + +/* + * Note: samba-2.0.7 (at least) has a very similar routine, cli_list, in + * source/libsmb/clilist.c. When looking for smb bugs in the readdir code, + * go there for advise. + * + * Bugs Noted: + * (1) When using Info Level 1 Win NT 4.0 truncates directory listings + * for certain patterns of names and/or lengths. The breakage pattern + * is completely reproducible and can be toggled by the creation of a + * single file. (E.g. echo hi >foo breaks, rm -f foo works.) + */ +static int +smb_proc_readdir_long(struct file *filp, void *dirent, filldir_t filldir, + struct smb_cache_control *ctl) +{ + struct dentry *dir = filp->f_dentry; + struct smb_sb_info *server = server_from_dentry(dir); + struct qstr qname; + struct smb_fattr fattr; + + unsigned char *p, *lastname; + char *mask, *param; + __u16 command; + int first, entries_seen; + + /* Both NT and OS/2 accept info level 1 (but see note below). */ + int info_level = 260; + const int max_matches = 512; + + unsigned int ff_searchcount = 0; + unsigned int ff_eos = 0; + unsigned int ff_lastname = 0; + unsigned int ff_dir_handle = 0; + unsigned int loop_count = 0; + unsigned int mask_len, i; + int result; + struct smb_request *req; + unsigned char *name_buf; + static struct qstr star = { + .name = "*", + .len = 1, + }; + + lock_kernel(); + + /* + * We always prefer unix style. Use info level 1 for older + * servers that don't do 260. + */ + if (server->opt.capabilities & SMB_CAP_UNIX) + info_level = SMB_FIND_FILE_UNIX; + else if (server->opt.protocol < SMB_PROTOCOL_NT1) + info_level = 1; + + result = -ENOMEM; + if (! (name_buf = kmalloc(SMB_MAXNAMELEN+2, GFP_KERNEL))) + goto out; + if (! (req = smb_alloc_request(server, server->opt.max_xmit))) + goto out_name; + param = req->rq_buffer; + + /* + * Encode the initial path + */ + mask = param + 12; + + result = smb_encode_path(server, mask, SMB_MAXPATHLEN+1, dir, &star); + if (result <= 0) + goto out_free; + mask_len = result - 1; /* mask_len is strlen, not #bytes */ + result = 0; + first = 1; + VERBOSE("starting mask_len=%d, mask=%s\n", mask_len, mask); + + entries_seen = 2; + ff_eos = 0; + + while (ff_eos == 0) { + loop_count += 1; + if (loop_count > 10) { + printk(KERN_WARNING "smb_proc_readdir_long: " + "Looping in FIND_NEXT??\n"); + result = -EIO; + break; + } + + if (first != 0) { + command = TRANSACT2_FINDFIRST; + WSET(param, 0, aSYSTEM | aHIDDEN | aDIR); + WSET(param, 2, max_matches); /* max count */ + WSET(param, 4, SMB_CLOSE_IF_END); + WSET(param, 6, info_level); + DSET(param, 8, 0); + } else { + command = TRANSACT2_FINDNEXT; + + VERBOSE("handle=0x%X, lastname=%d, mask=%.*s\n", + ff_dir_handle, ff_lastname, mask_len, mask); + + WSET(param, 0, ff_dir_handle); /* search handle */ + WSET(param, 2, max_matches); /* max count */ + WSET(param, 4, info_level); + DSET(param, 6, 0); + WSET(param, 10, SMB_CONTINUE_BIT|SMB_CLOSE_IF_END); + } + + req->rq_trans2_command = command; + req->rq_ldata = 0; + req->rq_data = NULL; + req->rq_lparm = 12 + mask_len + 1; + req->rq_parm = param; + req->rq_flags = 0; + result = smb_add_request(req); + if (result < 0) { + PARANOIA("error=%d, breaking\n", result); + break; + } + + if (req->rq_rcls == ERRSRV && req->rq_err == ERRerror) { + /* a damn Win95 bug - sometimes it clags if you + ask it too fast */ + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(HZ/5); + continue; + } + + if (req->rq_rcls != 0) { + result = smb_errno(req); + PARANOIA("name=%s, result=%d, rcls=%d, err=%d\n", + mask, result, req->rq_rcls, req->rq_err); + break; + } + + /* parse out some important return info */ + if (first != 0) { + ff_dir_handle = WVAL(req->rq_parm, 0); + ff_searchcount = WVAL(req->rq_parm, 2); + ff_eos = WVAL(req->rq_parm, 4); + ff_lastname = WVAL(req->rq_parm, 8); + } else { + ff_searchcount = WVAL(req->rq_parm, 0); + ff_eos = WVAL(req->rq_parm, 2); + ff_lastname = WVAL(req->rq_parm, 6); + } + + if (ff_searchcount == 0) + break; + + /* Now we are ready to parse smb directory entries. */ + + /* point to the data bytes */ + p = req->rq_data; + for (i = 0; i < ff_searchcount; i++) { + /* make sure we stay within the buffer */ + if (p >= req->rq_data + req->rq_ldata) { + printk(KERN_ERR "smb_proc_readdir_long: " + "dirent pointer outside buffer! " + "%p %d@%p\n", + p, req->rq_ldata, req->rq_data); + result = -EIO; /* always a comm. error? */ + goto out_free; + } + + p = smb_decode_long_dirent(server, p, info_level, + &qname, &fattr, name_buf); + + /* ignore . and .. from the server */ + if (entries_seen == 2 && qname.name[0] == '.') { + if (qname.len == 1) + continue; + if (qname.name[1] == '.' && qname.len == 2) + continue; + } + + if (!smb_fill_cache(filp, dirent, filldir, ctl, + &qname, &fattr)) + ; /* stop reading? */ + entries_seen++; + } + + VERBOSE("received %d entries, eos=%d\n", ff_searchcount,ff_eos); + + /* + * We might need the lastname for continuations. + * + * Note that some servers (win95?) point to the filename and + * others (NT4, Samba using NT1) to the dir entry. We assume + * here that those who do not point to a filename do not need + * this info to continue the listing. + * + * OS/2 needs this and talks infolevel 1. + * NetApps want lastname with infolevel 260. + * win2k want lastname with infolevel 260, and points to + * the record not to the name. + * Samba+CifsUnixExt doesn't need lastname. + * + * Both are happy if we return the data they point to. So we do. + * (FIXME: above is not true with win2k) + */ + mask_len = 0; + if (info_level != SMB_FIND_FILE_UNIX && + ff_lastname > 0 && ff_lastname < req->rq_ldata) { + lastname = req->rq_data + ff_lastname; + + switch (info_level) { + case 260: + mask_len = req->rq_ldata - ff_lastname; + break; + case 1: + /* lastname points to a length byte */ + mask_len = *lastname++; + if (ff_lastname + 1 + mask_len > req->rq_ldata) + mask_len = req->rq_ldata - ff_lastname - 1; + break; + } + + /* + * Update the mask string for the next message. + */ + if (mask_len > 255) + mask_len = 255; + if (mask_len) + strncpy(mask, lastname, mask_len); + } + mask_len = strnlen(mask, mask_len); + VERBOSE("new mask, len=%d@%d of %d, mask=%.*s\n", + mask_len, ff_lastname, req->rq_ldata, mask_len, mask); + + first = 0; + loop_count = 0; + } + +out_free: + smb_rput(req); +out_name: + kfree(name_buf); +out: + unlock_kernel(); + return result; +} + +/* + * This version uses the trans2 TRANSACT2_FINDFIRST message + * to get the attribute data. + * + * Bugs Noted: + */ +static int +smb_proc_getattr_ff(struct smb_sb_info *server, struct dentry *dentry, + struct smb_fattr *fattr) +{ + char *param, *mask; + __u16 date, time; + int mask_len, result; + struct smb_request *req; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, PAGE_SIZE))) + goto out; + param = req->rq_buffer; + mask = param + 12; + + mask_len = smb_encode_path(server, mask, SMB_MAXPATHLEN+1, dentry,NULL); + if (mask_len < 0) { + result = mask_len; + goto out_free; + } + VERBOSE("name=%s, len=%d\n", mask, mask_len); + WSET(param, 0, aSYSTEM | aHIDDEN | aDIR); + WSET(param, 2, 1); /* max count */ + WSET(param, 4, 1); /* close after this call */ + WSET(param, 6, 1); /* info_level */ + DSET(param, 8, 0); + + req->rq_trans2_command = TRANSACT2_FINDFIRST; + req->rq_ldata = 0; + req->rq_data = NULL; + req->rq_lparm = 12 + mask_len; + req->rq_parm = param; + req->rq_flags = 0; + result = smb_add_request(req); + if (result < 0) + goto out_free; + if (req->rq_rcls != 0) { + result = smb_errno(req); +#ifdef SMBFS_PARANOIA + if (result != -ENOENT) + PARANOIA("error for %s, rcls=%d, err=%d\n", + mask, req->rq_rcls, req->rq_err); +#endif + goto out_free; + } + /* Make sure we got enough data ... */ + result = -EINVAL; + if (req->rq_ldata < 22 || WVAL(req->rq_parm, 2) != 1) { + PARANOIA("bad result for %s, len=%d, count=%d\n", + mask, req->rq_ldata, WVAL(req->rq_parm, 2)); + goto out_free; + } + + /* + * Decode the response into the fattr ... + */ + date = WVAL(req->rq_data, 0); + time = WVAL(req->rq_data, 2); + fattr->f_ctime.tv_sec = date_dos2unix(server, date, time); + fattr->f_ctime.tv_nsec = 0; + + date = WVAL(req->rq_data, 4); + time = WVAL(req->rq_data, 6); + fattr->f_atime.tv_sec = date_dos2unix(server, date, time); + fattr->f_atime.tv_nsec = 0; + + date = WVAL(req->rq_data, 8); + time = WVAL(req->rq_data, 10); + fattr->f_mtime.tv_sec = date_dos2unix(server, date, time); + fattr->f_mtime.tv_nsec = 0; + VERBOSE("name=%s, date=%x, time=%x, mtime=%ld\n", + mask, date, time, fattr->f_mtime); + fattr->f_size = DVAL(req->rq_data, 12); + /* ULONG allocation size */ + fattr->attr = WVAL(req->rq_data, 20); + result = 0; + +out_free: + smb_rput(req); +out: + return result; +} + +static int +smb_proc_getattr_core(struct smb_sb_info *server, struct dentry *dir, + struct smb_fattr *fattr) +{ + int result; + char *p; + struct smb_request *req; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, PAGE_SIZE))) + goto out; + + p = smb_setup_header(req, SMBgetatr, 0, 0); + result = smb_simple_encode_path(req, &p, dir, NULL); + if (result < 0) + goto out_free; + smb_setup_bcc(req, p); + + if ((result = smb_request_ok(req, SMBgetatr, 10, 0)) < 0) + goto out_free; + fattr->attr = WVAL(req->rq_header, smb_vwv0); + fattr->f_mtime.tv_sec = local2utc(server, DVAL(req->rq_header, smb_vwv1)); + fattr->f_mtime.tv_nsec = 0; + fattr->f_size = DVAL(req->rq_header, smb_vwv3); + fattr->f_ctime = fattr->f_mtime; + fattr->f_atime = fattr->f_mtime; +#ifdef SMBFS_DEBUG_TIMESTAMP + printk("getattr_core: %s/%s, mtime=%ld\n", + DENTRY_PATH(dir), fattr->f_mtime); +#endif + result = 0; + +out_free: + smb_rput(req); +out: + return result; +} + +/* + * Bugs Noted: + * (1) Win 95 swaps the date and time fields in the standard info level. + */ +static int +smb_proc_getattr_trans2(struct smb_sb_info *server, struct dentry *dir, + struct smb_request *req, int infolevel) +{ + char *p, *param; + int result; + + param = req->rq_buffer; + WSET(param, 0, infolevel); + DSET(param, 2, 0); + result = smb_encode_path(server, param+6, SMB_MAXPATHLEN+1, dir, NULL); + if (result < 0) + goto out; + p = param + 6 + result; + + req->rq_trans2_command = TRANSACT2_QPATHINFO; + req->rq_ldata = 0; + req->rq_data = NULL; + req->rq_lparm = p - param; + req->rq_parm = param; + req->rq_flags = 0; + result = smb_add_request(req); + if (result < 0) + goto out; + if (req->rq_rcls != 0) { + VERBOSE("for %s: result=%d, rcls=%d, err=%d\n", + ¶m[6], result, req->rq_rcls, req->rq_err); + result = smb_errno(req); + goto out; + } + result = -ENOENT; + if (req->rq_ldata < 22) { + PARANOIA("not enough data for %s, len=%d\n", + ¶m[6], req->rq_ldata); + goto out; + } + + result = 0; +out: + return result; +} + +static int +smb_proc_getattr_trans2_std(struct smb_sb_info *server, struct dentry *dir, + struct smb_fattr *attr) +{ + u16 date, time; + int off_date = 0, off_time = 2; + int result; + struct smb_request *req; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, PAGE_SIZE))) + goto out; + + result = smb_proc_getattr_trans2(server, dir, req, SMB_INFO_STANDARD); + if (result < 0) + goto out_free; + + /* + * Kludge alert: Win 95 swaps the date and time field, + * contrary to the CIFS docs and Win NT practice. + */ + if (server->mnt->flags & SMB_MOUNT_WIN95) { + off_date = 2; + off_time = 0; + } + date = WVAL(req->rq_data, off_date); + time = WVAL(req->rq_data, off_time); + attr->f_ctime.tv_sec = date_dos2unix(server, date, time); + attr->f_ctime.tv_nsec = 0; + + date = WVAL(req->rq_data, 4 + off_date); + time = WVAL(req->rq_data, 4 + off_time); + attr->f_atime.tv_sec = date_dos2unix(server, date, time); + attr->f_atime.tv_nsec = 0; + + date = WVAL(req->rq_data, 8 + off_date); + time = WVAL(req->rq_data, 8 + off_time); + attr->f_mtime.tv_sec = date_dos2unix(server, date, time); + attr->f_mtime.tv_nsec = 0; +#ifdef SMBFS_DEBUG_TIMESTAMP + printk(KERN_DEBUG "getattr_trans2: %s/%s, date=%x, time=%x, mtime=%ld\n", + DENTRY_PATH(dir), date, time, attr->f_mtime); +#endif + attr->f_size = DVAL(req->rq_data, 12); + attr->attr = WVAL(req->rq_data, 20); + +out_free: + smb_rput(req); +out: + return result; +} + +static int +smb_proc_getattr_trans2_all(struct smb_sb_info *server, struct dentry *dir, + struct smb_fattr *attr) +{ + struct smb_request *req; + int result; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, PAGE_SIZE))) + goto out; + + result = smb_proc_getattr_trans2(server, dir, req, + SMB_QUERY_FILE_ALL_INFO); + if (result < 0) + goto out_free; + + attr->f_ctime = smb_ntutc2unixutc(LVAL(req->rq_data, 0)); + attr->f_atime = smb_ntutc2unixutc(LVAL(req->rq_data, 8)); + attr->f_mtime = smb_ntutc2unixutc(LVAL(req->rq_data, 16)); + /* change (24) */ + attr->attr = WVAL(req->rq_data, 32); + /* pad? (34) */ + /* allocated size (40) */ + attr->f_size = LVAL(req->rq_data, 48); + +out_free: + smb_rput(req); +out: + return result; +} + +static int +smb_proc_getattr_unix(struct smb_sb_info *server, struct dentry *dir, + struct smb_fattr *attr) +{ + struct smb_request *req; + int result; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, PAGE_SIZE))) + goto out; + + result = smb_proc_getattr_trans2(server, dir, req, + SMB_QUERY_FILE_UNIX_BASIC); + if (result < 0) + goto out_free; + + smb_decode_unix_basic(attr, server, req->rq_data); + +out_free: + smb_rput(req); +out: + return result; +} + +static int +smb_proc_getattr_95(struct smb_sb_info *server, struct dentry *dir, + struct smb_fattr *attr) +{ + struct inode *inode = dir->d_inode; + int result; + + /* FIXME: why not use the "all" version? */ + result = smb_proc_getattr_trans2_std(server, dir, attr); + if (result < 0) + goto out; + + /* + * None of the getattr versions here can make win9x return the right + * filesize if there are changes made to an open file. + * A seek-to-end does return the right size, but we only need to do + * that on files we have written. + */ + if (inode && SMB_I(inode)->flags & SMB_F_LOCALWRITE && + smb_is_open(inode)) + { + __u16 fileid = SMB_I(inode)->fileid; + attr->f_size = smb_proc_seek(server, fileid, 2, 0); + } + +out: + return result; +} + +static int +smb_proc_ops_wait(struct smb_sb_info *server) +{ + int result; + + result = wait_event_interruptible_timeout(server->conn_wq, + server->conn_complete, 30*HZ); + + if (!result || signal_pending(current)) + return -EIO; + + return 0; +} + +static int +smb_proc_getattr_null(struct smb_sb_info *server, struct dentry *dir, + struct smb_fattr *fattr) +{ + int result; + + if (smb_proc_ops_wait(server) < 0) + return -EIO; + + smb_init_dirent(server, fattr); + result = server->ops->getattr(server, dir, fattr); + smb_finish_dirent(server, fattr); + + return result; +} + +static int +smb_proc_readdir_null(struct file *filp, void *dirent, filldir_t filldir, + struct smb_cache_control *ctl) +{ + struct smb_sb_info *server = server_from_dentry(filp->f_dentry); + + if (smb_proc_ops_wait(server) < 0) + return -EIO; + + return server->ops->readdir(filp, dirent, filldir, ctl); +} + +int +smb_proc_getattr(struct dentry *dir, struct smb_fattr *fattr) +{ + struct smb_sb_info *server = server_from_dentry(dir); + int result; + + smb_init_dirent(server, fattr); + result = server->ops->getattr(server, dir, fattr); + smb_finish_dirent(server, fattr); + + return result; +} + + +/* + * Because of bugs in the core protocol, we use this only to set + * attributes. See smb_proc_settime() below for timestamp handling. + * + * Bugs Noted: + * (1) If mtime is non-zero, both Win 3.1 and Win 95 fail + * with an undocumented error (ERRDOS code 50). Setting + * mtime to 0 allows the attributes to be set. + * (2) The extra parameters following the name string aren't + * in the CIFS docs, but seem to be necessary for operation. + */ +static int +smb_proc_setattr_core(struct smb_sb_info *server, struct dentry *dentry, + __u16 attr) +{ + char *p; + int result; + struct smb_request *req; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, PAGE_SIZE))) + goto out; + + p = smb_setup_header(req, SMBsetatr, 8, 0); + WSET(req->rq_header, smb_vwv0, attr); + DSET(req->rq_header, smb_vwv1, 0); /* mtime */ + WSET(req->rq_header, smb_vwv3, 0); /* reserved values */ + WSET(req->rq_header, smb_vwv4, 0); + WSET(req->rq_header, smb_vwv5, 0); + WSET(req->rq_header, smb_vwv6, 0); + WSET(req->rq_header, smb_vwv7, 0); + result = smb_simple_encode_path(req, &p, dentry, NULL); + if (result < 0) + goto out_free; + if (p + 2 > (char *)req->rq_buffer + req->rq_bufsize) { + result = -ENAMETOOLONG; + goto out_free; + } + *p++ = 4; + *p++ = 0; + smb_setup_bcc(req, p); + + result = smb_request_ok(req, SMBsetatr, 0, 0); + if (result < 0) + goto out_free; + result = 0; + +out_free: + smb_rput(req); +out: + return result; +} + +/* + * Because of bugs in the trans2 setattr messages, we must set + * attributes and timestamps separately. The core SMBsetatr + * message seems to be the only reliable way to set attributes. + */ +int +smb_proc_setattr(struct dentry *dir, struct smb_fattr *fattr) +{ + struct smb_sb_info *server = server_from_dentry(dir); + int result; + + VERBOSE("setting %s/%s, open=%d\n", + DENTRY_PATH(dir), smb_is_open(dir->d_inode)); + result = smb_proc_setattr_core(server, dir, fattr->attr); + return result; +} + +/* + * Sets the timestamps for an file open with write permissions. + */ +static int +smb_proc_setattr_ext(struct smb_sb_info *server, + struct inode *inode, struct smb_fattr *fattr) +{ + __u16 date, time; + int result; + struct smb_request *req; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, 0))) + goto out; + + smb_setup_header(req, SMBsetattrE, 7, 0); + WSET(req->rq_header, smb_vwv0, SMB_I(inode)->fileid); + /* We don't change the creation time */ + WSET(req->rq_header, smb_vwv1, 0); + WSET(req->rq_header, smb_vwv2, 0); + date_unix2dos(server, fattr->f_atime.tv_sec, &date, &time); + WSET(req->rq_header, smb_vwv3, date); + WSET(req->rq_header, smb_vwv4, time); + date_unix2dos(server, fattr->f_mtime.tv_sec, &date, &time); + WSET(req->rq_header, smb_vwv5, date); + WSET(req->rq_header, smb_vwv6, time); +#ifdef SMBFS_DEBUG_TIMESTAMP + printk(KERN_DEBUG "smb_proc_setattr_ext: date=%d, time=%d, mtime=%ld\n", + date, time, fattr->f_mtime); +#endif + + req->rq_flags |= SMB_REQ_NORETRY; + result = smb_request_ok(req, SMBsetattrE, 0, 0); + if (result < 0) + goto out_free; + result = 0; +out_free: + smb_rput(req); +out: + return result; +} + +/* + * Bugs Noted: + * (1) The TRANSACT2_SETPATHINFO message under Win NT 4.0 doesn't + * set the file's attribute flags. + */ +static int +smb_proc_setattr_trans2(struct smb_sb_info *server, + struct dentry *dir, struct smb_fattr *fattr) +{ + __u16 date, time; + char *p, *param; + int result; + char data[26]; + struct smb_request *req; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, PAGE_SIZE))) + goto out; + param = req->rq_buffer; + + WSET(param, 0, 1); /* Info level SMB_INFO_STANDARD */ + DSET(param, 2, 0); + result = smb_encode_path(server, param+6, SMB_MAXPATHLEN+1, dir, NULL); + if (result < 0) + goto out_free; + p = param + 6 + result; + + WSET(data, 0, 0); /* creation time */ + WSET(data, 2, 0); + date_unix2dos(server, fattr->f_atime.tv_sec, &date, &time); + WSET(data, 4, date); + WSET(data, 6, time); + date_unix2dos(server, fattr->f_mtime.tv_sec, &date, &time); + WSET(data, 8, date); + WSET(data, 10, time); +#ifdef SMBFS_DEBUG_TIMESTAMP + printk(KERN_DEBUG "setattr_trans2: %s/%s, date=%x, time=%x, mtime=%ld\n", + DENTRY_PATH(dir), date, time, fattr->f_mtime); +#endif + DSET(data, 12, 0); /* size */ + DSET(data, 16, 0); /* blksize */ + WSET(data, 20, 0); /* attr */ + DSET(data, 22, 0); /* ULONG EA size */ + + req->rq_trans2_command = TRANSACT2_SETPATHINFO; + req->rq_ldata = 26; + req->rq_data = data; + req->rq_lparm = p - param; + req->rq_parm = param; + req->rq_flags = 0; + result = smb_add_request(req); + if (result < 0) + goto out_free; + result = 0; + if (req->rq_rcls != 0) + result = smb_errno(req); + +out_free: + smb_rput(req); +out: + return result; +} + +/* + * ATTR_MODE 0x001 + * ATTR_UID 0x002 + * ATTR_GID 0x004 + * ATTR_SIZE 0x008 + * ATTR_ATIME 0x010 + * ATTR_MTIME 0x020 + * ATTR_CTIME 0x040 + * ATTR_ATIME_SET 0x080 + * ATTR_MTIME_SET 0x100 + * ATTR_FORCE 0x200 + * ATTR_ATTR_FLAG 0x400 + * + * major/minor should only be set by mknod. + */ +int +smb_proc_setattr_unix(struct dentry *d, struct iattr *attr, + unsigned int major, unsigned int minor) +{ + struct smb_sb_info *server = server_from_dentry(d); + u64 nttime; + char *p, *param; + int result; + char data[100]; + struct smb_request *req; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, PAGE_SIZE))) + goto out; + param = req->rq_buffer; + + DEBUG1("valid flags = 0x%04x\n", attr->ia_valid); + + WSET(param, 0, SMB_SET_FILE_UNIX_BASIC); + DSET(param, 2, 0); + result = smb_encode_path(server, param+6, SMB_MAXPATHLEN+1, d, NULL); + if (result < 0) + goto out_free; + p = param + 6 + result; + + /* 0 L file size in bytes */ + /* 8 L file size on disk in bytes (block count) */ + /* 40 L uid */ + /* 48 L gid */ + /* 56 W file type enum */ + /* 60 L devmajor */ + /* 68 L devminor */ + /* 76 L unique ID (inode) */ + /* 84 L permissions */ + /* 92 L link count */ + LSET(data, 0, SMB_SIZE_NO_CHANGE); + LSET(data, 8, SMB_SIZE_NO_CHANGE); + LSET(data, 16, SMB_TIME_NO_CHANGE); + LSET(data, 24, SMB_TIME_NO_CHANGE); + LSET(data, 32, SMB_TIME_NO_CHANGE); + LSET(data, 40, SMB_UID_NO_CHANGE); + LSET(data, 48, SMB_GID_NO_CHANGE); + LSET(data, 56, smb_filetype_from_mode(attr->ia_mode)); + LSET(data, 60, major); + LSET(data, 68, minor); + LSET(data, 76, 0); + LSET(data, 84, SMB_MODE_NO_CHANGE); + LSET(data, 92, 0); + + if (attr->ia_valid & ATTR_SIZE) { + LSET(data, 0, attr->ia_size); + LSET(data, 8, 0); /* can't set anyway */ + } + + /* + * FIXME: check the conversion function it the correct one + * + * we can't set ctime but we might as well pass this to the server + * and let it ignore it. + */ + if (attr->ia_valid & ATTR_CTIME) { + nttime = smb_unixutc2ntutc(attr->ia_ctime); + LSET(data, 16, nttime); + } + if (attr->ia_valid & ATTR_ATIME) { + nttime = smb_unixutc2ntutc(attr->ia_atime); + LSET(data, 24, nttime); + } + if (attr->ia_valid & ATTR_MTIME) { + nttime = smb_unixutc2ntutc(attr->ia_mtime); + LSET(data, 32, nttime); + } + + if (attr->ia_valid & ATTR_UID) { + LSET(data, 40, attr->ia_uid); + } + if (attr->ia_valid & ATTR_GID) { + LSET(data, 48, attr->ia_gid); + } + + if (attr->ia_valid & ATTR_MODE) { + LSET(data, 84, attr->ia_mode); + } + + req->rq_trans2_command = TRANSACT2_SETPATHINFO; + req->rq_ldata = 100; + req->rq_data = data; + req->rq_lparm = p - param; + req->rq_parm = param; + req->rq_flags = 0; + result = smb_add_request(req); + +out_free: + smb_rput(req); +out: + return result; +} + + +/* + * Set the modify and access timestamps for a file. + * + * Incredibly enough, in all of SMB there is no message to allow + * setting both attributes and timestamps at once. + * + * Bugs Noted: + * (1) Win 95 doesn't support the TRANSACT2_SETFILEINFO message + * with info level 1 (INFO_STANDARD). + * (2) Win 95 seems not to support setting directory timestamps. + * (3) Under the core protocol apparently the only way to set the + * timestamp is to open and close the file. + */ +int +smb_proc_settime(struct dentry *dentry, struct smb_fattr *fattr) +{ + struct smb_sb_info *server = server_from_dentry(dentry); + struct inode *inode = dentry->d_inode; + int result; + + VERBOSE("setting %s/%s, open=%d\n", + DENTRY_PATH(dentry), smb_is_open(inode)); + + /* setting the time on a Win95 server fails (tridge) */ + if (server->opt.protocol >= SMB_PROTOCOL_LANMAN2 && + !(server->mnt->flags & SMB_MOUNT_WIN95)) { + if (smb_is_open(inode) && SMB_I(inode)->access != SMB_O_RDONLY) + result = smb_proc_setattr_ext(server, inode, fattr); + else + result = smb_proc_setattr_trans2(server, dentry, fattr); + } else { + /* + * Fail silently on directories ... timestamp can't be set? + */ + result = 0; + if (S_ISREG(inode->i_mode)) { + /* + * Set the mtime by opening and closing the file. + * Note that the file is opened read-only, but this + * still allows us to set the date (tridge) + */ + result = -EACCES; + if (!smb_is_open(inode)) + smb_proc_open(server, dentry, SMB_O_RDONLY); + if (smb_is_open(inode)) { + inode->i_mtime = fattr->f_mtime; + result = smb_proc_close_inode(server, inode); + } + } + } + + return result; +} + +int +smb_proc_dskattr(struct super_block *sb, struct kstatfs *attr) +{ + struct smb_sb_info *server = SMB_SB(sb); + int result; + char *p; + long unit; + struct smb_request *req; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, 0))) + goto out; + + smb_setup_header(req, SMBdskattr, 0, 0); + if ((result = smb_request_ok(req, SMBdskattr, 5, 0)) < 0) + goto out_free; + p = SMB_VWV(req->rq_header); + unit = (WVAL(p, 2) * WVAL(p, 4)) >> SMB_ST_BLKSHIFT; + attr->f_blocks = WVAL(p, 0) * unit; + attr->f_bsize = SMB_ST_BLKSIZE; + attr->f_bavail = attr->f_bfree = WVAL(p, 6) * unit; + result = 0; + +out_free: + smb_rput(req); +out: + return result; +} + +int +smb_proc_read_link(struct smb_sb_info *server, struct dentry *d, + char *buffer, int len) +{ + char *p, *param; + int result; + struct smb_request *req; + + DEBUG1("readlink of %s/%s\n", DENTRY_PATH(d)); + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, PAGE_SIZE))) + goto out; + param = req->rq_buffer; + + WSET(param, 0, SMB_QUERY_FILE_UNIX_LINK); + DSET(param, 2, 0); + result = smb_encode_path(server, param+6, SMB_MAXPATHLEN+1, d, NULL); + if (result < 0) + goto out_free; + p = param + 6 + result; + + req->rq_trans2_command = TRANSACT2_QPATHINFO; + req->rq_ldata = 0; + req->rq_data = NULL; + req->rq_lparm = p - param; + req->rq_parm = param; + req->rq_flags = 0; + result = smb_add_request(req); + if (result < 0) + goto out_free; + DEBUG1("for %s: result=%d, rcls=%d, err=%d\n", + ¶m[6], result, req->rq_rcls, req->rq_err); + + /* copy data up to the \0 or buffer length */ + result = len; + if (req->rq_ldata < len) + result = req->rq_ldata; + strncpy(buffer, req->rq_data, result); + +out_free: + smb_rput(req); +out: + return result; +} + + +/* + * Create a symlink object called dentry which points to oldpath. + * Samba does not permit dangling links but returns a suitable error message. + */ +int +smb_proc_symlink(struct smb_sb_info *server, struct dentry *d, + const char *oldpath) +{ + char *p, *param; + int result; + struct smb_request *req; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, PAGE_SIZE))) + goto out; + param = req->rq_buffer; + + WSET(param, 0, SMB_SET_FILE_UNIX_LINK); + DSET(param, 2, 0); + result = smb_encode_path(server, param + 6, SMB_MAXPATHLEN+1, d, NULL); + if (result < 0) + goto out_free; + p = param + 6 + result; + + req->rq_trans2_command = TRANSACT2_SETPATHINFO; + req->rq_ldata = strlen(oldpath) + 1; + req->rq_data = (char *) oldpath; + req->rq_lparm = p - param; + req->rq_parm = param; + req->rq_flags = 0; + result = smb_add_request(req); + if (result < 0) + goto out_free; + + DEBUG1("for %s: result=%d, rcls=%d, err=%d\n", + ¶m[6], result, req->rq_rcls, req->rq_err); + result = 0; + +out_free: + smb_rput(req); +out: + return result; +} + +/* + * Create a hard link object called new_dentry which points to dentry. + */ +int +smb_proc_link(struct smb_sb_info *server, struct dentry *dentry, + struct dentry *new_dentry) +{ + char *p, *param; + int result; + struct smb_request *req; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, PAGE_SIZE))) + goto out; + param = req->rq_buffer; + + WSET(param, 0, SMB_SET_FILE_UNIX_HLINK); + DSET(param, 2, 0); + result = smb_encode_path(server, param + 6, SMB_MAXPATHLEN+1, + new_dentry, NULL); + if (result < 0) + goto out_free; + p = param + 6 + result; + + /* Grr, pointless separation of parameters and data ... */ + req->rq_data = p; + req->rq_ldata = smb_encode_path(server, p, SMB_MAXPATHLEN+1, + dentry, NULL); + + req->rq_trans2_command = TRANSACT2_SETPATHINFO; + req->rq_lparm = p - param; + req->rq_parm = param; + req->rq_flags = 0; + result = smb_add_request(req); + if (result < 0) + goto out_free; + + DEBUG1("for %s: result=%d, rcls=%d, err=%d\n", + ¶m[6], result, req->rq_rcls, req->rq_err); + result = 0; + +out_free: + smb_rput(req); +out: + return result; +} + +static int +smb_proc_query_cifsunix(struct smb_sb_info *server) +{ + int result; + int major, minor; + u64 caps; + char param[2]; + struct smb_request *req; + + result = -ENOMEM; + if (! (req = smb_alloc_request(server, 100))) + goto out; + + WSET(param, 0, SMB_QUERY_CIFS_UNIX_INFO); + + req->rq_trans2_command = TRANSACT2_QFSINFO; + req->rq_ldata = 0; + req->rq_data = NULL; + req->rq_lparm = 2; + req->rq_parm = param; + req->rq_flags = 0; + result = smb_add_request(req); + if (result < 0) + goto out_free; + + if (req->rq_ldata < 12) { + PARANOIA("Not enough data\n"); + goto out_free; + } + major = WVAL(req->rq_data, 0); + minor = WVAL(req->rq_data, 2); + + DEBUG1("Server implements CIFS Extensions for UNIX systems v%d.%d\n", + major, minor); + /* FIXME: verify that we are ok with this major/minor? */ + + caps = LVAL(req->rq_data, 4); + DEBUG1("Server capabilities 0x%016llx\n", caps); + +out_free: + smb_rput(req); +out: + return result; +} + + +static void +install_ops(struct smb_ops *dst, struct smb_ops *src) +{ + memcpy(dst, src, sizeof(void *) * SMB_OPS_NUM_STATIC); +} + +/* < LANMAN2 */ +static struct smb_ops smb_ops_core = +{ + .read = smb_proc_read, + .write = smb_proc_write, + .readdir = smb_proc_readdir_short, + .getattr = smb_proc_getattr_core, + .truncate = smb_proc_trunc32, +}; + +/* LANMAN2, OS/2, others? */ +static struct smb_ops smb_ops_os2 = +{ + .read = smb_proc_read, + .write = smb_proc_write, + .readdir = smb_proc_readdir_long, + .getattr = smb_proc_getattr_trans2_std, + .truncate = smb_proc_trunc32, +}; + +/* Win95, and possibly some NetApp versions too */ +static struct smb_ops smb_ops_win95 = +{ + .read = smb_proc_read, /* does not support 12word readX */ + .write = smb_proc_write, + .readdir = smb_proc_readdir_long, + .getattr = smb_proc_getattr_95, + .truncate = smb_proc_trunc95, +}; + +/* Samba, NT4 and NT5 */ +static struct smb_ops smb_ops_winNT = +{ + .read = smb_proc_readX, + .write = smb_proc_writeX, + .readdir = smb_proc_readdir_long, + .getattr = smb_proc_getattr_trans2_all, + .truncate = smb_proc_trunc64, +}; + +/* Samba w/ unix extensions. Others? */ +static struct smb_ops smb_ops_unix = +{ + .read = smb_proc_readX, + .write = smb_proc_writeX, + .readdir = smb_proc_readdir_long, + .getattr = smb_proc_getattr_unix, + /* FIXME: core/ext/time setattr needs to be cleaned up! */ + /* .setattr = smb_proc_setattr_unix, */ + .truncate = smb_proc_trunc64, +}; + +/* Place holder until real ops are in place */ +static struct smb_ops smb_ops_null = +{ + .readdir = smb_proc_readdir_null, + .getattr = smb_proc_getattr_null, +}; + +void smb_install_null_ops(struct smb_ops *ops) +{ + install_ops(ops, &smb_ops_null); +} diff --git a/fs/smbfs/proto.h b/fs/smbfs/proto.h new file mode 100644 index 00000000000..e866ec8660d --- /dev/null +++ b/fs/smbfs/proto.h @@ -0,0 +1,87 @@ +/* + * Autogenerated with cproto on: Sat Sep 13 17:18:51 CEST 2003 + */ + +struct smb_request; +struct sock; +struct statfs; + +/* proc.c */ +extern int smb_setcodepage(struct smb_sb_info *server, struct smb_nls_codepage *cp); +extern __u32 smb_len(__u8 *p); +extern int smb_get_rsize(struct smb_sb_info *server); +extern int smb_get_wsize(struct smb_sb_info *server); +extern int smb_errno(struct smb_request *req); +extern int smb_newconn(struct smb_sb_info *server, struct smb_conn_opt *opt); +extern __u8 *smb_setup_header(struct smb_request *req, __u8 command, __u16 wct, __u16 bcc); +extern int smb_open(struct dentry *dentry, int wish); +extern int smb_close(struct inode *ino); +extern int smb_close_fileid(struct dentry *dentry, __u16 fileid); +extern int smb_proc_create(struct dentry *dentry, __u16 attr, time_t ctime, __u16 *fileid); +extern int smb_proc_mv(struct dentry *old_dentry, struct dentry *new_dentry); +extern int smb_proc_mkdir(struct dentry *dentry); +extern int smb_proc_rmdir(struct dentry *dentry); +extern int smb_proc_unlink(struct dentry *dentry); +extern int smb_proc_flush(struct smb_sb_info *server, __u16 fileid); +extern void smb_init_root_dirent(struct smb_sb_info *server, struct smb_fattr *fattr, + struct super_block *sb); +extern int smb_proc_getattr(struct dentry *dir, struct smb_fattr *fattr); +extern int smb_proc_setattr(struct dentry *dir, struct smb_fattr *fattr); +extern int smb_proc_setattr_unix(struct dentry *d, struct iattr *attr, unsigned int major, unsigned int minor); +extern int smb_proc_settime(struct dentry *dentry, struct smb_fattr *fattr); +extern int smb_proc_dskattr(struct super_block *sb, struct kstatfs *attr); +extern int smb_proc_read_link(struct smb_sb_info *server, struct dentry *d, char *buffer, int len); +extern int smb_proc_symlink(struct smb_sb_info *server, struct dentry *d, const char *oldpath); +extern int smb_proc_link(struct smb_sb_info *server, struct dentry *dentry, struct dentry *new_dentry); +extern void smb_install_null_ops(struct smb_ops *ops); +/* dir.c */ +extern struct file_operations smb_dir_operations; +extern struct inode_operations smb_dir_inode_operations; +extern struct inode_operations smb_dir_inode_operations_unix; +extern void smb_new_dentry(struct dentry *dentry); +extern void smb_renew_times(struct dentry *dentry); +/* cache.c */ +extern void smb_invalid_dir_cache(struct inode *dir); +extern void smb_invalidate_dircache_entries(struct dentry *parent); +extern struct dentry *smb_dget_fpos(struct dentry *dentry, struct dentry *parent, unsigned long fpos); +extern int smb_fill_cache(struct file *filp, void *dirent, filldir_t filldir, struct smb_cache_control *ctrl, struct qstr *qname, struct smb_fattr *entry); +/* sock.c */ +extern void smb_data_ready(struct sock *sk, int len); +extern int smb_valid_socket(struct inode *inode); +extern void smb_close_socket(struct smb_sb_info *server); +extern int smb_recv_available(struct smb_sb_info *server); +extern int smb_receive_header(struct smb_sb_info *server); +extern int smb_receive_drop(struct smb_sb_info *server); +extern int smb_receive(struct smb_sb_info *server, struct smb_request *req); +extern int smb_send_request(struct smb_request *req); +/* inode.c */ +extern struct inode *smb_iget(struct super_block *sb, struct smb_fattr *fattr); +extern void smb_get_inode_attr(struct inode *inode, struct smb_fattr *fattr); +extern void smb_set_inode_attr(struct inode *inode, struct smb_fattr *fattr); +extern void smb_invalidate_inodes(struct smb_sb_info *server); +extern int smb_revalidate_inode(struct dentry *dentry); +extern int smb_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat); +extern int smb_notify_change(struct dentry *dentry, struct iattr *attr); +/* file.c */ +extern struct address_space_operations smb_file_aops; +extern struct file_operations smb_file_operations; +extern struct inode_operations smb_file_inode_operations; +/* ioctl.c */ +extern int smb_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); +/* smbiod.c */ +extern void smbiod_wake_up(void); +extern int smbiod_register_server(struct smb_sb_info *server); +extern void smbiod_unregister_server(struct smb_sb_info *server); +extern void smbiod_flush(struct smb_sb_info *server); +extern int smbiod_retry(struct smb_sb_info *server); +/* request.c */ +extern int smb_init_request_cache(void); +extern void smb_destroy_request_cache(void); +extern struct smb_request *smb_alloc_request(struct smb_sb_info *server, int bufsize); +extern void smb_rput(struct smb_request *req); +extern int smb_add_request(struct smb_request *req); +extern int smb_request_send_server(struct smb_sb_info *server); +extern int smb_request_recv(struct smb_sb_info *server); +/* symlink.c */ +extern int smb_symlink(struct inode *inode, struct dentry *dentry, const char *oldname); +extern struct inode_operations smb_link_inode_operations; diff --git a/fs/smbfs/request.c b/fs/smbfs/request.c new file mode 100644 index 00000000000..2d85dd7415b --- /dev/null +++ b/fs/smbfs/request.c @@ -0,0 +1,823 @@ +/* + * request.c + * + * Copyright (C) 2001 by Urban Widmark + * + * Please add a note about your changes to smbfs in the ChangeLog file. + */ + +#include <linux/types.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/net.h> + +#include <linux/smb_fs.h> +#include <linux/smbno.h> +#include <linux/smb_mount.h> + +#include "smb_debug.h" +#include "request.h" +#include "proto.h" + +/* #define SMB_SLAB_DEBUG (SLAB_RED_ZONE | SLAB_POISON) */ +#define SMB_SLAB_DEBUG 0 + +#define ROUND_UP(x) (((x)+3) & ~3) + +/* cache for request structures */ +static kmem_cache_t *req_cachep; + +static int smb_request_send_req(struct smb_request *req); + +/* + /proc/slabinfo: + name, active, num, objsize, active_slabs, num_slaps, #pages +*/ + + +int smb_init_request_cache(void) +{ + req_cachep = kmem_cache_create("smb_request", + sizeof(struct smb_request), 0, + SMB_SLAB_DEBUG | SLAB_HWCACHE_ALIGN, + NULL, NULL); + if (req_cachep == NULL) + return -ENOMEM; + + return 0; +} + +void smb_destroy_request_cache(void) +{ + if (kmem_cache_destroy(req_cachep)) + printk(KERN_INFO "smb_destroy_request_cache: not all structures were freed\n"); +} + +/* + * Allocate and initialise a request structure + */ +static struct smb_request *smb_do_alloc_request(struct smb_sb_info *server, + int bufsize) +{ + struct smb_request *req; + unsigned char *buf = NULL; + + req = kmem_cache_alloc(req_cachep, SLAB_KERNEL); + VERBOSE("allocating request: %p\n", req); + if (!req) + goto out; + + if (bufsize > 0) { + buf = smb_kmalloc(bufsize, GFP_NOFS); + if (!buf) { + kmem_cache_free(req_cachep, req); + return NULL; + } + } + + memset(req, 0, sizeof(struct smb_request)); + req->rq_buffer = buf; + req->rq_bufsize = bufsize; + req->rq_server = server; + init_waitqueue_head(&req->rq_wait); + INIT_LIST_HEAD(&req->rq_queue); + atomic_set(&req->rq_count, 1); + +out: + return req; +} + +struct smb_request *smb_alloc_request(struct smb_sb_info *server, int bufsize) +{ + struct smb_request *req = NULL; + + for (;;) { + atomic_inc(&server->nr_requests); + if (atomic_read(&server->nr_requests) <= MAX_REQUEST_HARD) { + req = smb_do_alloc_request(server, bufsize); + if (req != NULL) + break; + } + +#if 0 + /* + * Try to free up at least one request in order to stay + * below the hard limit + */ + if (nfs_try_to_free_pages(server)) + continue; + + if (signalled() && (server->flags & NFS_MOUNT_INTR)) + return ERR_PTR(-ERESTARTSYS); + current->policy = SCHED_YIELD; + schedule(); +#else + /* FIXME: we want something like nfs does above, but that + requires changes to all callers and can wait. */ + break; +#endif + } + return req; +} + +static void smb_free_request(struct smb_request *req) +{ + atomic_dec(&req->rq_server->nr_requests); + if (req->rq_buffer && !(req->rq_flags & SMB_REQ_STATIC)) + smb_kfree(req->rq_buffer); + if (req->rq_trans2buffer) + smb_kfree(req->rq_trans2buffer); + kmem_cache_free(req_cachep, req); +} + +/* + * What prevents a rget to race with a rput? The count must never drop to zero + * while it is in use. Only rput if it is ok that it is free'd. + */ +static void smb_rget(struct smb_request *req) +{ + atomic_inc(&req->rq_count); +} +void smb_rput(struct smb_request *req) +{ + if (atomic_dec_and_test(&req->rq_count)) { + list_del_init(&req->rq_queue); + smb_free_request(req); + } +} + +/* setup to receive the data part of the SMB */ +static int smb_setup_bcc(struct smb_request *req) +{ + int result = 0; + req->rq_rlen = smb_len(req->rq_header) + 4 - req->rq_bytes_recvd; + + if (req->rq_rlen > req->rq_bufsize) { + PARANOIA("Packet too large %d > %d\n", + req->rq_rlen, req->rq_bufsize); + return -ENOBUFS; + } + + req->rq_iov[0].iov_base = req->rq_buffer; + req->rq_iov[0].iov_len = req->rq_rlen; + req->rq_iovlen = 1; + + return result; +} + +/* + * Prepare a "normal" request structure. + */ +static int smb_setup_request(struct smb_request *req) +{ + int len = smb_len(req->rq_header) + 4; + req->rq_slen = len; + + /* if we expect a data part in the reply we set the iov's to read it */ + if (req->rq_resp_bcc) + req->rq_setup_read = smb_setup_bcc; + + /* This tries to support re-using the same request */ + req->rq_bytes_sent = 0; + req->rq_rcls = 0; + req->rq_err = 0; + req->rq_errno = 0; + req->rq_fragment = 0; + if (req->rq_trans2buffer) + smb_kfree(req->rq_trans2buffer); + + return 0; +} + +/* + * Prepare a transaction2 request structure + */ +static int smb_setup_trans2request(struct smb_request *req) +{ + struct smb_sb_info *server = req->rq_server; + int mparam, mdata; + static unsigned char padding[4]; + + /* I know the following is very ugly, but I want to build the + smb packet as efficiently as possible. */ + + const int smb_parameters = 15; + const int header = SMB_HEADER_LEN + 2 * smb_parameters + 2; + const int oparam = ROUND_UP(header + 3); + const int odata = ROUND_UP(oparam + req->rq_lparm); + const int bcc = (req->rq_data ? odata + req->rq_ldata : + oparam + req->rq_lparm) - header; + + if ((bcc + oparam) > server->opt.max_xmit) + return -ENOMEM; + smb_setup_header(req, SMBtrans2, smb_parameters, bcc); + + /* + * max parameters + max data + max setup == bufsize to make NT4 happy + * and not abort the transfer or split into multiple responses. It also + * makes smbfs happy as handling packets larger than the buffer size + * is extra work. + * + * OS/2 is probably going to hate me for this ... + */ + mparam = SMB_TRANS2_MAX_PARAM; + mdata = req->rq_bufsize - mparam; + + mdata = server->opt.max_xmit - mparam - 100; + if (mdata < 1024) { + mdata = 1024; + mparam = 20; + } + +#if 0 + /* NT/win2k has ~4k max_xmit, so with this we request more than it wants + to return as one SMB. Useful for testing the fragmented trans2 + handling. */ + mdata = 8192; +#endif + + WSET(req->rq_header, smb_tpscnt, req->rq_lparm); + WSET(req->rq_header, smb_tdscnt, req->rq_ldata); + WSET(req->rq_header, smb_mprcnt, mparam); + WSET(req->rq_header, smb_mdrcnt, mdata); + WSET(req->rq_header, smb_msrcnt, 0); /* max setup always 0 ? */ + WSET(req->rq_header, smb_flags, 0); + DSET(req->rq_header, smb_timeout, 0); + WSET(req->rq_header, smb_pscnt, req->rq_lparm); + WSET(req->rq_header, smb_psoff, oparam - 4); + WSET(req->rq_header, smb_dscnt, req->rq_ldata); + WSET(req->rq_header, smb_dsoff, req->rq_data ? odata - 4 : 0); + *(req->rq_header + smb_suwcnt) = 0x01; /* setup count */ + *(req->rq_header + smb_suwcnt + 1) = 0x00; /* reserved */ + WSET(req->rq_header, smb_setup0, req->rq_trans2_command); + + req->rq_iovlen = 2; + req->rq_iov[0].iov_base = (void *) req->rq_header; + req->rq_iov[0].iov_len = oparam; + req->rq_iov[1].iov_base = (req->rq_parm==NULL) ? padding : req->rq_parm; + req->rq_iov[1].iov_len = req->rq_lparm; + req->rq_slen = oparam + req->rq_lparm; + + if (req->rq_data) { + req->rq_iovlen += 2; + req->rq_iov[2].iov_base = padding; + req->rq_iov[2].iov_len = odata - oparam - req->rq_lparm; + req->rq_iov[3].iov_base = req->rq_data; + req->rq_iov[3].iov_len = req->rq_ldata; + req->rq_slen = odata + req->rq_ldata; + } + + /* always a data part for trans2 replies */ + req->rq_setup_read = smb_setup_bcc; + + return 0; +} + +/* + * Add a request and tell smbiod to process it + */ +int smb_add_request(struct smb_request *req) +{ + long timeleft; + struct smb_sb_info *server = req->rq_server; + int result = 0; + + smb_setup_request(req); + if (req->rq_trans2_command) { + if (req->rq_buffer == NULL) { + PARANOIA("trans2 attempted without response buffer!\n"); + return -EIO; + } + result = smb_setup_trans2request(req); + } + if (result < 0) + return result; + +#ifdef SMB_DEBUG_PACKET_SIZE + add_xmit_stats(req); +#endif + + /* add 'req' to the queue of requests */ + if (smb_lock_server_interruptible(server)) + return -EINTR; + + /* + * Try to send the request as the process. If that fails we queue the + * request and let smbiod send it later. + */ + + /* FIXME: each server has a number on the maximum number of parallel + requests. 10, 50 or so. We should not allow more requests to be + active. */ + if (server->mid > 0xf000) + server->mid = 0; + req->rq_mid = server->mid++; + WSET(req->rq_header, smb_mid, req->rq_mid); + + result = 0; + if (server->state == CONN_VALID) { + if (list_empty(&server->xmitq)) + result = smb_request_send_req(req); + if (result < 0) { + /* Connection lost? */ + server->conn_error = result; + server->state = CONN_INVALID; + } + } + if (result != 1) + list_add_tail(&req->rq_queue, &server->xmitq); + smb_rget(req); + + if (server->state != CONN_VALID) + smbiod_retry(server); + + smb_unlock_server(server); + + smbiod_wake_up(); + + timeleft = wait_event_interruptible_timeout(req->rq_wait, + req->rq_flags & SMB_REQ_RECEIVED, 30*HZ); + if (!timeleft || signal_pending(current)) { + /* + * On timeout or on interrupt we want to try and remove the + * request from the recvq/xmitq. + */ + smb_lock_server(server); + if (!(req->rq_flags & SMB_REQ_RECEIVED)) { + list_del_init(&req->rq_queue); + smb_rput(req); + } + smb_unlock_server(server); + } + + if (!timeleft) { + PARANOIA("request [%p, mid=%d] timed out!\n", + req, req->rq_mid); + VERBOSE("smb_com: %02x\n", *(req->rq_header + smb_com)); + VERBOSE("smb_rcls: %02x\n", *(req->rq_header + smb_rcls)); + VERBOSE("smb_flg: %02x\n", *(req->rq_header + smb_flg)); + VERBOSE("smb_tid: %04x\n", WVAL(req->rq_header, smb_tid)); + VERBOSE("smb_pid: %04x\n", WVAL(req->rq_header, smb_pid)); + VERBOSE("smb_uid: %04x\n", WVAL(req->rq_header, smb_uid)); + VERBOSE("smb_mid: %04x\n", WVAL(req->rq_header, smb_mid)); + VERBOSE("smb_wct: %02x\n", *(req->rq_header + smb_wct)); + + req->rq_rcls = ERRSRV; + req->rq_err = ERRtimeout; + + /* Just in case it was "stuck" */ + smbiod_wake_up(); + } + VERBOSE("woke up, rcls=%d\n", req->rq_rcls); + + if (req->rq_rcls != 0) + req->rq_errno = smb_errno(req); + if (signal_pending(current)) + req->rq_errno = -ERESTARTSYS; + return req->rq_errno; +} + +/* + * Send a request and place it on the recvq if successfully sent. + * Must be called with the server lock held. + */ +static int smb_request_send_req(struct smb_request *req) +{ + struct smb_sb_info *server = req->rq_server; + int result; + + if (req->rq_bytes_sent == 0) { + WSET(req->rq_header, smb_tid, server->opt.tid); + WSET(req->rq_header, smb_pid, 1); + WSET(req->rq_header, smb_uid, server->opt.server_uid); + } + + result = smb_send_request(req); + if (result < 0 && result != -EAGAIN) + goto out; + + result = 0; + if (!(req->rq_flags & SMB_REQ_TRANSMITTED)) + goto out; + + list_del_init(&req->rq_queue); + list_add_tail(&req->rq_queue, &server->recvq); + result = 1; +out: + return result; +} + +/* + * Sends one request for this server. (smbiod) + * Must be called with the server lock held. + * Returns: <0 on error + * 0 if no request could be completely sent + * 1 if all data for one request was sent + */ +int smb_request_send_server(struct smb_sb_info *server) +{ + struct list_head *head; + struct smb_request *req; + int result; + + if (server->state != CONN_VALID) + return 0; + + /* dequeue first request, if any */ + req = NULL; + head = server->xmitq.next; + if (head != &server->xmitq) { + req = list_entry(head, struct smb_request, rq_queue); + } + if (!req) + return 0; + + result = smb_request_send_req(req); + if (result < 0) { + server->conn_error = result; + list_del_init(&req->rq_queue); + list_add(&req->rq_queue, &server->xmitq); + result = -EIO; + goto out; + } + +out: + return result; +} + +/* + * Try to find a request matching this "mid". Typically the first entry will + * be the matching one. + */ +static struct smb_request *find_request(struct smb_sb_info *server, int mid) +{ + struct list_head *tmp; + struct smb_request *req = NULL; + + list_for_each(tmp, &server->recvq) { + req = list_entry(tmp, struct smb_request, rq_queue); + if (req->rq_mid == mid) { + break; + } + req = NULL; + } + + if (!req) { + VERBOSE("received reply with mid %d but no request!\n", + WVAL(server->header, smb_mid)); + server->rstate = SMB_RECV_DROP; + } + + return req; +} + +/* + * Called when we have read the smb header and believe this is a response. + */ +static int smb_init_request(struct smb_sb_info *server, struct smb_request *req) +{ + int hdrlen, wct; + + memcpy(req->rq_header, server->header, SMB_HEADER_LEN); + + wct = *(req->rq_header + smb_wct); + if (wct > 20) { + PARANOIA("wct too large, %d > 20\n", wct); + server->rstate = SMB_RECV_DROP; + return 0; + } + + req->rq_resp_wct = wct; + hdrlen = SMB_HEADER_LEN + wct*2 + 2; + VERBOSE("header length: %d smb_wct: %2d\n", hdrlen, wct); + + req->rq_bytes_recvd = SMB_HEADER_LEN; + req->rq_rlen = hdrlen; + req->rq_iov[0].iov_base = req->rq_header; + req->rq_iov[0].iov_len = hdrlen; + req->rq_iovlen = 1; + server->rstate = SMB_RECV_PARAM; + +#ifdef SMB_DEBUG_PACKET_SIZE + add_recv_stats(smb_len(server->header)); +#endif + return 0; +} + +/* + * Reads the SMB parameters + */ +static int smb_recv_param(struct smb_sb_info *server, struct smb_request *req) +{ + int result; + + result = smb_receive(server, req); + if (result < 0) + return result; + if (req->rq_bytes_recvd < req->rq_rlen) + return 0; + + VERBOSE("result: %d smb_bcc: %04x\n", result, + WVAL(req->rq_header, SMB_HEADER_LEN + + (*(req->rq_header + smb_wct) * 2))); + + result = 0; + req->rq_iov[0].iov_base = NULL; + req->rq_rlen = 0; + if (req->rq_callback) + req->rq_callback(req); + else if (req->rq_setup_read) + result = req->rq_setup_read(req); + if (result < 0) { + server->rstate = SMB_RECV_DROP; + return result; + } + + server->rstate = req->rq_rlen > 0 ? SMB_RECV_DATA : SMB_RECV_END; + + req->rq_bytes_recvd = 0; // recvd out of the iov + + VERBOSE("rlen: %d\n", req->rq_rlen); + if (req->rq_rlen < 0) { + PARANOIA("Parameters read beyond end of packet!\n"); + server->rstate = SMB_RECV_END; + return -EIO; + } + return 0; +} + +/* + * Reads the SMB data + */ +static int smb_recv_data(struct smb_sb_info *server, struct smb_request *req) +{ + int result; + + result = smb_receive(server, req); + if (result < 0) + goto out; + if (req->rq_bytes_recvd < req->rq_rlen) + goto out; + server->rstate = SMB_RECV_END; +out: + VERBOSE("result: %d\n", result); + return result; +} + +/* + * Receive a transaction2 response + * Return: 0 if the response has been fully read + * 1 if there are further "fragments" to read + * <0 if there is an error + */ +static int smb_recv_trans2(struct smb_sb_info *server, struct smb_request *req) +{ + unsigned char *inbuf; + unsigned int parm_disp, parm_offset, parm_count, parm_tot; + unsigned int data_disp, data_offset, data_count, data_tot; + int hdrlen = SMB_HEADER_LEN + req->rq_resp_wct*2 - 2; + + VERBOSE("handling trans2\n"); + + inbuf = req->rq_header; + data_tot = WVAL(inbuf, smb_tdrcnt); + parm_tot = WVAL(inbuf, smb_tprcnt); + parm_disp = WVAL(inbuf, smb_prdisp); + parm_offset = WVAL(inbuf, smb_proff); + parm_count = WVAL(inbuf, smb_prcnt); + data_disp = WVAL(inbuf, smb_drdisp); + data_offset = WVAL(inbuf, smb_droff); + data_count = WVAL(inbuf, smb_drcnt); + + /* Modify offset for the split header/buffer we use */ + if (data_count || data_offset) { + if (unlikely(data_offset < hdrlen)) + goto out_bad_data; + else + data_offset -= hdrlen; + } + if (parm_count || parm_offset) { + if (unlikely(parm_offset < hdrlen)) + goto out_bad_parm; + else + parm_offset -= hdrlen; + } + + if (parm_count == parm_tot && data_count == data_tot) { + /* + * This packet has all the trans2 data. + * + * We setup the request so that this will be the common + * case. It may be a server error to not return a + * response that fits. + */ + VERBOSE("single trans2 response " + "dcnt=%u, pcnt=%u, doff=%u, poff=%u\n", + data_count, parm_count, + data_offset, parm_offset); + req->rq_ldata = data_count; + req->rq_lparm = parm_count; + req->rq_data = req->rq_buffer + data_offset; + req->rq_parm = req->rq_buffer + parm_offset; + if (unlikely(parm_offset + parm_count > req->rq_rlen)) + goto out_bad_parm; + if (unlikely(data_offset + data_count > req->rq_rlen)) + goto out_bad_data; + return 0; + } + + VERBOSE("multi trans2 response " + "frag=%d, dcnt=%u, pcnt=%u, doff=%u, poff=%u\n", + req->rq_fragment, + data_count, parm_count, + data_offset, parm_offset); + + if (!req->rq_fragment) { + int buf_len; + + /* We got the first trans2 fragment */ + req->rq_fragment = 1; + req->rq_total_data = data_tot; + req->rq_total_parm = parm_tot; + req->rq_ldata = 0; + req->rq_lparm = 0; + + buf_len = data_tot + parm_tot; + if (buf_len > SMB_MAX_PACKET_SIZE) + goto out_too_long; + + req->rq_trans2bufsize = buf_len; + req->rq_trans2buffer = smb_kmalloc(buf_len, GFP_NOFS); + if (!req->rq_trans2buffer) + goto out_no_mem; + memset(req->rq_trans2buffer, 0, buf_len); + + req->rq_parm = req->rq_trans2buffer; + req->rq_data = req->rq_trans2buffer + parm_tot; + } else if (unlikely(req->rq_total_data < data_tot || + req->rq_total_parm < parm_tot)) + goto out_data_grew; + + if (unlikely(parm_disp + parm_count > req->rq_total_parm || + parm_offset + parm_count > req->rq_rlen)) + goto out_bad_parm; + if (unlikely(data_disp + data_count > req->rq_total_data || + data_offset + data_count > req->rq_rlen)) + goto out_bad_data; + + inbuf = req->rq_buffer; + memcpy(req->rq_parm + parm_disp, inbuf + parm_offset, parm_count); + memcpy(req->rq_data + data_disp, inbuf + data_offset, data_count); + + req->rq_ldata += data_count; + req->rq_lparm += parm_count; + + /* + * Check whether we've received all of the data. Note that + * we use the packet totals -- total lengths might shrink! + */ + if (req->rq_ldata >= data_tot && req->rq_lparm >= parm_tot) { + req->rq_ldata = data_tot; + req->rq_lparm = parm_tot; + return 0; + } + return 1; + +out_too_long: + printk(KERN_ERR "smb_trans2: data/param too long, data=%u, parm=%u\n", + data_tot, parm_tot); + goto out_EIO; +out_no_mem: + printk(KERN_ERR "smb_trans2: couldn't allocate data area of %d bytes\n", + req->rq_trans2bufsize); + req->rq_errno = -ENOMEM; + goto out; +out_data_grew: + printk(KERN_ERR "smb_trans2: data/params grew!\n"); + goto out_EIO; +out_bad_parm: + printk(KERN_ERR "smb_trans2: invalid parms, disp=%u, cnt=%u, tot=%u, ofs=%u\n", + parm_disp, parm_count, parm_tot, parm_offset); + goto out_EIO; +out_bad_data: + printk(KERN_ERR "smb_trans2: invalid data, disp=%u, cnt=%u, tot=%u, ofs=%u\n", + data_disp, data_count, data_tot, data_offset); +out_EIO: + req->rq_errno = -EIO; +out: + return req->rq_errno; +} + +/* + * State machine for receiving responses. We handle the fact that we can't + * read the full response in one try by having states telling us how much we + * have read. + * + * Must be called with the server lock held (only called from smbiod). + * + * Return: <0 on error + */ +int smb_request_recv(struct smb_sb_info *server) +{ + struct smb_request *req = NULL; + int result = 0; + + if (smb_recv_available(server) <= 0) + return 0; + + VERBOSE("state: %d\n", server->rstate); + switch (server->rstate) { + case SMB_RECV_DROP: + result = smb_receive_drop(server); + if (result < 0) + break; + if (server->rstate == SMB_RECV_DROP) + break; + server->rstate = SMB_RECV_START; + /* fallthrough */ + case SMB_RECV_START: + server->smb_read = 0; + server->rstate = SMB_RECV_HEADER; + /* fallthrough */ + case SMB_RECV_HEADER: + result = smb_receive_header(server); + if (result < 0) + break; + if (server->rstate == SMB_RECV_HEADER) + break; + if (! (*(server->header + smb_flg) & SMB_FLAGS_REPLY) ) { + server->rstate = SMB_RECV_REQUEST; + break; + } + if (server->rstate != SMB_RECV_HCOMPLETE) + break; + /* fallthrough */ + case SMB_RECV_HCOMPLETE: + req = find_request(server, WVAL(server->header, smb_mid)); + if (!req) + break; + smb_init_request(server, req); + req->rq_rcls = *(req->rq_header + smb_rcls); + req->rq_err = WVAL(req->rq_header, smb_err); + if (server->rstate != SMB_RECV_PARAM) + break; + /* fallthrough */ + case SMB_RECV_PARAM: + if (!req) + req = find_request(server,WVAL(server->header,smb_mid)); + if (!req) + break; + result = smb_recv_param(server, req); + if (result < 0) + break; + if (server->rstate != SMB_RECV_DATA) + break; + /* fallthrough */ + case SMB_RECV_DATA: + if (!req) + req = find_request(server,WVAL(server->header,smb_mid)); + if (!req) + break; + result = smb_recv_data(server, req); + if (result < 0) + break; + break; + + /* We should never be called with any of these states */ + case SMB_RECV_END: + case SMB_RECV_REQUEST: + server->rstate = SMB_RECV_END; + break; + } + + if (result < 0) { + /* We saw an error */ + return result; + } + + if (server->rstate != SMB_RECV_END) + return 0; + + result = 0; + if (req->rq_trans2_command && req->rq_rcls == SUCCESS) + result = smb_recv_trans2(server, req); + + /* + * Response completely read. Drop any extra bytes sent by the server. + * (Yes, servers sometimes add extra bytes to responses) + */ + VERBOSE("smb_len: %d smb_read: %d\n", + server->smb_len, server->smb_read); + if (server->smb_read < server->smb_len) + smb_receive_drop(server); + + server->rstate = SMB_RECV_START; + + if (!result) { + list_del_init(&req->rq_queue); + req->rq_flags |= SMB_REQ_RECEIVED; + smb_rput(req); + wake_up_interruptible(&req->rq_wait); + } + return 0; +} diff --git a/fs/smbfs/request.h b/fs/smbfs/request.h new file mode 100644 index 00000000000..efb21451e7c --- /dev/null +++ b/fs/smbfs/request.h @@ -0,0 +1,70 @@ +#include <linux/list.h> +#include <linux/types.h> +#include <linux/uio.h> +#include <linux/wait.h> + +struct smb_request { + struct list_head rq_queue; /* recvq or xmitq for the server */ + + atomic_t rq_count; + + wait_queue_head_t rq_wait; + int rq_flags; + int rq_mid; /* multiplex ID, set by request.c */ + + struct smb_sb_info *rq_server; + + /* header + word count + parameter words + byte count */ + unsigned char rq_header[SMB_HEADER_LEN + 20*2 + 2]; + + int rq_bufsize; + unsigned char *rq_buffer; + + /* FIXME: this is not good enough for merging IO requests. */ + unsigned char *rq_page; + int rq_rsize; + + int rq_resp_wct; + int rq_resp_bcc; + + int rq_rlen; + int rq_bytes_recvd; + + int rq_slen; + int rq_bytes_sent; + + int rq_iovlen; + struct kvec rq_iov[4]; + + int (*rq_setup_read) (struct smb_request *); + void (*rq_callback) (struct smb_request *); + + /* ------ trans2 stuff ------ */ + + u16 rq_trans2_command; /* 0 if not a trans2 request */ + unsigned int rq_ldata; + unsigned char *rq_data; + unsigned int rq_lparm; + unsigned char *rq_parm; + + int rq_fragment; + u32 rq_total_data; + u32 rq_total_parm; + int rq_trans2bufsize; + unsigned char *rq_trans2buffer; + + /* ------ response ------ */ + + unsigned short rq_rcls; + unsigned short rq_err; + int rq_errno; +}; + +#define SMB_REQ_STATIC 0x0001 /* rq_buffer is static */ +#define SMB_REQ_NORETRY 0x0002 /* request is invalid after retry */ + +#define SMB_REQ_TRANSMITTED 0x4000 /* all data has been sent */ +#define SMB_REQ_RECEIVED 0x8000 /* reply received, smbiod is done */ + +#define xSMB_REQ_NOREPLY 0x0004 /* we don't want the reply (if any) */ +#define xSMB_REQ_NORECEIVER 0x0008 /* caller doesn't wait for response */ diff --git a/fs/smbfs/smb_debug.h b/fs/smbfs/smb_debug.h new file mode 100644 index 00000000000..734972b9269 --- /dev/null +++ b/fs/smbfs/smb_debug.h @@ -0,0 +1,34 @@ +/* + * Defines some debug macros for smbfs. + */ + +/* This makes a dentry parent/child name pair. Useful for debugging printk's */ +#define DENTRY_PATH(dentry) \ + (dentry)->d_parent->d_name.name,(dentry)->d_name.name + +/* + * safety checks that should never happen ??? + * these are normally enabled. + */ +#ifdef SMBFS_PARANOIA +# define PARANOIA(f, a...) printk(KERN_NOTICE "%s: " f, __FUNCTION__ , ## a) +#else +# define PARANOIA(f, a...) do { ; } while(0) +#endif + +/* lots of debug messages */ +#ifdef SMBFS_DEBUG_VERBOSE +# define VERBOSE(f, a...) printk(KERN_DEBUG "%s: " f, __FUNCTION__ , ## a) +#else +# define VERBOSE(f, a...) do { ; } while(0) +#endif + +/* + * "normal" debug messages, but not with a normal DEBUG define ... way + * too common name. + */ +#ifdef SMBFS_DEBUG +#define DEBUG1(f, a...) printk(KERN_DEBUG "%s: " f, __FUNCTION__ , ## a) +#else +#define DEBUG1(f, a...) do { ; } while(0) +#endif diff --git a/fs/smbfs/smbiod.c b/fs/smbfs/smbiod.c new file mode 100644 index 00000000000..481a97a423f --- /dev/null +++ b/fs/smbfs/smbiod.c @@ -0,0 +1,341 @@ +/* + * smbiod.c + * + * Copyright (C) 2000, Charles Loep / Corel Corp. + * Copyright (C) 2001, Urban Widmark + */ + +#include <linux/config.h> + +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/stat.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/file.h> +#include <linux/dcache.h> +#include <linux/smp_lock.h> +#include <linux/module.h> +#include <linux/net.h> +#include <net/ip.h> + +#include <linux/smb_fs.h> +#include <linux/smbno.h> +#include <linux/smb_mount.h> + +#include <asm/system.h> +#include <asm/uaccess.h> + +#include "smb_debug.h" +#include "request.h" +#include "proto.h" + +enum smbiod_state { + SMBIOD_DEAD, + SMBIOD_STARTING, + SMBIOD_RUNNING, +}; + +static enum smbiod_state smbiod_state = SMBIOD_DEAD; +static pid_t smbiod_pid; +static DECLARE_WAIT_QUEUE_HEAD(smbiod_wait); +static LIST_HEAD(smb_servers); +static DEFINE_SPINLOCK(servers_lock); + +#define SMBIOD_DATA_READY (1<<0) +static long smbiod_flags; + +static int smbiod(void *); +static int smbiod_start(void); + +/* + * called when there's work for us to do + */ +void smbiod_wake_up(void) +{ + if (smbiod_state == SMBIOD_DEAD) + return; + set_bit(SMBIOD_DATA_READY, &smbiod_flags); + wake_up_interruptible(&smbiod_wait); +} + +/* + * start smbiod if none is running + */ +static int smbiod_start(void) +{ + pid_t pid; + if (smbiod_state != SMBIOD_DEAD) + return 0; + smbiod_state = SMBIOD_STARTING; + __module_get(THIS_MODULE); + spin_unlock(&servers_lock); + pid = kernel_thread(smbiod, NULL, 0); + if (pid < 0) + module_put(THIS_MODULE); + + spin_lock(&servers_lock); + smbiod_state = pid < 0 ? SMBIOD_DEAD : SMBIOD_RUNNING; + smbiod_pid = pid; + return pid; +} + +/* + * register a server & start smbiod if necessary + */ +int smbiod_register_server(struct smb_sb_info *server) +{ + int ret; + spin_lock(&servers_lock); + list_add(&server->entry, &smb_servers); + VERBOSE("%p\n", server); + ret = smbiod_start(); + spin_unlock(&servers_lock); + return ret; +} + +/* + * Unregister a server + * Must be called with the server lock held. + */ +void smbiod_unregister_server(struct smb_sb_info *server) +{ + spin_lock(&servers_lock); + list_del_init(&server->entry); + VERBOSE("%p\n", server); + spin_unlock(&servers_lock); + + smbiod_wake_up(); + smbiod_flush(server); +} + +void smbiod_flush(struct smb_sb_info *server) +{ + struct list_head *tmp, *n; + struct smb_request *req; + + list_for_each_safe(tmp, n, &server->xmitq) { + req = list_entry(tmp, struct smb_request, rq_queue); + req->rq_errno = -EIO; + list_del_init(&req->rq_queue); + smb_rput(req); + wake_up_interruptible(&req->rq_wait); + } + list_for_each_safe(tmp, n, &server->recvq) { + req = list_entry(tmp, struct smb_request, rq_queue); + req->rq_errno = -EIO; + list_del_init(&req->rq_queue); + smb_rput(req); + wake_up_interruptible(&req->rq_wait); + } +} + +/* + * Wake up smbmount and make it reconnect to the server. + * This must be called with the server locked. + * + * FIXME: add smbconnect version to this + */ +int smbiod_retry(struct smb_sb_info *server) +{ + struct list_head *head; + struct smb_request *req; + pid_t pid = server->conn_pid; + int result = 0; + + VERBOSE("state: %d\n", server->state); + if (server->state == CONN_VALID || server->state == CONN_RETRYING) + goto out; + + smb_invalidate_inodes(server); + + /* + * Some requests are meaningless after a retry, so we abort them. + * One example are all requests using 'fileid' since the files are + * closed on retry. + */ + head = server->xmitq.next; + while (head != &server->xmitq) { + req = list_entry(head, struct smb_request, rq_queue); + head = head->next; + + req->rq_bytes_sent = 0; + if (req->rq_flags & SMB_REQ_NORETRY) { + VERBOSE("aborting request %p on xmitq\n", req); + req->rq_errno = -EIO; + list_del_init(&req->rq_queue); + smb_rput(req); + wake_up_interruptible(&req->rq_wait); + } + } + + /* + * FIXME: test the code for retrying request we already sent + */ + head = server->recvq.next; + while (head != &server->recvq) { + req = list_entry(head, struct smb_request, rq_queue); + head = head->next; +#if 0 + if (req->rq_flags & SMB_REQ_RETRY) { + /* must move the request to the xmitq */ + VERBOSE("retrying request %p on recvq\n", req); + list_del(&req->rq_queue); + list_add(&req->rq_queue, &server->xmitq); + continue; + } +#endif + + VERBOSE("aborting request %p on recvq\n", req); + /* req->rq_rcls = ???; */ /* FIXME: set smb error code too? */ + req->rq_errno = -EIO; + list_del_init(&req->rq_queue); + smb_rput(req); + wake_up_interruptible(&req->rq_wait); + } + + smb_close_socket(server); + + if (pid == 0) { + /* FIXME: this is fatal, umount? */ + printk(KERN_ERR "smb_retry: no connection process\n"); + server->state = CONN_RETRIED; + goto out; + } + + /* + * Change state so that only one retry per server will be started. + */ + server->state = CONN_RETRYING; + + /* + * Note: use the "priv" flag, as a user process may need to reconnect. + */ + result = kill_proc(pid, SIGUSR1, 1); + if (result) { + /* FIXME: this is most likely fatal, umount? */ + printk(KERN_ERR "smb_retry: signal failed [%d]\n", result); + goto out; + } + VERBOSE("signalled pid %d\n", pid); + + /* FIXME: The retried requests should perhaps get a "time boost". */ + +out: + return result; +} + +/* + * Currently handles lockingX packets. + */ +static void smbiod_handle_request(struct smb_sb_info *server) +{ + PARANOIA("smbiod got a request ... and we don't implement oplocks!\n"); + server->rstate = SMB_RECV_DROP; +} + +/* + * Do some IO for one server. + */ +static void smbiod_doio(struct smb_sb_info *server) +{ + int result; + int maxwork = 7; + + if (server->state != CONN_VALID) + goto out; + + do { + result = smb_request_recv(server); + if (result < 0) { + server->state = CONN_INVALID; + smbiod_retry(server); + goto out; /* reconnecting is slow */ + } else if (server->rstate == SMB_RECV_REQUEST) + smbiod_handle_request(server); + } while (result > 0 && maxwork-- > 0); + + /* + * If there is more to read then we want to be sure to wake up again. + */ + if (server->state != CONN_VALID) + goto out; + if (smb_recv_available(server) > 0) + set_bit(SMBIOD_DATA_READY, &smbiod_flags); + + do { + result = smb_request_send_server(server); + if (result < 0) { + server->state = CONN_INVALID; + smbiod_retry(server); + goto out; /* reconnecting is slow */ + } + } while (result > 0); + + /* + * If the last request was not sent out we want to wake up again. + */ + if (!list_empty(&server->xmitq)) + set_bit(SMBIOD_DATA_READY, &smbiod_flags); + +out: + return; +} + +/* + * smbiod kernel thread + */ +static int smbiod(void *unused) +{ + daemonize("smbiod"); + + allow_signal(SIGKILL); + + VERBOSE("SMB Kernel thread starting (%d) ...\n", current->pid); + + for (;;) { + struct smb_sb_info *server; + struct list_head *pos, *n; + + /* FIXME: Use poll? */ + wait_event_interruptible(smbiod_wait, + test_bit(SMBIOD_DATA_READY, &smbiod_flags)); + if (signal_pending(current)) { + spin_lock(&servers_lock); + smbiod_state = SMBIOD_DEAD; + spin_unlock(&servers_lock); + break; + } + + clear_bit(SMBIOD_DATA_READY, &smbiod_flags); + + spin_lock(&servers_lock); + if (list_empty(&smb_servers)) { + smbiod_state = SMBIOD_DEAD; + spin_unlock(&servers_lock); + break; + } + + list_for_each_safe(pos, n, &smb_servers) { + server = list_entry(pos, struct smb_sb_info, entry); + VERBOSE("checking server %p\n", server); + + if (server->state == CONN_VALID) { + spin_unlock(&servers_lock); + + smb_lock_server(server); + smbiod_doio(server); + smb_unlock_server(server); + + spin_lock(&servers_lock); + } + } + spin_unlock(&servers_lock); + } + + VERBOSE("SMB Kernel thread exiting (%d) ...\n", current->pid); + module_put_and_exit(0); +} diff --git a/fs/smbfs/sock.c b/fs/smbfs/sock.c new file mode 100644 index 00000000000..93f3cd22a2e --- /dev/null +++ b/fs/smbfs/sock.c @@ -0,0 +1,388 @@ +/* + * sock.c + * + * Copyright (C) 1995, 1996 by Paal-Kr. Engstad and Volker Lendecke + * Copyright (C) 1997 by Volker Lendecke + * + * Please add a note about your changes to smbfs in the ChangeLog file. + */ + +#include <linux/fs.h> +#include <linux/time.h> +#include <linux/errno.h> +#include <linux/socket.h> +#include <linux/fcntl.h> +#include <linux/file.h> +#include <linux/in.h> +#include <linux/net.h> +#include <linux/tcp.h> +#include <linux/mm.h> +#include <linux/netdevice.h> +#include <linux/smp_lock.h> +#include <linux/workqueue.h> +#include <net/scm.h> +#include <net/ip.h> + +#include <linux/smb_fs.h> +#include <linux/smb.h> +#include <linux/smbno.h> + +#include <asm/uaccess.h> +#include <asm/ioctls.h> + +#include "smb_debug.h" +#include "proto.h" +#include "request.h" + + +static int +_recvfrom(struct socket *socket, unsigned char *ubuf, int size, unsigned flags) +{ + struct kvec iov = {ubuf, size}; + struct msghdr msg = {.msg_flags = flags}; + msg.msg_flags |= MSG_DONTWAIT | MSG_NOSIGNAL; + return kernel_recvmsg(socket, &msg, &iov, 1, size, msg.msg_flags); +} + +/* + * Return the server this socket belongs to + */ +static struct smb_sb_info * +server_from_socket(struct socket *socket) +{ + return socket->sk->sk_user_data; +} + +/* + * Called when there is data on the socket. + */ +void +smb_data_ready(struct sock *sk, int len) +{ + struct smb_sb_info *server = server_from_socket(sk->sk_socket); + void (*data_ready)(struct sock *, int) = server->data_ready; + + data_ready(sk, len); + VERBOSE("(%p, %d)\n", sk, len); + smbiod_wake_up(); +} + +int +smb_valid_socket(struct inode * inode) +{ + return (inode && S_ISSOCK(inode->i_mode) && + SOCKET_I(inode)->type == SOCK_STREAM); +} + +static struct socket * +server_sock(struct smb_sb_info *server) +{ + struct file *file; + + if (server && (file = server->sock_file)) + { +#ifdef SMBFS_PARANOIA + if (!smb_valid_socket(file->f_dentry->d_inode)) + PARANOIA("bad socket!\n"); +#endif + return SOCKET_I(file->f_dentry->d_inode); + } + return NULL; +} + +void +smb_close_socket(struct smb_sb_info *server) +{ + struct file * file = server->sock_file; + + if (file) { + struct socket *sock = server_sock(server); + + VERBOSE("closing socket %p\n", sock); + sock->sk->sk_data_ready = server->data_ready; + server->sock_file = NULL; + fput(file); + } +} + +static int +smb_get_length(struct socket *socket, unsigned char *header) +{ + int result; + + result = _recvfrom(socket, header, 4, MSG_PEEK); + if (result == -EAGAIN) + return -ENODATA; + if (result < 0) { + PARANOIA("recv error = %d\n", -result); + return result; + } + if (result < 4) + return -ENODATA; + + switch (header[0]) { + case 0x00: + case 0x82: + break; + + case 0x85: + DEBUG1("Got SESSION KEEP ALIVE\n"); + _recvfrom(socket, header, 4, 0); /* read away */ + return -ENODATA; + + default: + PARANOIA("Invalid NBT packet, code=%x\n", header[0]); + return -EIO; + } + + /* The length in the RFC NB header is the raw data length */ + return smb_len(header); +} + +int +smb_recv_available(struct smb_sb_info *server) +{ + mm_segment_t oldfs; + int avail, err; + struct socket *sock = server_sock(server); + + oldfs = get_fs(); + set_fs(get_ds()); + err = sock->ops->ioctl(sock, SIOCINQ, (unsigned long) &avail); + set_fs(oldfs); + return (err >= 0) ? avail : err; +} + +/* + * Adjust the kvec to move on 'n' bytes (from nfs/sunrpc) + */ +static int +smb_move_iov(struct kvec **data, size_t *num, struct kvec *vec, unsigned amount) +{ + struct kvec *iv = *data; + int i; + int len; + + /* + * Eat any sent kvecs + */ + while (iv->iov_len <= amount) { + amount -= iv->iov_len; + iv++; + (*num)--; + } + + /* + * And chew down the partial one + */ + vec[0].iov_len = iv->iov_len-amount; + vec[0].iov_base =((unsigned char *)iv->iov_base)+amount; + iv++; + + len = vec[0].iov_len; + + /* + * And copy any others + */ + for (i = 1; i < *num; i++) { + vec[i] = *iv++; + len += vec[i].iov_len; + } + + *data = vec; + return len; +} + +/* + * smb_receive_header + * Only called by the smbiod thread. + */ +int +smb_receive_header(struct smb_sb_info *server) +{ + struct socket *sock; + int result = 0; + unsigned char peek_buf[4]; + + result = -EIO; + sock = server_sock(server); + if (!sock) + goto out; + if (sock->sk->sk_state != TCP_ESTABLISHED) + goto out; + + if (!server->smb_read) { + result = smb_get_length(sock, peek_buf); + if (result < 0) { + if (result == -ENODATA) + result = 0; + goto out; + } + server->smb_len = result + 4; + + if (server->smb_len < SMB_HEADER_LEN) { + PARANOIA("short packet: %d\n", result); + server->rstate = SMB_RECV_DROP; + result = -EIO; + goto out; + } + if (server->smb_len > SMB_MAX_PACKET_SIZE) { + PARANOIA("long packet: %d\n", result); + server->rstate = SMB_RECV_DROP; + result = -EIO; + goto out; + } + } + + result = _recvfrom(sock, server->header + server->smb_read, + SMB_HEADER_LEN - server->smb_read, 0); + VERBOSE("_recvfrom: %d\n", result); + if (result < 0) { + VERBOSE("receive error: %d\n", result); + goto out; + } + server->smb_read += result; + + if (server->smb_read == SMB_HEADER_LEN) + server->rstate = SMB_RECV_HCOMPLETE; +out: + return result; +} + +static char drop_buffer[PAGE_SIZE]; + +/* + * smb_receive_drop - read and throw away the data + * Only called by the smbiod thread. + * + * FIXME: we are in the kernel, could we just tell the socket that we want + * to drop stuff from the buffer? + */ +int +smb_receive_drop(struct smb_sb_info *server) +{ + struct socket *sock; + unsigned int flags; + struct kvec iov; + struct msghdr msg; + int rlen = smb_len(server->header) - server->smb_read + 4; + int result = -EIO; + + if (rlen > PAGE_SIZE) + rlen = PAGE_SIZE; + + sock = server_sock(server); + if (!sock) + goto out; + if (sock->sk->sk_state != TCP_ESTABLISHED) + goto out; + + flags = MSG_DONTWAIT | MSG_NOSIGNAL; + iov.iov_base = drop_buffer; + iov.iov_len = PAGE_SIZE; + msg.msg_flags = flags; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_control = NULL; + + result = kernel_recvmsg(sock, &msg, &iov, 1, rlen, flags); + + VERBOSE("read: %d\n", result); + if (result < 0) { + VERBOSE("receive error: %d\n", result); + goto out; + } + server->smb_read += result; + + if (server->smb_read >= server->smb_len) + server->rstate = SMB_RECV_END; + +out: + return result; +} + +/* + * smb_receive + * Only called by the smbiod thread. + */ +int +smb_receive(struct smb_sb_info *server, struct smb_request *req) +{ + struct socket *sock; + unsigned int flags; + struct kvec iov[4]; + struct kvec *p = req->rq_iov; + size_t num = req->rq_iovlen; + struct msghdr msg; + int rlen; + int result = -EIO; + + sock = server_sock(server); + if (!sock) + goto out; + if (sock->sk->sk_state != TCP_ESTABLISHED) + goto out; + + flags = MSG_DONTWAIT | MSG_NOSIGNAL; + msg.msg_flags = flags; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_control = NULL; + + /* Dont repeat bytes and count available bufferspace */ + rlen = smb_move_iov(&p, &num, iov, req->rq_bytes_recvd); + if (req->rq_rlen < rlen) + rlen = req->rq_rlen; + + result = kernel_recvmsg(sock, &msg, p, num, rlen, flags); + + VERBOSE("read: %d\n", result); + if (result < 0) { + VERBOSE("receive error: %d\n", result); + goto out; + } + req->rq_bytes_recvd += result; + server->smb_read += result; + +out: + return result; +} + +/* + * Try to send a SMB request. This may return after sending only parts of the + * request. SMB_REQ_TRANSMITTED will be set if a request was fully sent. + * + * Parts of this was taken from xprt_sendmsg from net/sunrpc/xprt.c + */ +int +smb_send_request(struct smb_request *req) +{ + struct smb_sb_info *server = req->rq_server; + struct socket *sock; + struct msghdr msg = {.msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT}; + int slen = req->rq_slen - req->rq_bytes_sent; + int result = -EIO; + struct kvec iov[4]; + struct kvec *p = req->rq_iov; + size_t num = req->rq_iovlen; + + sock = server_sock(server); + if (!sock) + goto out; + if (sock->sk->sk_state != TCP_ESTABLISHED) + goto out; + + /* Dont repeat bytes */ + if (req->rq_bytes_sent) + smb_move_iov(&p, &num, iov, req->rq_bytes_sent); + + result = kernel_sendmsg(sock, &msg, p, num, slen); + + if (result >= 0) { + req->rq_bytes_sent += result; + if (req->rq_bytes_sent >= req->rq_slen) + req->rq_flags |= SMB_REQ_TRANSMITTED; + } +out: + return result; +} diff --git a/fs/smbfs/symlink.c b/fs/smbfs/symlink.c new file mode 100644 index 00000000000..8b069e06433 --- /dev/null +++ b/fs/smbfs/symlink.c @@ -0,0 +1,70 @@ +/* + * symlink.c + * + * Copyright (C) 2002 by John Newbigin + * + * Please add a note about your changes to smbfs in the ChangeLog file. + */ + +#include <linux/sched.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/pagemap.h> +#include <linux/smp_lock.h> +#include <linux/net.h> +#include <linux/namei.h> + +#include <asm/uaccess.h> +#include <asm/system.h> + +#include <linux/smbno.h> +#include <linux/smb_fs.h> + +#include "smb_debug.h" +#include "proto.h" + +int smb_symlink(struct inode *inode, struct dentry *dentry, const char *oldname) +{ + DEBUG1("create symlink %s -> %s/%s\n", oldname, DENTRY_PATH(dentry)); + + return smb_proc_symlink(server_from_dentry(dentry), dentry, oldname); +} + +static int smb_follow_link(struct dentry *dentry, struct nameidata *nd) +{ + char *link = __getname(); + DEBUG1("followlink of %s/%s\n", DENTRY_PATH(dentry)); + + if (!link) { + link = ERR_PTR(-ENOMEM); + } else { + int len = smb_proc_read_link(server_from_dentry(dentry), + dentry, link, PATH_MAX - 1); + if (len < 0) { + putname(link); + link = ERR_PTR(len); + } else { + link[len] = 0; + } + } + nd_set_link(nd, link); + return 0; +} + +static void smb_put_link(struct dentry *dentry, struct nameidata *nd) +{ + char *s = nd_get_link(nd); + if (!IS_ERR(s)) + putname(s); +} + +struct inode_operations smb_link_inode_operations = +{ + .readlink = generic_readlink, + .follow_link = smb_follow_link, + .put_link = smb_put_link, +}; |