add idl4k kernel firmware version 1.13.0.105

This commit is contained in:
Jaroslav Kysela
2015-03-26 17:22:37 +01:00
parent 5194d2792e
commit e9070cdc77
31064 changed files with 12769984 additions and 0 deletions

55
kernel/fs/smbfs/Kconfig Normal file
View File

@@ -0,0 +1,55 @@
config SMB_FS
tristate "SMB file system support (OBSOLETE, please use CIFS)"
depends on INET
select NLS
help
SMB (Server Message Block) is the protocol Windows for Workgroups
(WfW), Windows 95/98, Windows NT and OS/2 Lan Manager use to share
files and printers over local networks. Saying Y here allows you to
mount their file systems (often called "shares" in this context) and
access them just like any other Unix directory. Currently, this
works only if the Windows machines use TCP/IP as the underlying
transport protocol, and not NetBEUI. For details, read
<file:Documentation/filesystems/smbfs.txt> and the SMB-HOWTO,
available from <http://www.tldp.org/docs.html#howto>.
Note: if you just want your box to act as an SMB *server* and make
files and printing services available to Windows clients (which need
to have a TCP/IP stack), you don't need to say Y here; you can use
the program SAMBA (available from <ftp://ftp.samba.org/pub/samba/>)
for that.
General information about how to connect Linux, Windows machines and
Macs is on the WWW at <http://www.eats.com/linux_mac_win.html>.
To compile the SMB support as a module, choose M here:
the module will be called smbfs. Most people say N, however.
config SMB_NLS_DEFAULT
bool "Use a default NLS"
depends on SMB_FS
help
Enabling this will make smbfs use nls translations by default. You
need to specify the local charset (CONFIG_NLS_DEFAULT) in the nls
settings and you need to give the default nls for the SMB server as
CONFIG_SMB_NLS_REMOTE.
The nls settings can be changed at mount time, if your smbmount
supports that, using the codepage and iocharset parameters.
smbmount from samba 2.2.0 or later supports this.
config SMB_NLS_REMOTE
string "Default Remote NLS Option"
depends on SMB_NLS_DEFAULT
default "cp437"
help
This setting allows you to specify a default value for which
codepage the server uses. If this field is left blank no
translations will be done by default. The local codepage/charset
default to CONFIG_NLS_DEFAULT.
The nls settings can be changed at mount time, if your smbmount
supports that, using the codepage and iocharset parameters.
smbmount from samba 2.2.0 or later supports this.

18
kernel/fs/smbfs/Makefile Normal file
View File

@@ -0,0 +1,18 @@
#
# 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_TIMESTAMP
#EXTRA_CFLAGS += -Werror

208
kernel/fs/smbfs/cache.c Normal file
View File

@@ -0,0 +1,208 @@
/*
* 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/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_u.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_u.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_path.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);
}

701
kernel/fs/smbfs/dir.c Normal file
View File

@@ -0,0 +1,701 @@
/*
* 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/sched.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 *);
const struct file_operations smb_dir_operations =
{
.read = generic_read_dir,
.readdir = smb_readdir,
.ioctl = smb_ioctl,
.open = smb_dir_open,
};
const 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,
};
const 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_path.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 (result == -ERESTARTSYS && page)
ClearPageUptodate(page);
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);
if (result != -ERESTARTSYS)
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_path.dentry;
struct smb_sb_info *server;
int error = 0;
VERBOSE("(%s/%s)\n", dentry->d_parent->d_name.name,
file->f_path.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 const 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 const 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;
/* Do not allow lookup of names with backslashes in */
error = -EINVAL;
if (memchr(dentry->d_name.name, '\\', dentry->d_name.len))
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;
current_euid_egid(&attr.ia_uid, &attr.ia_gid);
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;
}

454
kernel/fs/smbfs/file.c Normal file
View File

@@ -0,0 +1,454 @@
/*
* 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 <linux/aio.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_path.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;
BUG_ON(!mapping);
inode = mapping->host;
BUG_ON(!inode);
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_path.dentry;
DEBUG1("(%s/%s %d@%lld)\n", DENTRY_PATH(dentry), count,
((unsigned long long)page->index << PAGE_CACHE_SHIFT) + offset);
return smb_writepage_sync(dentry->d_inode, page, offset, count);
}
static ssize_t
smb_file_aio_read(struct kiocb *iocb, const struct iovec *iov,
unsigned long nr_segs, loff_t pos)
{
struct file * file = iocb->ki_filp;
struct dentry * dentry = file->f_path.dentry;
ssize_t status;
VERBOSE("file %s/%s, count=%lu@%lu\n", DENTRY_PATH(dentry),
(unsigned long) iocb->ki_left, (unsigned long) pos);
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.tv_sec);
status = generic_file_aio_read(iocb, iov, nr_segs, pos);
out:
return status;
}
static int
smb_file_mmap(struct file * file, struct vm_area_struct * vma)
{
struct dentry * dentry = file->f_path.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_splice_read(struct file *file, loff_t *ppos,
struct pipe_inode_info *pipe, size_t count,
unsigned int flags)
{
struct dentry *dentry = file->f_path.dentry;
ssize_t status;
VERBOSE("file %s/%s, pos=%Ld, count=%lu\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_splice_read(file, ppos, pipe, count, flags);
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_write_begin(struct file *file, struct address_space *mapping,
loff_t pos, unsigned len, unsigned flags,
struct page **pagep, void **fsdata)
{
pgoff_t index = pos >> PAGE_CACHE_SHIFT;
*pagep = grab_cache_page_write_begin(mapping, index, flags);
if (!*pagep)
return -ENOMEM;
return 0;
}
static int smb_write_end(struct file *file, struct address_space *mapping,
loff_t pos, unsigned len, unsigned copied,
struct page *page, void *fsdata)
{
int status;
unsigned offset = pos & (PAGE_CACHE_SIZE - 1);
lock_kernel();
status = smb_updatepage(file, page, offset, copied);
unlock_kernel();
if (!status) {
if (!PageUptodate(page) && copied == PAGE_CACHE_SIZE)
SetPageUptodate(page);
status = copied;
}
unlock_page(page);
page_cache_release(page);
return status;
}
const struct address_space_operations smb_file_aops = {
.readpage = smb_readpage,
.writepage = smb_writepage,
.write_begin = smb_write_begin,
.write_end = smb_write_end,
};
/*
* Write to a file (through the page cache).
*/
static ssize_t
smb_file_aio_write(struct kiocb *iocb, const struct iovec *iov,
unsigned long nr_segs, loff_t pos)
{
struct file * file = iocb->ki_filp;
struct dentry * dentry = file->f_path.dentry;
ssize_t result;
VERBOSE("file %s/%s, count=%lu@%lu\n",
DENTRY_PATH(dentry),
(unsigned long) iocb->ki_left, (unsigned long) pos);
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 (iocb->ki_left > 0) {
result = generic_file_aio_write(iocb, iov, nr_segs, pos);
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.tv_sec,
dentry->d_inode->i_atime.tv_sec);
}
out:
return result;
}
static int
smb_file_open(struct inode *inode, struct file * file)
{
int result;
struct dentry *dentry = file->f_path.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_write_and_wait(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)
{
int mode = inode->i_mode;
int error = 0;
VERBOSE("mode=%x, mask=%x\n", mode, mask);
/* Look at user permissions */
mode >>= 6;
if (mask & ~mode & (MAY_READ | MAY_WRITE | MAY_EXEC))
error = -EACCES;
return error;
}
static loff_t smb_remote_llseek(struct file *file, loff_t offset, int origin)
{
loff_t ret;
lock_kernel();
ret = generic_file_llseek_unlocked(file, offset, origin);
unlock_kernel();
return ret;
}
const struct file_operations smb_file_operations =
{
.llseek = smb_remote_llseek,
.read = do_sync_read,
.aio_read = smb_file_aio_read,
.write = do_sync_write,
.aio_write = smb_file_aio_write,
.ioctl = smb_ioctl,
.mmap = smb_file_mmap,
.open = smb_file_open,
.release = smb_file_release,
.fsync = smb_fsync,
.splice_read = smb_file_splice_read,
};
const struct inode_operations smb_file_inode_operations =
{
.permission = smb_file_permission,
.getattr = smb_getattr,
.setattr = smb_notify_change,
};

64
kernel/fs/smbfs/getopt.c Normal file
View File

@@ -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;
}

14
kernel/fs/smbfs/getopt.h Normal file
View File

@@ -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 */

833
kernel/fs/smbfs/inode.c Normal file
View File

@@ -0,0 +1,833 @@
/*
* 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/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/sched.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 dentry *, struct kstatfs *);
static int smb_show_options(struct seq_file *, struct vfsmount *);
static struct kmem_cache *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, GFP_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)
{
struct smb_inode_info *ei = (struct smb_inode_info *) foo;
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|
SLAB_MEM_SPREAD),
init_once);
if (smb_inode_cachep == NULL)
return -ENOMEM;
return 0;
}
static void destroy_inodecache(void)
{
kmem_cache_destroy(smb_inode_cachep);
}
static int smb_remount(struct super_block *sb, int *flags, char *data)
{
*flags |= MS_NODIRATIME;
return 0;
}
static const 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_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_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.tv_sec,
(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);
truncate_inode_pages(&ino->i_data, 0);
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)
{
unload_nls(server->remote_nls);
unload_nls(server->local_nls);
}
static void
smb_put_super(struct super_block *sb)
{
struct smb_sb_info *server = SMB_SB(sb);
lock_kernel();
smb_lock_server(server);
server->state = CONN_INVALID;
smbiod_unregister_server(server);
smb_close_socket(server);
if (server->conn_pid)
kill_pid(server->conn_pid, SIGTERM, 1);
kfree(server->ops);
smb_unload_nls(server);
sb->s_fs_info = NULL;
smb_unlock_server(server);
put_pid(server->conn_pid);
kfree(server);
unlock_kernel();
}
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;
static int warn_count;
if (warn_count < 5) {
warn_count++;
printk(KERN_EMERG "smbfs is deprecated and will be removed"
" from the 2.6.27 kernel. Please migrate to cifs\n");
}
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 = kzalloc(sizeof(struct smb_sb_info), GFP_KERNEL);
if (!server)
goto out_no_server;
sb->s_fs_info = server;
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 = NULL;
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 = %lu\n", sizeof(struct smb_ops) +
sizeof(struct smb_mount_data_kernel));
mem = 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:
kfree(mem);
out_no_mem:
if (!server->mnt)
printk(KERN_ERR "smb_fill_super: allocation failure\n");
sb->s_fs_info = NULL;
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 dentry *dentry, struct kstatfs *buf)
{
int result;
lock_kernel();
result = smb_proc_dskattr(dentry, 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_write_and_wait(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;
}
static int smb_get_sb(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data, struct vfsmount *mnt)
{
return get_sb_nodev(fs_type, flags, data, smb_fill_super, mnt);
}
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");
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();
}
module_init(init_smb_fs)
module_exit(exit_smb_fs)
MODULE_LICENSE("GPL");

67
kernel/fs/smbfs/ioctl.c Normal file
View File

@@ -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;
}

3507
kernel/fs/smbfs/proc.c Normal file

File diff suppressed because it is too large Load Diff

87
kernel/fs/smbfs/proto.h Normal file
View File

@@ -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 dentry *dentry, 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 const struct file_operations smb_dir_operations;
extern const struct inode_operations smb_dir_inode_operations;
extern const 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 const struct address_space_operations smb_file_aops;
extern const struct file_operations smb_file_operations;
extern const 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 const struct inode_operations smb_link_inode_operations;

818
kernel/fs/smbfs/request.c Normal file
View File

@@ -0,0 +1,818 @@
/*
* request.c
*
* Copyright (C) 2001 by Urban Widmark
*
* Please add a note about your changes to smbfs in the ChangeLog file.
*/
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/net.h>
#include <linux/sched.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
/* cache for request structures */
static struct kmem_cache *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);
if (req_cachep == NULL)
return -ENOMEM;
return 0;
}
void smb_destroy_request_cache(void)
{
kmem_cache_destroy(req_cachep);
}
/*
* 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_zalloc(req_cachep, GFP_KERNEL);
VERBOSE("allocating request: %p\n", req);
if (!req)
goto out;
if (bufsize > 0) {
buf = kmalloc(bufsize, GFP_NOFS);
if (!buf) {
kmem_cache_free(req_cachep, req);
return NULL;
}
}
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 (fatal_signal_pending(current))
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))
kfree(req->rq_buffer);
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;
kfree(req->rq_trans2buffer);
req->rq_trans2buffer = NULL;
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 = ALIGN(header + 3, sizeof(u32));
const int odata = ALIGN(oparam + req->rq_lparm, sizeof(u32));
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.
* First check if the request is still part of a queue. (May
* have been removed by some error condition)
*/
smb_lock_server(server);
if (!list_empty(&req->rq_queue)) {
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_move_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_move(&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 = kzalloc(buf_len, GFP_NOFS);
if (!req->rq_trans2buffer)
goto out_no_mem;
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:
BUG();
}
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;
}

70
kernel/fs/smbfs/request.h Normal file
View File

@@ -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 */

View File

@@ -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, __func__ , ## 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, __func__ , ## 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, __func__ , ## a)
#else
#define DEBUG1(f, a...) do { ; } while(0)
#endif

345
kernel/fs/smbfs/smbiod.c Normal file
View File

@@ -0,0 +1,345 @@
/*
* smbiod.c
*
* Copyright (C) 2000, Charles Loep / Corel Corp.
* Copyright (C) 2001, Urban Widmark
*/
#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/module.h>
#include <linux/net.h>
#include <linux/kthread.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 struct task_struct *smbiod_thread;
static DECLARE_WAIT_QUEUE_HEAD(smbiod_wait);
static LIST_HEAD(smb_servers);
static DEFINE_SPINLOCK(servers_lock);
#define SMBIOD_DATA_READY (1<<0)
static unsigned 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)
{
struct task_struct *tsk;
int err = 0;
if (smbiod_state != SMBIOD_DEAD)
return 0;
smbiod_state = SMBIOD_STARTING;
__module_get(THIS_MODULE);
spin_unlock(&servers_lock);
tsk = kthread_run(smbiod, NULL, "smbiod");
if (IS_ERR(tsk)) {
err = PTR_ERR(tsk);
module_put(THIS_MODULE);
}
spin_lock(&servers_lock);
if (err < 0) {
smbiod_state = SMBIOD_DEAD;
smbiod_thread = NULL;
} else {
smbiod_state = SMBIOD_RUNNING;
smbiod_thread = tsk;
}
return err;
}
/*
* 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;
struct pid *pid = get_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_move(&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) {
/* 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_pid(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_nr(pid));
/* FIXME: The retried requests should perhaps get a "time boost". */
out:
put_pid(pid);
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)
{
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);
}

386
kernel/fs/smbfs/sock.c Normal file
View File

@@ -0,0 +1,386 @@
/*
* 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/mm.h>
#include <linux/netdevice.h>
#include <linux/workqueue.h>
#include <net/scm.h>
#include <net/tcp_states.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_path.dentry->d_inode))
PARANOIA("bad socket!\n");
#endif
return SOCKET_I(file->f_path.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 = min_t(int, smb_move_iov(&p, &num, iov, req->rq_bytes_recvd),
(req->rq_rlen - req->rq_bytes_recvd));
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;
}

68
kernel/fs/smbfs/symlink.c Normal file
View File

@@ -0,0 +1,68 @@
/*
* symlink.c
*
* Copyright (C) 2002 by John Newbigin
*
* Please add a note about your changes to smbfs in the ChangeLog file.
*/
#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/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 void *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 NULL;
}
static void smb_put_link(struct dentry *dentry, struct nameidata *nd, void *p)
{
char *s = nd_get_link(nd);
if (!IS_ERR(s))
__putname(s);
}
const struct inode_operations smb_link_inode_operations =
{
.readlink = generic_readlink,
.follow_link = smb_follow_link,
.put_link = smb_put_link,
};