diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1997-01-07 02:33:00 +0000 |
---|---|---|
committer | <ralf@linux-mips.org> | 1997-01-07 02:33:00 +0000 |
commit | beb116954b9b7f3bb56412b2494b562f02b864b1 (patch) | |
tree | 120e997879884e1b9d93b265221b939d2ef1ade1 /fs/smbfs | |
parent | 908d4681a1dc3792ecafbe64265783a86c4cccb6 (diff) |
Import of Linux/MIPS 2.1.14
Diffstat (limited to 'fs/smbfs')
-rw-r--r-- | fs/smbfs/Makefile | 18 | ||||
-rw-r--r-- | fs/smbfs/dir.c | 846 | ||||
-rw-r--r-- | fs/smbfs/file.c | 244 | ||||
-rw-r--r-- | fs/smbfs/inode.c | 474 | ||||
-rw-r--r-- | fs/smbfs/ioctl.c | 29 | ||||
-rw-r--r-- | fs/smbfs/mmap.c | 126 | ||||
-rw-r--r-- | fs/smbfs/proc.c | 1969 | ||||
-rw-r--r-- | fs/smbfs/sock.c | 714 |
8 files changed, 4420 insertions, 0 deletions
diff --git a/fs/smbfs/Makefile b/fs/smbfs/Makefile new file mode 100644 index 000000000..0210be26b --- /dev/null +++ b/fs/smbfs/Makefile @@ -0,0 +1,18 @@ +# +# Makefile for the linux smb-filesystem routines. +# +# Note! Dependencies are done automagically by 'make dep', which also +# removes any old dependencies. DON'T put your own dependencies here +# unless it's something special (ie not a .c file). +# +# Note 2! The CFLAGS definitions are now in the main makefile... + +O_TARGET := smbfs.o +O_OBJS := proc.o sock.o inode.o file.o dir.o ioctl.o mmap.o +M_OBJS := $(O_TARGET) + +# If you want debugging output, please uncomment the following line + +# EXTRA_CFLAGS += -DDEBUG_SMB=1 -DDEBUG_SMB_MALLOC=1 + +include $(TOPDIR)/Rules.make diff --git a/fs/smbfs/dir.c b/fs/smbfs/dir.c new file mode 100644 index 000000000..a07d13517 --- /dev/null +++ b/fs/smbfs/dir.c @@ -0,0 +1,846 @@ +/* + * dir.c + * + * Copyright (C) 1995, 1996 by Paal-Kr. Engstad and Volker Lendecke + * + */ + +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/stat.h> +#include <linux/kernel.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#include <linux/smb_fs.h> +#include <linux/smbno.h> +#include <linux/errno.h> + +#include <asm/uaccess.h> +#include <asm/semaphore.h> + +static long + smb_dir_read(struct inode *inode, struct file *filp, char *buf, unsigned long count); + +static int + smb_readdir(struct inode *inode, struct file *filp, + void *dirent, filldir_t filldir); + +static struct smb_inode_info * + smb_find_dir_inode(struct inode *parent, const char *name, int len); + +static int + smb_lookup(struct inode *dir, const char *__name, + int len, struct inode **result); + +static int + smb_create(struct inode *dir, const char *name, int len, int mode, + struct inode **result); + +static int + smb_mkdir(struct inode *dir, const char *name, int len, int mode); + +static int + smb_rmdir(struct inode *dir, const char *name, int len); + +static int + smb_unlink(struct inode *dir, const char *name, int len); + +static int + smb_rename(struct inode *old_dir, const char *old_name, int old_len, + struct inode *new_dir, const char *new_name, int new_len, + int must_be_dir); + +static struct file_operations smb_dir_operations = +{ + NULL, /* lseek - default */ + smb_dir_read, /* read - bad */ + NULL, /* write - bad */ + smb_readdir, /* readdir */ + NULL, /* select - default */ + smb_ioctl, /* ioctl - default */ + NULL, /* mmap */ + NULL, /* no special open code */ + NULL, /* no special release code */ + NULL /* fsync */ +}; + +struct inode_operations smb_dir_inode_operations = +{ + &smb_dir_operations, /* default directory file ops */ + smb_create, /* create */ + smb_lookup, /* lookup */ + NULL, /* link */ + smb_unlink, /* unlink */ + NULL, /* symlink */ + smb_mkdir, /* mkdir */ + smb_rmdir, /* rmdir */ + NULL, /* mknod */ + smb_rename, /* rename */ + NULL, /* readlink */ + NULL, /* follow_link */ + NULL, /* readpage */ + NULL, /* writepage */ + NULL, /* bmap */ + NULL, /* truncate */ + NULL, /* permission */ + NULL /* smap */ +}; + +static int +strncasecmp(const char *s1, const char *s2, int len) +{ + int result = 0; + + for (; len > 0; len -= 1) + { + char c1, c2; + + c1 = (*s1 >= 'a' && *s1 <= 'z') ? *s1 - ('a' - 'A') : *s1; + c2 = (*s2 >= 'a' && *s2 <= 'z') ? *s2 - ('a' - 'A') : *s2; + s1 += 1; + s2 += 1; + + if ((result = c1 - c2) != 0 || c1 == 0) + { + return result; + } + } + return result; +} + +static int +compare_filename(const struct smb_server *server, + const char *s1, int len, struct smb_dirent *entry) +{ + if (len != entry->len) + { + return 1; + } + if (server->case_handling == CASE_DEFAULT) + { + return strncasecmp(s1, entry->name, len); + } + return strncmp(s1, entry->name, len); +} + +struct smb_inode_info * +smb_find_inode(struct smb_server *server, ino_t ino) +{ + struct smb_inode_info *root = &(server->root); + struct smb_inode_info *this = root; + + do + { + if (ino == smb_info_ino(this)) + { + return this; + } + this = this->next; + } + while (this != root); + + return NULL; +} + +static ino_t +smb_fresh_inodes(struct smb_server *server, int no) +{ + static ino_t seed = 1; + struct smb_inode_info *root = &(server->root); + struct smb_inode_info *this; + + retry: + if (seed + no <= no) + { + /* avoid inode number of 0 at wrap-around */ + seed += no; + } + this = root; + do + { + /* We assume that ino_t is unsigned! */ + if (this->finfo.f_ino - seed < no) + { + seed += no; + goto retry; + } + this = this->next; + } + while (this != root); + + seed += no; + + return seed - no; +} + +static long +smb_dir_read(struct inode *inode, struct file *filp, char *buf, unsigned long count) +{ + return -EISDIR; +} + + +static unsigned long c_ino = 0; +static kdev_t c_dev; +static int c_size; +static int c_seen_eof; +static int c_last_returned_index; +static struct smb_dirent *c_entry = NULL; + +static struct smb_dirent * +smb_search_in_cache(struct inode *dir, unsigned long f_pos) +{ + int i; + + if ((dir->i_dev != c_dev) || (dir->i_ino != c_ino)) + { + return NULL; + } + for (i = 0; i < c_size; i++) + { + if (f_pos == c_entry[i].f_pos) + { + c_last_returned_index = i; + return &(c_entry[i]); + } + } + return NULL; +} + +static int +smb_refill_dir_cache(struct smb_server *server, struct inode *dir, + unsigned long f_pos) +{ + int result; + static struct semaphore sem = MUTEX; + int i; + ino_t ino; + + do + { + down(&sem); + result = smb_proc_readdir(server, dir, f_pos, + SMB_READDIR_CACHE_SIZE, c_entry); + + if (result <= 0) + { + smb_invalid_dir_cache(dir->i_ino); + up(&sem); + return result; + } + c_seen_eof = (result < SMB_READDIR_CACHE_SIZE); + c_dev = dir->i_dev; + c_ino = dir->i_ino; + c_size = result; + c_last_returned_index = 0; + + ino = smb_fresh_inodes(server, c_size); + for (i = 0; i < c_size; i++) + { + c_entry[i].f_ino = ino; + ino += 1; + } + + up(&sem); + + } + while ((c_dev != dir->i_dev) || (c_ino != dir->i_ino)); + + return result; +} + +static int +smb_readdir(struct inode *dir, struct file *filp, + void *dirent, filldir_t filldir) +{ + int result, i = 0; + struct smb_dirent *entry = NULL; + struct smb_server *server = SMB_SERVER(dir); + + DPRINTK("smb_readdir: filp->f_pos = %d\n", (int) filp->f_pos); + DDPRINTK("smb_readdir: dir->i_ino = %ld, c_ino = %ld\n", + dir->i_ino, c_ino); + + if ((dir == NULL) || !S_ISDIR(dir->i_mode)) + { + printk("smb_readdir: dir is NULL or not a directory\n"); + return -EBADF; + } + if (c_entry == NULL) + { + i = sizeof(struct smb_dirent) * SMB_READDIR_CACHE_SIZE; + c_entry = (struct smb_dirent *) smb_vmalloc(i); + if (c_entry == NULL) + { + printk("smb_readdir: no MEMORY for cache\n"); + return -ENOMEM; + } + } + if (filp->f_pos == 0) + { + c_ino = 0; + c_dev = 0; + c_seen_eof = 0; + + if (filldir(dirent, ".", 1, filp->f_pos, + smb_info_ino(SMB_INOP(dir))) < 0) + { + return 0; + } + filp->f_pos += 1; + } + if (filp->f_pos == 1) + { + if (filldir(dirent, "..", 2, filp->f_pos, + smb_info_ino(SMB_INOP(dir)->dir)) < 0) + { + return 0; + } + filp->f_pos += 1; + } + entry = smb_search_in_cache(dir, filp->f_pos); + + if (entry == NULL) + { + if (c_seen_eof) + { + /* End of directory */ + return 0; + } + result = smb_refill_dir_cache(server, dir, filp->f_pos); + if (result <= 0) + { + return result; + } + entry = c_entry; + } + while (entry < &(c_entry[c_size])) + { + /* We found it. For getwd(), we have to return the + correct inode in d_ino if the inode is currently in + use. Otherwise the inode number does not + matter. (You can argue a lot about this..) */ + + struct smb_inode_info *ino_info + = smb_find_dir_inode(dir, entry->name, entry->len); + + ino_t ino = entry->f_ino; + + if (ino_info != NULL) + { + ino = smb_info_ino(ino_info); + } + DDPRINTK("smb_readdir: entry->name = %s\n", entry->name); + DDPRINTK("smb_readdir: entry->f_pos = %ld\n", entry->f_pos); + + if (filldir(dirent, entry->name, strlen(entry->name), + entry->f_pos, ino) < 0) + { + break; + } + if ((dir->i_dev != c_dev) || (dir->i_ino != c_ino) + || (entry->f_pos != filp->f_pos)) + { + /* Someone has destroyed the cache while we slept + in filldir */ + break; + } + filp->f_pos += 1; + entry += 1; + } + return 0; +} + +void +smb_init_dir_cache(void) +{ + c_ino = 0; + c_dev = 0; + c_entry = NULL; +} + +void +smb_invalid_dir_cache(unsigned long ino) +{ + /* TODO: check for dev as well */ + if (ino == c_ino) + { + c_ino = 0; + c_seen_eof = 0; + } +} + +void +smb_free_dir_cache(void) +{ + if (c_entry != NULL) + { + smb_vfree(c_entry); + } + c_entry = NULL; +} + +/* Insert a NEW smb_inode_info into the inode tree of our filesystem, + under dir. The caller must assure that it's not already there. We + assume that path is allocated for us. */ + +static struct inode * +smb_iget(struct inode *dir, struct smb_inode_info *new_inode_info) +{ + struct inode *inode; + struct smb_inode_info *root; + + if ((dir == NULL) || (new_inode_info == NULL)) + { + printk("smb_iget: parameter is NULL\n"); + return NULL; + } + new_inode_info->state = SMB_INODE_LOOKED_UP; + new_inode_info->nused = 0; + new_inode_info->dir = SMB_INOP(dir); + + SMB_INOP(dir)->nused += 1; + + /* We have to link the new inode_info into the doubly linked + list of inode_infos to make a complete linear search + possible. */ + + root = &(SMB_SERVER(dir)->root); + + new_inode_info->prev = root; + new_inode_info->next = root->next; + root->next->prev = new_inode_info; + root->next = new_inode_info; + + if (!(inode = iget(dir->i_sb, smb_info_ino(new_inode_info)))) + { + new_inode_info->next->prev = new_inode_info->prev; + new_inode_info->prev->next = new_inode_info->next; + SMB_INOP(dir)->nused -= 1; + + printk("smb_iget: iget failed!"); + return NULL; + } + return inode; +} + +void +smb_free_inode_info(struct smb_inode_info *i) +{ + if (i == NULL) + { + printk("smb_free_inode: i == NULL\n"); + return; + } + i->state = SMB_INODE_CACHED; + while ((i->nused == 0) && (i->state == SMB_INODE_CACHED)) + { + struct smb_inode_info *dir = i->dir; + + i->next->prev = i->prev; + i->prev->next = i->next; + + smb_kfree_s(i, sizeof(struct smb_inode_info)); + + if (dir == NULL) + { + return; + } + dir->nused -= 1; + i = dir; + } +} + +void +smb_init_root(struct smb_server *server) +{ + struct smb_inode_info *root = &(server->root); + + root->state = SMB_INODE_LOOKED_UP; + root->nused = 1; + root->dir = NULL; + root->next = root->prev = root; + + return; +} + +void +smb_free_all_inodes(struct smb_server *server) +{ + /* Here nothing should be to do. I do not know whether it's + better to leave some memory allocated or be stuck in an + endless loop */ +#if 1 + struct smb_inode_info *root = &(server->root); + + if (root->next != root) + { + printk("smb_free_all_inodes: INODES LEFT!!!\n"); + } + while (root->next != root) + { + printk("smb_free_all_inodes: freeing inode\n"); + smb_free_inode_info(root->next); + /* In case we have an endless loop.. */ + schedule(); + } +#endif + + return; +} + +/* This has to be called when a connection has gone down, so that all + file-handles we got from the server are invalid */ +void +smb_invalidate_all_inodes(struct smb_server *server) +{ + struct smb_inode_info *ino = &(server->root); + + do + { + ino->finfo.opened = 0; + ino = ino->next; + } + while (ino != &(server->root)); + + return; +} + +/* We will search the inode that belongs to this name, currently by a + complete linear search through the inodes belonging to this + filesystem. This has to be fixed. */ +static struct smb_inode_info * +smb_find_dir_inode(struct inode *parent, const char *name, int len) +{ + struct smb_server *server = SMB_SERVER(parent); + struct smb_inode_info *dir = SMB_INOP(parent); + struct smb_inode_info *result = &(server->root); + + if (name == NULL) + { + return NULL; + } + if ((len == 1) && (name[0] == '.')) + { + return dir; + } + if ((len == 2) && (name[0] == '.') && (name[1] == '.')) + { + return dir->dir; + } + do + { + if (result->dir == dir) + { + if (compare_filename(server, name, len, + &(result->finfo)) == 0) + { + return result; + } + } + result = result->next; + } + while (result != &(server->root)); + + return NULL; +} + +static int +smb_lookup(struct inode *dir, const char *name, int len, + struct inode **result) +{ + struct smb_dirent finfo; + struct smb_inode_info *result_info; + int error; + int found_in_cache; + + struct smb_inode_info *new_inode_info = NULL; + + *result = NULL; + + if (!dir || !S_ISDIR(dir->i_mode)) + { + printk("smb_lookup: inode is NULL or not a directory.\n"); + iput(dir); + return -ENOENT; + } + DDPRINTK("smb_lookup: %s\n", name); + + /* Fast cheat for . */ + if (len == 0 || (len == 1 && name[0] == '.')) + { + *result = dir; + return 0; + } + /* ..and for .. */ + if (len == 2 && name[0] == '.' && name[1] == '.') + { + struct smb_inode_info *parent = SMB_INOP(dir)->dir; + + if (parent->state == SMB_INODE_CACHED) + { + parent->state = SMB_INODE_LOOKED_UP; + } + *result = iget(dir->i_sb, smb_info_ino(parent)); + iput(dir); + if (*result == 0) + { + return -EACCES; + } + return 0; + } + result_info = smb_find_dir_inode(dir, name, len); + + in_tree: + if (result_info != NULL) + { + if (result_info->state == SMB_INODE_CACHED) + { + result_info->state = SMB_INODE_LOOKED_UP; + } + *result = iget(dir->i_sb, smb_info_ino(result_info)); + iput(dir); + + if (new_inode_info != NULL) + { + smb_kfree_s(new_inode_info, + sizeof(struct smb_inode_info)); + } + if (*result == NULL) + { + return -EACCES; + } + return 0; + } + /* If the file is in the dir cache, we do not have to ask the + server. */ + found_in_cache = 0; + + if ((dir->i_dev == c_dev) && (dir->i_ino == c_ino) && (c_size != 0)) + { + int first = c_last_returned_index; + int i; + + i = first; + do + { + if (compare_filename(SMB_SERVER(dir), name, len, + &(c_entry[i])) == 0) + { + finfo = c_entry[i]; + found_in_cache = 1; + break; + } + i = (i + 1) % c_size; + } + while (i != first); + } + if (found_in_cache == 0) + { + DPRINTK("smb_lookup: not found in cache: %s\n", name); + if (len > SMB_MAXNAMELEN) + { + iput(dir); + return -ENAMETOOLONG; + } + error = smb_proc_getattr(dir, name, len, &finfo); + if (error < 0) + { + iput(dir); + return error; + } + finfo.f_ino = smb_fresh_inodes(SMB_SERVER(dir), 1); + } + new_inode_info = smb_kmalloc(sizeof(struct smb_inode_info), + GFP_KERNEL); + + /* Here somebody else might have inserted the inode */ + + result_info = smb_find_dir_inode(dir, name, len); + if (result_info != NULL) + { + goto in_tree; + } + new_inode_info->finfo = finfo; + + DPRINTK("attr: %x\n", finfo.attr); + + if ((*result = smb_iget(dir, new_inode_info)) == NULL) + { + smb_kfree_s(new_inode_info, sizeof(struct smb_inode_info)); + iput(dir); + return -EACCES; + } + DDPRINTK("smb_lookup: %s => %lu\n", name, (unsigned long) result_info); + iput(dir); + return 0; +} + +static int +smb_create(struct inode *dir, const char *name, int len, int mode, + struct inode **result) +{ + int error; + struct smb_dirent entry; + struct smb_inode_info *new_inode_info; + + *result = NULL; + + if (!dir || !S_ISDIR(dir->i_mode)) + { + printk("smb_create: inode is NULL or not a directory\n"); + iput(dir); + return -ENOENT; + } + new_inode_info = smb_kmalloc(sizeof(struct smb_inode_info), + GFP_KERNEL); + if (new_inode_info == NULL) + { + iput(dir); + return -ENOMEM; + } + error = smb_proc_create(dir, name, len, 0, CURRENT_TIME); + if (error < 0) + { + smb_kfree_s(new_inode_info, sizeof(struct smb_inode_info)); + iput(dir); + return error; + } + smb_invalid_dir_cache(dir->i_ino); + + if ((error = smb_proc_getattr(dir, name, len, &entry)) < 0) + { + smb_kfree_s(new_inode_info, sizeof(struct smb_inode_info)); + iput(dir); + return error; + } + entry.f_ino = smb_fresh_inodes(SMB_SERVER(dir), 1); + + new_inode_info->finfo = entry; + + if ((*result = smb_iget(dir, new_inode_info)) == NULL) + { + smb_kfree_s(new_inode_info, sizeof(struct smb_inode_info)); + iput(dir); + return error; + } + iput(dir); + return 0; +} + +static int +smb_mkdir(struct inode *dir, const char *name, int len, int mode) +{ + int error; + + if (!dir || !S_ISDIR(dir->i_mode)) + { + iput(dir); + return -EINVAL; + } + if ((error = smb_proc_mkdir(dir, name, len)) == 0) + { + smb_invalid_dir_cache(dir->i_ino); + } + iput(dir); + return error; +} + +static int +smb_rmdir(struct inode *dir, const char *name, int len) +{ + int error; + + if (!dir || !S_ISDIR(dir->i_mode)) + { + printk("smb_rmdir: inode is NULL or not a directory\n"); + iput(dir); + return -ENOENT; + } + if (smb_find_dir_inode(dir, name, len) != NULL) + { + error = -EBUSY; + } else + { + if ((error = smb_proc_rmdir(dir, name, len)) == 0) + { + smb_invalid_dir_cache(dir->i_ino); + } + } + iput(dir); + return error; +} + +static int +smb_unlink(struct inode *dir, const char *name, int len) +{ + int error; + + if (!dir || !S_ISDIR(dir->i_mode)) + { + printk("smb_unlink: inode is NULL or not a directory\n"); + iput(dir); + return -ENOENT; + } + if (smb_find_dir_inode(dir, name, len) != NULL) + { + error = -EBUSY; + } else + { + if ((error = smb_proc_unlink(dir, name, len)) == 0) + { + smb_invalid_dir_cache(dir->i_ino); + } + } + iput(dir); + return error; +} + +static int +smb_rename(struct inode *old_dir, const char *old_name, int old_len, + struct inode *new_dir, const char *new_name, int new_len, + int must_be_dir) +{ + int res; + + if (!old_dir || !S_ISDIR(old_dir->i_mode)) + { + printk("smb_rename: old inode is NULL or not a directory\n"); + res = -ENOENT; + goto finished; + } + if (!new_dir || !S_ISDIR(new_dir->i_mode)) + { + printk("smb_rename: new inode is NULL or not a directory\n"); + res = -ENOENT; + goto finished; + } + if ((smb_find_dir_inode(old_dir, old_name, old_len) != NULL) + || (smb_find_dir_inode(new_dir, new_name, new_len) != NULL)) + { + res = -EBUSY; + goto finished; + } + res = smb_proc_mv(old_dir, old_name, old_len, + new_dir, new_name, new_len); + + if (res == -EEXIST) + { + int res1 = smb_proc_unlink(old_dir, new_name, new_len); + + if (res1 == 0) + { + res = smb_proc_mv(old_dir, old_name, old_len, + new_dir, new_name, new_len); + } + } + if (res == 0) + { + smb_invalid_dir_cache(old_dir->i_ino); + smb_invalid_dir_cache(new_dir->i_ino); + } + finished: + iput(old_dir); + iput(new_dir); + return res; +} diff --git a/fs/smbfs/file.c b/fs/smbfs/file.c new file mode 100644 index 000000000..525c78ab9 --- /dev/null +++ b/fs/smbfs/file.c @@ -0,0 +1,244 @@ +/* + * file.c + * + * Copyright (C) 1995, 1996 by Paal-Kr. Engstad and Volker Lendecke + * + */ + +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/fcntl.h> +#include <linux/stat.h> +#include <linux/mm.h> +#include <linux/smb_fs.h> +#include <linux/malloc.h> + +#include <asm/uaccess.h> +#include <asm/system.h> + +static inline int +min(int a, int b) +{ + return a < b ? a : b; +} + +static int +smb_fsync(struct inode *inode, struct file *file) +{ + return 0; +} + +int +smb_make_open(struct inode *i, int right) +{ + struct smb_dirent *dirent; + + if (i == NULL) + { + printk("smb_make_open: got NULL inode\n"); + return -EINVAL; + } + dirent = &(SMB_INOP(i)->finfo); + + DDPRINTK("smb_make_open: dirent->opened = %d\n", dirent->opened); + + if ((dirent->opened) == 0) + { + /* tries max. rights */ + int open_result = smb_proc_open(SMB_SERVER(i), + SMB_INOP(i)->dir, + dirent->name, dirent->len, + dirent); + if (open_result) + { + return open_result; + } + } + if (((right == O_RDONLY) && ((dirent->access == O_RDONLY) + || (dirent->access == O_RDWR))) + || ((right == O_WRONLY) && ((dirent->access == O_WRONLY) + || (dirent->access == O_RDWR))) + || ((right == O_RDWR) && (dirent->access == O_RDWR))) + return 0; + + return -EACCES; +} + +static long +smb_file_read(struct inode *inode, struct file *file, char *buf, unsigned long count) +{ + int result, bufsize, to_read, already_read; + off_t pos; + int errno; + + DPRINTK("smb_file_read: enter %s\n", SMB_FINFO(inode)->name); + + if (!inode) + { + DPRINTK("smb_file_read: inode = NULL\n"); + return -EINVAL; + } + if (!S_ISREG(inode->i_mode)) + { + DPRINTK("smb_file_read: read from non-file, mode %07o\n", + inode->i_mode); + return -EINVAL; + } + if ((errno = smb_make_open(inode, O_RDONLY)) != 0) + return errno; + + pos = file->f_pos; + + if (pos + count > inode->i_size) + { + count = inode->i_size - pos; + } + if (count <= 0) + { + return 0; + } + bufsize = SMB_SERVER(inode)->max_xmit - SMB_HEADER_LEN - 5 * 2 - 5; + + already_read = 0; + + /* First read in as much as possible for each bufsize. */ + while (already_read < count) + { + to_read = min(bufsize, count - already_read); + result = smb_proc_read(SMB_SERVER(inode), SMB_FINFO(inode), + pos, to_read, buf, 1); + if (result < 0) + { + return result; + } + pos += result; + buf += result; + already_read += result; + + if (result < to_read) + { + break; + } + } + + file->f_pos = pos; + + if (!IS_RDONLY(inode)) + inode->i_atime = CURRENT_TIME; + inode->i_dirt = 1; + + DPRINTK("smb_file_read: exit %s\n", SMB_FINFO(inode)->name); + + return already_read; +} + +static long +smb_file_write(struct inode *inode, struct file *file, const char *buf, + unsigned long count) +{ + int result, bufsize, to_write, already_written; + off_t pos; + int errno; + + if (!inode) + { + DPRINTK("smb_file_write: inode = NULL\n"); + return -EINVAL; + } + if (!S_ISREG(inode->i_mode)) + { + DPRINTK("smb_file_write: write to non-file, mode %07o\n", + inode->i_mode); + return -EINVAL; + } + DPRINTK("smb_file_write: enter %s\n", SMB_FINFO(inode)->name); + + if (count <= 0) + { + return 0; + } + if ((errno = smb_make_open(inode, O_RDWR)) != 0) + { + return errno; + } + pos = file->f_pos; + + if (file->f_flags & O_APPEND) + pos = inode->i_size; + + bufsize = SMB_SERVER(inode)->max_xmit - SMB_HEADER_LEN - 5 * 2 - 5; + + already_written = 0; + + DPRINTK("smb_write_file: blkmode = %d, blkmode & 2 = %d\n", + SMB_SERVER(inode)->blkmode, + SMB_SERVER(inode)->blkmode & 2); + + while (already_written < count) + { + to_write = min(bufsize, count - already_written); + result = smb_proc_write(SMB_SERVER(inode), SMB_FINFO(inode), + pos, to_write, buf); + + if (result < 0) + { + return result; + } + pos += result; + buf += result; + already_written += result; + + if (result < to_write) + { + break; + } + } + + inode->i_mtime = inode->i_ctime = CURRENT_TIME; + inode->i_dirt = 1; + + file->f_pos = pos; + + if (pos > inode->i_size) + { + inode->i_size = pos; + } + DPRINTK("smb_file_write: exit %s\n", SMB_FINFO(inode)->name); + + return already_written; +} + +static struct file_operations smb_file_operations = +{ + NULL, /* lseek - default */ + smb_file_read, /* read */ + smb_file_write, /* write */ + NULL, /* readdir - bad */ + NULL, /* select - default */ + smb_ioctl, /* ioctl */ + smb_mmap, /* mmap */ + NULL, /* open */ + NULL, /* release */ + smb_fsync, /* fsync */ +}; + +struct inode_operations smb_file_inode_operations = +{ + &smb_file_operations, /* default file operations */ + NULL, /* create */ + NULL, /* lookup */ + NULL, /* link */ + NULL, /* unlink */ + NULL, /* symlink */ + NULL, /* mkdir */ + NULL, /* rmdir */ + NULL, /* mknod */ + NULL, /* rename */ + NULL, /* readlink */ + NULL, /* follow_link */ + NULL, /* readpage */ + NULL, /* writepage */ + NULL, /* bmap */ + NULL /* truncate */ +}; diff --git a/fs/smbfs/inode.c b/fs/smbfs/inode.c new file mode 100644 index 000000000..b39403358 --- /dev/null +++ b/fs/smbfs/inode.c @@ -0,0 +1,474 @@ +/* + * inode.c + * + * Copyright (C) 1995, 1996 by Paal-Kr. Engstad and Volker Lendecke + * + */ + +#include <linux/module.h> + +#include <linux/sched.h> +#include <linux/smb_fs.h> +#include <linux/smbno.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/stat.h> +#include <linux/errno.h> +#include <linux/locks.h> +#include <linux/fcntl.h> +#include <linux/malloc.h> + +#include <asm/system.h> +#include <asm/uaccess.h> + +extern int close_fp(struct file *filp); + +static void smb_put_inode(struct inode *); +static void smb_read_inode(struct inode *); +static void smb_put_super(struct super_block *); +static void smb_statfs(struct super_block *, struct statfs *, int bufsiz); + +static struct super_operations smb_sops = +{ + smb_read_inode, /* read inode */ + smb_notify_change, /* notify change */ + NULL, /* write inode */ + smb_put_inode, /* put inode */ + smb_put_super, /* put superblock */ + NULL, /* write superblock */ + smb_statfs, /* stat filesystem */ + NULL +}; + +/* smb_read_inode: Called from iget, it only traverses the allocated + smb_inode_info's and initializes the inode from the data found + there. It does not allocate or deallocate anything. */ + +static void +smb_read_inode(struct inode *inode) +{ + /* Our task should be extremely simple here. We only have to + look up the information somebody else (smb_iget) put into + the inode tree. */ + struct smb_server *server = SMB_SERVER(inode); + struct smb_inode_info *inode_info + = smb_find_inode(server, inode->i_ino); + + if (inode_info == NULL) + { + /* Ok, now we're in trouble. The inode info is not + there. What should we do now??? */ + printk("smb_read_inode: inode info not found\n"); + return; + } + inode_info->state = SMB_INODE_VALID; + + SMB_INOP(inode) = inode_info; + inode->i_mode = inode_info->finfo.f_mode; + inode->i_nlink = inode_info->finfo.f_nlink; + inode->i_uid = inode_info->finfo.f_uid; + inode->i_gid = inode_info->finfo.f_gid; + inode->i_rdev = inode_info->finfo.f_rdev; + inode->i_size = inode_info->finfo.f_size; + inode->i_mtime = inode_info->finfo.f_mtime; + inode->i_ctime = inode_info->finfo.f_ctime; + inode->i_atime = inode_info->finfo.f_atime; + inode->i_blksize = inode_info->finfo.f_blksize; + inode->i_blocks = inode_info->finfo.f_blocks; + + if (S_ISREG(inode->i_mode)) + { + inode->i_op = &smb_file_inode_operations; + } else if (S_ISDIR(inode->i_mode)) + { + inode->i_op = &smb_dir_inode_operations; + } else + { + inode->i_op = NULL; + } +} + +static void +smb_put_inode(struct inode *inode) +{ + struct smb_dirent *finfo = SMB_FINFO(inode); + struct smb_server *server = SMB_SERVER(inode); + struct smb_inode_info *info = SMB_INOP(inode); + + if (S_ISDIR(inode->i_mode)) + { + smb_invalid_dir_cache(inode->i_ino); + } + if (finfo->opened != 0) + { + if (smb_proc_close(server, finfo->fileid, inode->i_mtime)) + { + /* We can't do anything but complain. */ + DPRINTK("smb_put_inode: could not close\n"); + } + } + smb_free_inode_info(info); + clear_inode(inode); +} + +static void +smb_put_super(struct super_block *sb) +{ + struct smb_server *server = &(SMB_SBP(sb)->s_server); + + smb_proc_disconnect(server); + smb_dont_catch_keepalive(server); + close_fp(server->sock_file); + + lock_super(sb); + + smb_free_all_inodes(server); + + smb_vfree(server->packet); + server->packet = NULL; + + sb->s_dev = 0; + smb_kfree_s(SMB_SBP(sb), sizeof(struct smb_sb_info)); + + unlock_super(sb); + + MOD_DEC_USE_COUNT; +} + +struct smb_mount_data_v4 +{ + int version; + unsigned int fd; + uid_t mounted_uid; + struct sockaddr_in addr; + + char server_name[17]; + char client_name[17]; + char service[64]; + char root_path[64]; + + char username[64]; + char password[64]; + + unsigned short max_xmit; + + uid_t uid; + gid_t gid; + mode_t file_mode; + mode_t dir_mode; +}; + +static int +smb_get_mount_data(struct smb_mount_data *target, void *source) +{ + struct smb_mount_data_v4 *v4 = (struct smb_mount_data_v4 *) source; + struct smb_mount_data *cur = (struct smb_mount_data *) source; + + if (source == NULL) + { + return 1; + } + if (cur->version == SMB_MOUNT_VERSION) + { + memcpy(target, cur, sizeof(struct smb_mount_data)); + return 0; + } + if (v4->version == 4) + { + target->version = 5; + target->fd = v4->fd; + target->mounted_uid = v4->mounted_uid; + target->addr = v4->addr; + + memcpy(target->server_name, v4->server_name, 17); + memcpy(target->client_name, v4->client_name, 17); + memcpy(target->service, v4->service, 64); + memcpy(target->root_path, v4->root_path, 64); + memcpy(target->username, v4->username, 64); + memcpy(target->password, v4->password, 64); + + target->max_xmit = v4->max_xmit; + target->uid = v4->uid; + target->gid = v4->gid; + target->file_mode = v4->file_mode; + target->dir_mode = v4->dir_mode; + + memset(target->domain, 0, 64); + strcpy(target->domain, "?"); + return 0; + } + return 1; +} + +struct super_block * +smb_read_super(struct super_block *sb, void *raw_data, int silent) +{ + struct smb_mount_data data; + struct smb_server *server; + struct smb_sb_info *smb_sb; + unsigned int fd; + struct file *filp; + kdev_t dev = sb->s_dev; + int error; + + if (smb_get_mount_data(&data, raw_data) != 0) + { + printk("smb_read_super: wrong data argument\n"); + sb->s_dev = 0; + return NULL; + } + fd = data.fd; + if (fd >= NR_OPEN || !(filp = current->files->fd[fd])) + { + printk("smb_read_super: invalid file descriptor\n"); + sb->s_dev = 0; + return NULL; + } + if (!S_ISSOCK(filp->f_inode->i_mode)) + { + printk("smb_read_super: not a socket!\n"); + sb->s_dev = 0; + return NULL; + } + /* We must malloc our own super-block info */ + smb_sb = (struct smb_sb_info *) smb_kmalloc(sizeof(struct smb_sb_info), + GFP_KERNEL); + + if (smb_sb == NULL) + { + printk("smb_read_super: could not alloc smb_sb_info\n"); + return NULL; + } + filp->f_count += 1; + + lock_super(sb); + + SMB_SBP(sb) = smb_sb; + + sb->s_blocksize = 1024; /* Eh... Is this correct? */ + sb->s_blocksize_bits = 10; + sb->s_magic = SMB_SUPER_MAGIC; + sb->s_dev = dev; + sb->s_op = &smb_sops; + + server = &(SMB_SBP(sb)->s_server); + server->sock_file = filp; + server->lock = 0; + server->wait = NULL; + server->packet = NULL; + server->max_xmit = data.max_xmit; + if (server->max_xmit <= 0) + { + server->max_xmit = SMB_DEF_MAX_XMIT; + } + server->tid = 0; + server->pid = current->pid; + server->mid = current->pid + 20; + + server->m = data; + server->m.file_mode = (server->m.file_mode & + (S_IRWXU | S_IRWXG | S_IRWXO)) | S_IFREG; + server->m.dir_mode = (server->m.dir_mode & + (S_IRWXU | S_IRWXG | S_IRWXO)) | S_IFDIR; + + smb_init_root(server); + + error = smb_proc_connect(server); + + unlock_super(sb); + + if (error < 0) + { + sb->s_dev = 0; + DPRINTK("smb_read_super: Failed connection, bailing out " + "(error = %d).\n", -error); + goto fail; + } + if (server->protocol >= PROTOCOL_LANMAN2) + { + server->case_handling = CASE_DEFAULT; + } else + { + server->case_handling = CASE_LOWER; + } + + if ((error = smb_proc_dskattr(sb, &(SMB_SBP(sb)->s_attr))) < 0) + { + sb->s_dev = 0; + printk("smb_read_super: could not get super block " + "attributes\n"); + goto fail; + } + smb_init_root_dirent(server, &(server->root.finfo)); + + if (!(sb->s_mounted = iget(sb, smb_info_ino(&(server->root))))) + { + sb->s_dev = 0; + printk("smb_read_super: get root inode failed\n"); + goto fail; + } + MOD_INC_USE_COUNT; + return sb; + + fail: + if (server->packet != NULL) + { + smb_vfree(server->packet); + server->packet = NULL; + } + filp->f_count -= 1; + smb_dont_catch_keepalive(server); + smb_kfree_s(SMB_SBP(sb), sizeof(struct smb_sb_info)); + return NULL; +} + +static void +smb_statfs(struct super_block *sb, struct statfs *buf, int bufsiz) +{ + int error; + struct smb_dskattr attr; + struct statfs tmp; + + error = smb_proc_dskattr(sb, &attr); + + if (error) + { + printk("smb_statfs: dskattr error = %d\n", -error); + attr.total = attr.allocblocks = attr.blocksize = + attr.free = 0; + } + tmp.f_type = SMB_SUPER_MAGIC; + tmp.f_bsize = attr.blocksize * attr.allocblocks; + tmp.f_blocks = attr.total; + tmp.f_bfree = attr.free; + tmp.f_bavail = attr.free; + tmp.f_files = -1; + tmp.f_ffree = -1; + tmp.f_namelen = SMB_MAXPATHLEN; + copy_to_user(buf, &tmp, bufsiz); +} + +int +smb_notify_change(struct inode *inode, struct iattr *attr) +{ + int error = 0; + + if ((error = inode_change_ok(inode, attr)) < 0) + return error; + + if (((attr->ia_valid & ATTR_UID) && + (attr->ia_uid != SMB_SERVER(inode)->m.uid))) + return -EPERM; + + if (((attr->ia_valid & ATTR_GID) && + (attr->ia_uid != SMB_SERVER(inode)->m.gid))) + return -EPERM; + + if (((attr->ia_valid & ATTR_MODE) && + (attr->ia_mode & ~(S_IFREG | S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO)))) + return -EPERM; + + if ((attr->ia_valid & ATTR_SIZE) != 0) + { + + if ((error = smb_make_open(inode, O_WRONLY)) < 0) + goto fail; + + if ((error = smb_proc_trunc(SMB_SERVER(inode), + SMB_FINFO(inode)->fileid, + attr->ia_size)) < 0) + goto fail; + + } + if ((attr->ia_valid & (ATTR_CTIME | ATTR_MTIME | ATTR_ATIME)) != 0) + { + + struct smb_dirent finfo; + + finfo.attr = 0; + finfo.f_size = inode->i_size; + finfo.f_blksize = inode->i_blksize; + + if ((attr->ia_valid & ATTR_CTIME) != 0) + finfo.f_ctime = attr->ia_ctime; + else + finfo.f_ctime = inode->i_ctime; + + if ((attr->ia_valid & ATTR_MTIME) != 0) + finfo.f_mtime = attr->ia_mtime; + else + finfo.f_mtime = inode->i_mtime; + + if ((attr->ia_valid & ATTR_ATIME) != 0) + finfo.f_atime = attr->ia_atime; + else + finfo.f_atime = inode->i_atime; + + if ((error = smb_proc_setattr(SMB_SERVER(inode), + inode, &finfo)) >= 0) + { + inode->i_ctime = finfo.f_ctime; + inode->i_mtime = finfo.f_mtime; + inode->i_atime = finfo.f_atime; + } + } + fail: + smb_invalid_dir_cache(smb_info_ino(SMB_INOP(inode)->dir)); + + return error; +} + + +#ifdef DEBUG_SMB_MALLOC +int smb_malloced; +int smb_current_kmalloced; +int smb_current_vmalloced; +#endif + +static struct file_system_type smb_fs_type = +{ + smb_read_super, "smbfs", 0, NULL +}; + +int +init_smb_fs(void) +{ + return register_filesystem(&smb_fs_type); +} + +#ifdef MODULE +int +init_module(void) +{ + int status; + + DPRINTK("smbfs: init_module called\n"); + +#ifdef DEBUG_SMB_MALLOC + smb_malloced = 0; + smb_current_kmalloced = 0; + smb_current_vmalloced = 0; +#endif + + smb_init_dir_cache(); + + if ((status = init_smb_fs()) == 0) + register_symtab(0); + return status; +} + +void +cleanup_module(void) +{ + DPRINTK("smbfs: cleanup_module called\n"); + smb_free_dir_cache(); + unregister_filesystem(&smb_fs_type); +#ifdef DEBUG_SMB_MALLOC + printk("smb_malloced: %d\n", smb_malloced); + printk("smb_current_kmalloced: %d\n", smb_current_kmalloced); + printk("smb_current_vmalloced: %d\n", smb_current_vmalloced); +#endif +} + +#endif diff --git a/fs/smbfs/ioctl.c b/fs/smbfs/ioctl.c new file mode 100644 index 000000000..d4685a60b --- /dev/null +++ b/fs/smbfs/ioctl.c @@ -0,0 +1,29 @@ +/* + * ioctl.c + * + * Copyright (C) 1995, 1996 by Volker Lendecke + * + */ + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/smb_fs.h> +#include <linux/ioctl.h> +#include <linux/sched.h> +#include <linux/mm.h> + +#include <asm/uaccess.h> + +int +smb_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + switch (cmd) + { + case SMB_IOC_GETMOUNTUID: + return put_user(SMB_SERVER(inode)->m.mounted_uid, (uid_t *) arg); + + default: + return -EINVAL; + } +} diff --git a/fs/smbfs/mmap.c b/fs/smbfs/mmap.c new file mode 100644 index 000000000..9fd157b2a --- /dev/null +++ b/fs/smbfs/mmap.c @@ -0,0 +1,126 @@ +/* + * mmap.c + * + * Copyright (C) 1995, 1996 by Paal-Kr. Engstad and Volker Lendecke + * + */ + +#include <linux/stat.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/shm.h> +#include <linux/errno.h> +#include <linux/mman.h> +#include <linux/string.h> +#include <linux/malloc.h> +#include <linux/smb_fs.h> +#include <linux/fcntl.h> + +#include <asm/uaccess.h> +#include <asm/system.h> + +/* + * Fill in the supplied page for mmap + */ +static unsigned long +smb_file_mmap_nopage(struct vm_area_struct *area, + unsigned long address, int no_share) +{ + struct inode *inode = area->vm_inode; + unsigned long page; + unsigned int clear; + unsigned long tmp; + int n; + int i; + int pos; + + page = __get_free_page(GFP_KERNEL); + if (!page) + return 0; + address &= PAGE_MASK; + pos = address - area->vm_start + area->vm_offset; + + clear = 0; + if (address + PAGE_SIZE > area->vm_end) + { + clear = address + PAGE_SIZE - area->vm_end; + } + /* what we can read in one go */ + n = SMB_SERVER(inode)->max_xmit - SMB_HEADER_LEN - 5 * 2 - 3 - 10; + + if (smb_make_open(inode, O_RDONLY) < 0) + { + clear = PAGE_SIZE; + } else + { + + for (i = 0; i < (PAGE_SIZE - clear); i += n) + { + int hunk, result; + + hunk = PAGE_SIZE - i; + if (hunk > n) + hunk = n; + + DDPRINTK("smb_file_mmap_nopage: reading\n"); + DDPRINTK("smb_file_mmap_nopage: pos = %d\n", pos); + result = smb_proc_read(SMB_SERVER(inode), + SMB_FINFO(inode), pos, hunk, + (char *) (page + i), 0); + DDPRINTK("smb_file_mmap_nopage: result= %d\n", result); + if (result < 0) + break; + pos += result; + if (result < n) + { + i += result; + break; + } + } + } + + tmp = page + PAGE_SIZE; + while (clear--) + { + *(char *) --tmp = 0; + } + return page; +} + +struct vm_operations_struct smb_file_mmap = +{ + NULL, /* open */ + NULL, /* close */ + NULL, /* unmap */ + NULL, /* protect */ + NULL, /* sync */ + NULL, /* advise */ + smb_file_mmap_nopage, /* nopage */ + NULL, /* wppage */ + NULL, /* swapout */ + NULL, /* swapin */ +}; + + +/* This is used for a general mmap of a smb file */ +int +smb_mmap(struct inode *inode, struct file *file, struct vm_area_struct *vma) +{ + DPRINTK("smb_mmap: called\n"); + + /* only PAGE_COW or read-only supported now */ + if (vma->vm_flags & VM_SHARED) + return -EINVAL; + if (!inode->i_sb || !S_ISREG(inode->i_mode)) + return -EACCES; + if (!IS_RDONLY(inode)) + { + inode->i_atime = CURRENT_TIME; + inode->i_dirt = 1; + } + vma->vm_inode = inode; + inode->i_count++; + vma->vm_ops = &smb_file_mmap; + return 0; +} diff --git a/fs/smbfs/proc.c b/fs/smbfs/proc.c new file mode 100644 index 000000000..ee998a89f --- /dev/null +++ b/fs/smbfs/proc.c @@ -0,0 +1,1969 @@ +/* + * proc.c + * + * Copyright (C) 1995, 1996 by Paal-Kr. Engstad and Volker Lendecke + * + * 28/06/96 - Fixed long file name support (smb_proc_readdir_long) by Yuri Per + */ + +#include <linux/config.h> +#include <linux/fs.h> +#include <linux/smbno.h> +#include <linux/smb_fs.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/malloc.h> +#include <linux/stat.h> +#include <linux/fcntl.h> + +#include <asm/uaccess.h> +#include <asm/string.h> + +#define SMB_VWV(packet) ((packet) + SMB_HEADER_LEN) +#define SMB_CMD(packet) (BVAL(packet,8)) +#define SMB_WCT(packet) (BVAL(packet, SMB_HEADER_LEN - 1)) +#define SMB_BCC(packet) smb_bcc(packet) +#define SMB_BUF(packet) ((packet) + SMB_HEADER_LEN + SMB_WCT(packet) * 2 + 2) + +#define SMB_DIRINFO_SIZE 43 +#define SMB_STATUS_SIZE 21 + +static int smb_request_ok(struct smb_server *s, int command, int wct, int bcc); + +static inline int +min(int a, int b) +{ + return a < b ? a : b; +} + +static void +str_upper(char *name) +{ + while (*name) + { + if (*name >= 'a' && *name <= 'z') + *name -= ('a' - 'A'); + name++; + } +} + +static void +str_lower(char *name) +{ + while (*name) + { + if (*name >= 'A' && *name <= 'Z') + *name += ('a' - 'A'); + name++; + } +} + +/*****************************************************************************/ +/* */ +/* Encoding/Decoding section */ +/* */ +/*****************************************************************************/ + +static inline byte * +smb_decode_word(byte * p, word * data) +{ + *data = WVAL(p, 0); + return p + 2; +} + +byte * +smb_encode_smb_length(byte * p, dword len) +{ + BSET(p, 0, 0); + BSET(p, 1, 0); + BSET(p, 2, (len & 0xFF00) >> 8); + BSET(p, 3, (len & 0xFF)); + if (len > 0xFFFF) + { + BSET(p, 1, 1); + } + return p + 4; +} + +static byte * +smb_encode_ascii(byte * p, const byte * name, int len) +{ + *p++ = 4; + strcpy(p, name); + return p + len + 1; +} + +static byte * +smb_encode_this_name(byte * p, const char *name, const int len) +{ + *p++ = '\\'; + strncpy(p, name, len); + return p + len; +} + +/* I put smb_encode_parents into a separate function so that the + recursion only takes 16 bytes on the stack per path component on a + 386. */ + +static byte * +smb_encode_parents(byte * p, struct smb_inode_info *ino) +{ + byte *q; + + if (ino->dir == NULL) + { + return p; + } + q = smb_encode_parents(p, ino->dir); + if (q - p + 1 + ino->finfo.len > SMB_MAXPATHLEN) + { + return p; + } + return smb_encode_this_name(q, ino->finfo.name, ino->finfo.len); +} + +static byte * +smb_encode_path(struct smb_server *server, + byte * p, struct smb_inode_info *dir, + const char *name, const int len) +{ + byte *start = p; + p = smb_encode_parents(p, dir); + p = smb_encode_this_name(p, name, len); + *p++ = 0; + if (server->protocol <= PROTOCOL_COREPLUS) + { + str_upper(start); + } + return p; +} + +static byte * +smb_decode_data(byte * p, byte * data, word * data_len, int fs) +{ + word len; + + if (!(*p == 1 || *p == 5)) + { + printk("smb_decode_data: Warning! Data block not starting " + "with 1 or 5\n"); + } + len = WVAL(p, 1); + p += 3; + + if (fs) + copy_to_user(data, p, len); + else + memcpy(data, p, len); + + *data_len = len; + + return p + len; +} + +static byte * +smb_name_mangle(byte * p, const byte * name) +{ + int len, pad = 0; + + len = strlen(name); + + if (len < 16) + pad = 16 - len; + + *p++ = 2 * (len + pad); + + while (*name) + { + *p++ = (*name >> 4) + 'A'; + *p++ = (*name & 0x0F) + 'A'; + name++; + } + while (pad--) + { + *p++ = 'C'; + *p++ = 'A'; + } + *p++ = '\0'; + + return p; +} + +/* The following are taken directly from msdos-fs */ + +/* Linear day numbers of the respective 1sts in non-leap years. */ + +static int day_n[] = +{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 0, 0, 0, 0}; + /* JanFebMarApr May Jun Jul Aug Sep Oct Nov Dec */ + + +extern struct timezone sys_tz; + +static int +utc2local(int time) +{ + return time - sys_tz.tz_minuteswest * 60; +} + +static int +local2utc(int time) +{ + return time + sys_tz.tz_minuteswest * 60; +} + +/* Convert a MS-DOS time/date pair to a UNIX date (seconds since 1 1 70). */ + +static int +date_dos2unix(unsigned short time, unsigned short date) +{ + int month, year, secs; + + month = ((date >> 5) & 15) - 1; + year = date >> 9; + secs = (time & 31) * 2 + 60 * ((time >> 5) & 63) + (time >> 11) * 3600 + 86400 * + ((date & 31) - 1 + day_n[month] + (year / 4) + year * 365 - ((year & 3) == 0 && + month < 2 ? 1 : 0) + 3653); + /* days since 1.1.70 plus 80's leap day */ + return local2utc(secs); +} + + +/* Convert linear UNIX date to a MS-DOS time/date pair. */ + +static void +date_unix2dos(int unix_date, byte * date, byte * time) +{ + int day, year, nl_day, month; + + unix_date = utc2local(unix_date); + WSET(time, 0, + (unix_date % 60) / 2 + (((unix_date / 60) % 60) << 5) + + (((unix_date / 3600) % 24) << 11)); + day = unix_date / 86400 - 3652; + year = day / 365; + if ((year + 3) / 4 + 365 * year > day) + year--; + day -= (year + 3) / 4 + 365 * year; + if (day == 59 && !(year & 3)) + { + nl_day = day; + month = 2; + } else + { + nl_day = (year & 3) || day <= 59 ? day : day - 1; + for (month = 0; month < 12; month++) + if (day_n[month] > nl_day) + break; + } + WSET(date, 0, + nl_day - day_n[month - 1] + 1 + (month << 5) + (year << 9)); +} + + + +/*****************************************************************************/ +/* */ +/* Support section. */ +/* */ +/*****************************************************************************/ + +dword +smb_len(byte * p) +{ + return ((BVAL(p, 1) & 0x1) << 16L) | (BVAL(p, 2) << 8L) | (BVAL(p, 3)); +} + +static word +smb_bcc(byte * packet) +{ + int pos = SMB_HEADER_LEN + SMB_WCT(packet) * sizeof(word); + return WVAL(packet, pos); +} + +/* smb_valid_packet: We check if packet fulfills the basic + requirements of a smb packet */ + +static int +smb_valid_packet(byte * packet) +{ + DDPRINTK("len: %d, wct: %d, bcc: %d\n", + smb_len(packet), SMB_WCT(packet), SMB_BCC(packet)); + return (packet[4] == 0xff + && packet[5] == 'S' + && packet[6] == 'M' + && packet[7] == 'B' + && (smb_len(packet) + 4 == SMB_HEADER_LEN + + SMB_WCT(packet) * 2 + SMB_BCC(packet))); +} + +/* smb_verify: We check if we got the answer we expected, and if we + got enough data. If bcc == -1, we don't care. */ + +static int +smb_verify(byte * packet, int command, int wct, int bcc) +{ + return (SMB_CMD(packet) == command && + SMB_WCT(packet) >= wct && + (bcc == -1 || SMB_BCC(packet) >= bcc)) ? 0 : -EIO; +} + +static int +smb_errno(int errcls, int error) +{ + if (errcls == ERRDOS) + switch (error) + { + case ERRbadfunc: + return EINVAL; + case ERRbadfile: + return ENOENT; + case ERRbadpath: + return ENOENT; + case ERRnofids: + return EMFILE; + case ERRnoaccess: + return EACCES; + case ERRbadfid: + return EBADF; + case ERRbadmcb: + return EREMOTEIO; + case ERRnomem: + return ENOMEM; + case ERRbadmem: + return EFAULT; + case ERRbadenv: + return EREMOTEIO; + case ERRbadformat: + return EREMOTEIO; + case ERRbadaccess: + return EACCES; + case ERRbaddata: + return E2BIG; + case ERRbaddrive: + return ENXIO; + case ERRremcd: + return EREMOTEIO; + case ERRdiffdevice: + return EXDEV; + case ERRnofiles: + return 0; + case ERRbadshare: + return ETXTBSY; + case ERRlock: + return EDEADLK; + case ERRfilexists: + return EEXIST; + case 87: + return 0; /* Unknown error!! */ + /* This next error seems to occur on an mv when + * the destination exists */ + case 183: + return EEXIST; + default: + return EIO; + } else if (errcls == ERRSRV) + switch (error) + { + case ERRerror: + return ENFILE; + case ERRbadpw: + return EINVAL; + case ERRbadtype: + return EIO; + case ERRaccess: + return EACCES; + default: + return EIO; + } else if (errcls == ERRHRD) + switch (error) + { + case ERRnowrite: + return EROFS; + case ERRbadunit: + return ENODEV; + case ERRnotready: + return EUCLEAN; + case ERRbadcmd: + return EIO; + case ERRdata: + return EIO; + case ERRbadreq: + return ERANGE; + case ERRbadshare: + return ETXTBSY; + case ERRlock: + return EDEADLK; + default: + return EIO; + } else if (errcls == ERRCMD) + return EIO; + return 0; +} + +static void +smb_lock_server(struct smb_server *server) +{ + while (server->lock) + sleep_on(&server->wait); + server->lock = 1; +} + +static void +smb_unlock_server(struct smb_server *server) +{ + if (server->lock != 1) + { + printk("smb_unlock_server: was not locked!\n"); + } + server->lock = 0; + wake_up(&server->wait); +} + +/* smb_request_ok: We expect the server to be locked. Then we do the + request and check the answer completely. When smb_request_ok + returns 0, you can be quite sure that everything went well. When + the answer is <=0, the returned number is a valid unix errno. */ + +static int +smb_request_ok(struct smb_server *s, int command, int wct, int bcc) +{ + int result = 0; + s->rcls = 0; + s->err = 0; + + if (smb_request(s) < 0) + { + DPRINTK("smb_request failed\n"); + result = -EIO; + } else if (smb_valid_packet(s->packet) != 0) + { + DPRINTK("not a valid packet!\n"); + result = -EIO; + } else if (s->rcls != 0) + { + result = -smb_errno(s->rcls, s->err); + } else if (smb_verify(s->packet, command, wct, bcc) != 0) + { + DPRINTK("smb_verify failed\n"); + result = -EIO; + } + return result; +} + +/* smb_retry: This function should be called when smb_request_ok has + indicated an error. If the error was indicated because the + connection was killed, we try to reconnect. If smb_retry returns 0, + the error was indicated for another reason, so a retry would not be + of any use. */ + +static int +smb_retry(struct smb_server *server) +{ + if (server->state != CONN_INVALID) + { + return 0; + } + if (smb_release(server) < 0) + { + DPRINTK("smb_retry: smb_release failed\n"); + server->state = CONN_RETRIED; + return 0; + } + if (smb_proc_reconnect(server) < 0) + { + DPRINTK("smb_proc_reconnect failed\n"); + server->state = CONN_RETRIED; + return 0; + } + server->state = CONN_VALID; + return 1; +} + +static int +smb_request_ok_unlock(struct smb_server *s, int command, int wct, int bcc) +{ + int result = smb_request_ok(s, command, wct, bcc); + + smb_unlock_server(s); + + return result; +} + +/* smb_setup_header: We completely set up the packet. You only have to + insert the command-specific fields */ + +__u8 * +smb_setup_header(struct smb_server * server, byte command, word wct, word bcc) +{ + dword xmit_len = SMB_HEADER_LEN + wct * sizeof(word) + bcc + 2; + byte *p = server->packet; + byte *buf = server->packet; + + p = smb_encode_smb_length(p, xmit_len - 4); + + BSET(p, 0, 0xff); + BSET(p, 1, 'S'); + BSET(p, 2, 'M'); + BSET(p, 3, 'B'); + BSET(p, 4, command); + + p += 5; + memset(p, '\0', 19); + p += 19; + p += 8; + + WSET(buf, smb_tid, server->tid); + WSET(buf, smb_pid, server->pid); + WSET(buf, smb_uid, server->server_uid); + WSET(buf, smb_mid, server->mid); + + if (server->protocol > PROTOCOL_CORE) + { + BSET(buf, smb_flg, 0x8); + WSET(buf, smb_flg2, 0x3); + } + *p++ = wct; /* wct */ + p += 2 * wct; + WSET(p, 0, bcc); + return p + 2; +} + +/* smb_setup_header_exclusive waits on server->lock and locks the + server, when it's free. You have to unlock it manually when you're + finished with server->packet! */ + +static byte * +smb_setup_header_exclusive(struct smb_server *server, + byte command, word wct, word bcc) +{ + smb_lock_server(server); + return smb_setup_header(server, command, wct, bcc); +} + +static void +smb_setup_bcc(struct smb_server *server, byte * p) +{ + __u8 *packet = server->packet; + __u8 *pbcc = packet + SMB_HEADER_LEN + 2 * SMB_WCT(packet); + __u16 bcc = p - (pbcc + 2); + + WSET(pbcc, 0, bcc); + smb_encode_smb_length(packet, + SMB_HEADER_LEN + 2 * SMB_WCT(packet) - 2 + bcc); +} + + +/*****************************************************************************/ +/* */ +/* File operation section. */ +/* */ +/*****************************************************************************/ + +int +smb_proc_open(struct smb_server *server, + struct smb_inode_info *dir, const char *name, int len, + struct smb_dirent *entry) +{ + int error; + char *p; + char *buf; + const word o_attr = aSYSTEM | aHIDDEN | aDIR; + + DPRINTK("smb_proc_open: name=%s\n", name); + + smb_lock_server(server); + buf = server->packet; + + if (entry->opened != 0) + { + /* Somebody else opened the file while we slept */ + smb_unlock_server(server); + return 0; + } + retry: + p = smb_setup_header(server, SMBopen, 2, 0); + WSET(buf, smb_vwv0, 0x42); /* read/write */ + WSET(buf, smb_vwv1, o_attr); + *p++ = 4; + p = smb_encode_path(server, p, dir, name, len); + smb_setup_bcc(server, p); + + if ((error = smb_request_ok(server, SMBopen, 7, 0)) != 0) + { + + if (smb_retry(server)) + { + goto retry; + } + if ((error != -EACCES) && (error != -ETXTBSY) + && (error != -EROFS)) + { + smb_unlock_server(server); + return error; + } + p = smb_setup_header(server, SMBopen, 2, 0); + WSET(buf, smb_vwv0, 0x40); /* read only */ + WSET(buf, smb_vwv1, o_attr); + *p++ = 4; + p = smb_encode_path(server, p, dir, name, len); + smb_setup_bcc(server, p); + + if ((error = smb_request_ok(server, SMBopen, 7, 0)) != 0) + { + if (smb_retry(server)) + { + goto retry; + } + smb_unlock_server(server); + return error; + } + } + /* We should now have data in vwv[0..6]. */ + + entry->fileid = WVAL(buf, smb_vwv0); + entry->attr = WVAL(buf, smb_vwv1); + entry->f_ctime = entry->f_atime = + entry->f_mtime = local2utc(DVAL(buf, smb_vwv2)); + entry->f_size = DVAL(buf, smb_vwv4); + entry->access = WVAL(buf, smb_vwv6); + + entry->opened = 1; + entry->access &= 3; + + smb_unlock_server(server); + + DPRINTK("smb_proc_open: entry->access = %d\n", entry->access); + return 0; +} + +int +smb_proc_close(struct smb_server *server, + __u16 fileid, __u32 mtime) +{ + char *buf; + + smb_setup_header_exclusive(server, SMBclose, 3, 0); + buf = server->packet; + WSET(buf, smb_vwv0, fileid); + DSET(buf, smb_vwv1, utc2local(mtime)); + + return smb_request_ok_unlock(server, SMBclose, 0, 0); +} + +/* In smb_proc_read and smb_proc_write we do not retry, because the + file-id would not be valid after a reconnection. */ + +/* smb_proc_read: fs indicates if it should be copied with + copy_to_user. */ + +int +smb_proc_read(struct smb_server *server, struct smb_dirent *finfo, + off_t offset, long count, char *data, int fs) +{ + word returned_count, data_len; + char *buf; + int error; + + smb_setup_header_exclusive(server, SMBread, 5, 0); + buf = server->packet; + + WSET(buf, smb_vwv0, finfo->fileid); + WSET(buf, smb_vwv1, count); + DSET(buf, smb_vwv2, offset); + WSET(buf, smb_vwv4, 0); + + if ((error = smb_request_ok(server, SMBread, 5, -1)) < 0) + { + smb_unlock_server(server); + return error; + } + returned_count = WVAL(buf, smb_vwv0); + + smb_decode_data(SMB_BUF(server->packet), data, &data_len, fs); + + smb_unlock_server(server); + + if (returned_count != data_len) + { + printk("smb_proc_read: Warning, returned_count != data_len\n"); + printk("smb_proc_read: ret_c=%d, data_len=%d\n", + returned_count, data_len); + } + return data_len; +} + +int +smb_proc_write(struct smb_server *server, struct smb_dirent *finfo, + off_t offset, int count, const char *data) +{ + int res = 0; + char *buf; + byte *p; + + p = smb_setup_header_exclusive(server, SMBwrite, 5, count + 3); + buf = server->packet; + WSET(buf, smb_vwv0, finfo->fileid); + WSET(buf, smb_vwv1, count); + DSET(buf, smb_vwv2, offset); + WSET(buf, smb_vwv4, 0); + + *p++ = 1; + WSET(p, 0, count); + copy_from_user(p + 2, data, count); + + if ((res = smb_request_ok(server, SMBwrite, 1, 0)) >= 0) + { + res = WVAL(buf, smb_vwv0); + } + smb_unlock_server(server); + + return res; +} + +int +smb_proc_create(struct inode *dir, const char *name, int len, + word attr, time_t ctime) +{ + int error; + char *p; + struct smb_server *server = SMB_SERVER(dir); + char *buf; + __u16 fileid; + + smb_lock_server(server); + buf = server->packet; + retry: + p = smb_setup_header(server, SMBcreate, 3, 0); + WSET(buf, smb_vwv0, attr); + DSET(buf, smb_vwv1, utc2local(ctime)); + *p++ = 4; + p = smb_encode_path(server, p, SMB_INOP(dir), name, len); + smb_setup_bcc(server, p); + + if ((error = smb_request_ok(server, SMBcreate, 1, 0)) < 0) + { + if (smb_retry(server)) + { + goto retry; + } + smb_unlock_server(server); + return error; + } + fileid = WVAL(buf, smb_vwv0); + smb_unlock_server(server); + + smb_proc_close(server, fileid, CURRENT_TIME); + + return 0; +} + +int +smb_proc_mv(struct inode *odir, const char *oname, const int olen, + struct inode *ndir, const char *nname, const int nlen) +{ + char *p; + struct smb_server *server = SMB_SERVER(odir); + char *buf; + int result; + + smb_lock_server(server); + buf = server->packet; + + retry: + p = smb_setup_header(server, SMBmv, 1, 0); + WSET(buf, smb_vwv0, aSYSTEM | aHIDDEN); + *p++ = 4; + p = smb_encode_path(server, p, SMB_INOP(odir), oname, olen); + *p++ = 4; + p = smb_encode_path(server, p, SMB_INOP(ndir), nname, nlen); + smb_setup_bcc(server, p); + + if ((result = smb_request_ok(server, SMBmv, 0, 0)) < 0) + { + if (smb_retry(server)) + { + goto retry; + } + } + smb_unlock_server(server); + return result; +} + +int +smb_proc_mkdir(struct inode *dir, const char *name, const int len) +{ + char *p; + int result; + struct smb_server *server = SMB_SERVER(dir); + + smb_lock_server(server); + + retry: + p = smb_setup_header(server, SMBmkdir, 0, 0); + *p++ = 4; + p = smb_encode_path(server, p, SMB_INOP(dir), name, len); + smb_setup_bcc(server, p); + + if ((result = smb_request_ok(server, SMBmkdir, 0, 0)) < 0) + { + if (smb_retry(server)) + { + goto retry; + } + } + smb_unlock_server(server); + return result; +} + +int +smb_proc_rmdir(struct inode *dir, const char *name, const int len) +{ + char *p; + int result; + struct smb_server *server = SMB_SERVER(dir); + + smb_lock_server(server); + + + retry: + p = smb_setup_header(server, SMBrmdir, 0, 0); + *p++ = 4; + p = smb_encode_path(server, p, SMB_INOP(dir), name, len); + smb_setup_bcc(server, p); + + if ((result = smb_request_ok(server, SMBrmdir, 0, 0)) < 0) + { + if (smb_retry(server)) + { + goto retry; + } + } + smb_unlock_server(server); + return result; +} + +int +smb_proc_unlink(struct inode *dir, const char *name, const int len) +{ + char *p; + struct smb_server *server = SMB_SERVER(dir); + char *buf; + int result; + + smb_lock_server(server); + buf = server->packet; + + retry: + p = smb_setup_header(server, SMBunlink, 1, 0); + WSET(buf, smb_vwv0, aSYSTEM | aHIDDEN); + *p++ = 4; + p = smb_encode_path(server, p, SMB_INOP(dir), name, len); + smb_setup_bcc(server, p); + + if ((result = smb_request_ok(server, SMBunlink, 0, 0)) < 0) + { + if (smb_retry(server)) + { + goto retry; + } + } + smb_unlock_server(server); + return result; +} + +int +smb_proc_trunc(struct smb_server *server, word fid, dword length) +{ + char *p; + char *buf; + int result; + + smb_lock_server(server); + buf = server->packet; + + retry: + p = smb_setup_header(server, SMBwrite, 5, 0); + WSET(buf, smb_vwv0, fid); + WSET(buf, smb_vwv1, 0); + DSET(buf, smb_vwv2, length); + WSET(buf, smb_vwv4, 0); + p = smb_encode_ascii(p, "", 0); + smb_setup_bcc(server, p); + + if ((result = smb_request_ok(server, SMBwrite, 1, 0)) < 0) + { + if (smb_retry(server)) + { + goto retry; + } + } + smb_unlock_server(server); + return result; +} + +static void +smb_init_dirent(struct smb_server *server, struct smb_dirent *entry) +{ + memset(entry, 0, sizeof(struct smb_dirent)); + + entry->f_nlink = 1; + entry->f_uid = server->m.uid; + entry->f_gid = server->m.gid; + entry->f_blksize = 512; +} + +static void +smb_finish_dirent(struct smb_server *server, struct smb_dirent *entry) +{ + if ((entry->attr & aDIR) != 0) + { + entry->f_mode = server->m.dir_mode; + entry->f_size = 512; + } else + { + entry->f_mode = server->m.file_mode; + } + + if ((entry->f_blksize != 0) && (entry->f_size != 0)) + { + entry->f_blocks = + (entry->f_size - 1) / entry->f_blksize + 1; + } else + { + entry->f_blocks = 0; + } + return; +} + +void +smb_init_root_dirent(struct smb_server *server, struct smb_dirent *entry) +{ + smb_init_dirent(server, entry); + entry->attr = aDIR; + entry->f_ino = 1; + smb_finish_dirent(server, entry); +} + + +static char * +smb_decode_dirent(struct smb_server *server, char *p, struct smb_dirent *entry) +{ + smb_init_dirent(server, entry); + + p += SMB_STATUS_SIZE; /* reserved (search_status) */ + entry->attr = BVAL(p, 0); + entry->f_mtime = entry->f_atime = entry->f_ctime = + date_dos2unix(WVAL(p, 1), WVAL(p, 3)); + entry->f_size = DVAL(p, 5); + entry->len = strlen(p + 9); + if (entry->len > 12) + { + entry->len = 12; + } + memcpy(entry->name, p + 9, entry->len); + entry->name[entry->len] = '\0'; + while (entry->len > 2) + { + /* Pathworks fills names with spaces */ + entry->len -= 1; + if (entry->name[entry->len] == ' ') + { + entry->name[entry->len] = '\0'; + } + } + switch (server->case_handling) + { + case CASE_UPPER: + str_upper(entry->name); + break; + case CASE_LOWER: + str_lower(entry->name); + break; + default: + break; + } + DPRINTK("smb_decode_dirent: name = %s\n", entry->name); + smb_finish_dirent(server, entry); + return p + 22; +} + +/* This routine is used to read in directory entries from the network. + Note that it is for short directory name seeks, i.e.: protocol < + PROTOCOL_LANMAN2 */ + +static int +smb_proc_readdir_short(struct smb_server *server, struct inode *dir, int fpos, + int cache_size, struct smb_dirent *entry) +{ + char *p; + char *buf; + int error; + int result; + int i; + int first, total_count; + struct smb_dirent *current_entry; + word bcc; + word count; + char status[SMB_STATUS_SIZE]; + int entries_asked = (server->max_xmit - 100) / SMB_DIRINFO_SIZE; + + DPRINTK("SMB call readdir %d @ %d\n", cache_size, fpos); + + smb_lock_server(server); + buf = server->packet; + + retry: + first = 1; + total_count = 0; + current_entry = entry; + + while (1) + { + if (first == 1) + { + p = smb_setup_header(server, SMBsearch, 2, 0); + WSET(buf, smb_vwv0, entries_asked); + WSET(buf, smb_vwv1, aDIR); + *p++ = 4; + p = smb_encode_path(server, p, SMB_INOP(dir), "*.*", 3); + *p++ = 5; + WSET(p, 0, 0); + p += 2; + } else + { + p = smb_setup_header(server, SMBsearch, 2, 0); + WSET(buf, smb_vwv0, entries_asked); + WSET(buf, smb_vwv1, aDIR); + p = smb_encode_ascii(p, "", 0); + *p++ = 5; + WSET(p, 0, SMB_STATUS_SIZE); + p += 2; + memcpy(p, status, SMB_STATUS_SIZE); + p += SMB_STATUS_SIZE; + } + + smb_setup_bcc(server, p); + + if ((error = smb_request_ok(server, SMBsearch, 1, -1)) < 0) + { + if ((server->rcls == ERRDOS) + && (server->err == ERRnofiles)) + { + result = total_count - fpos; + goto unlock_return; + } else + { + if (smb_retry(server)) + { + goto retry; + } + result = error; + goto unlock_return; + } + } + p = SMB_VWV(server->packet); + p = smb_decode_word(p, &count); + p = smb_decode_word(p, &bcc); + + first = 0; + + if (count <= 0) + { + result = total_count - fpos; + goto unlock_return; + } + if (bcc != count * SMB_DIRINFO_SIZE + 3) + { + result = -EIO; + goto unlock_return; + } + p += 3; /* Skipping VBLOCK header + (5, length lo, length hi). */ + + /* Read the last entry into the status field. */ + memcpy(status, + SMB_BUF(server->packet) + 3 + + (count - 1) * SMB_DIRINFO_SIZE, + SMB_STATUS_SIZE); + + /* Now we are ready to parse smb directory entries. */ + + for (i = 0; i < count; i++) + { + if (total_count < fpos) + { + p += SMB_DIRINFO_SIZE; + DDPRINTK("smb_proc_readdir: skipped entry.\n"); + DDPRINTK(" total_count = %d\n" + " i = %d, fpos = %d\n", + total_count, i, fpos); + } else if (total_count >= fpos + cache_size) + { + result = total_count - fpos; + goto unlock_return; + } else + { + p = smb_decode_dirent(server, p, + current_entry); + current_entry->f_pos = total_count; + DDPRINTK("smb_proc_readdir: entry->f_pos = " + "%lu\n", entry->f_pos); + current_entry += 1; + } + total_count += 1; + } + } + unlock_return: + smb_unlock_server(server); + return result; +} + +/* interpret a long filename structure - this is mostly guesses at the + moment. The length of the structure is returned. The structure of + a long filename depends on the info level. 260 is used by NT and 2 + is used by OS/2. */ + +static char * +smb_decode_long_dirent(struct smb_server *server, char *p, + struct smb_dirent *entry, int level) +{ + char *result; + + smb_init_dirent(server, entry); + + switch (level) + { + /* We might add more levels later... */ + case 1: + entry->len = BVAL(p, 26); + strncpy(entry->name, p + 27, entry->len); + entry->name[entry->len] = '\0'; + entry->f_size = DVAL(p, 16); + entry->attr = BVAL(p, 24); + + entry->f_ctime = date_dos2unix(WVAL(p, 6), WVAL(p, 4)); + entry->f_atime = date_dos2unix(WVAL(p, 10), WVAL(p, 8)); + entry->f_mtime = date_dos2unix(WVAL(p, 14), WVAL(p, 12)); + result = p + 28 + BVAL(p, 26); + break; + + default: + DPRINTK("Unknown long filename format %d\n", level); + result = p + WVAL(p, 0); + } + + switch (server->case_handling) + { + case CASE_UPPER: + str_upper(entry->name); + break; + case CASE_LOWER: + str_lower(entry->name); + break; + default: + break; + } + + smb_finish_dirent(server, entry); + return result; +} + +int +smb_proc_readdir_long(struct smb_server *server, struct inode *dir, int fpos, + int cache_size, struct smb_dirent *cache) +{ + /* NT uses 260, OS/2 uses 2. Both accept 1. */ + const int info_level = 1; + const int max_matches = 512; + + char *p; + char *lastname; + int lastname_len; + int i; + int first, entries, entries_seen; + + unsigned char *resp_data = NULL; + unsigned char *resp_param = NULL; + int resp_data_len = 0; + int resp_param_len = 0; + + __u16 command; + + int result; + + int ff_resume_key = 0; + int ff_searchcount = 0; + int ff_eos = 0; + int ff_lastname = 0; + int ff_dir_handle = 0; + int loop_count = 0; + + char param[SMB_MAXPATHLEN + 2 + 12]; + int mask_len; + unsigned char *mask = &(param[12]); + + mask_len = smb_encode_path(server, mask, + SMB_INOP(dir), "*", 1) - mask; + + mask[mask_len] = 0; + mask[mask_len + 1] = 0; + + DPRINTK("smb_readdir_long cache=%d, fpos=%d, mask=%s\n", + cache_size, fpos, mask); + + smb_lock_server(server); + + retry: + + first = 1; + entries = 0; + entries_seen = 2; + + while (ff_eos == 0) + { + loop_count += 1; + if (loop_count > 200) + { + printk("smb_proc_readdir_long: " + "Looping in FIND_NEXT??\n"); + break; + } + if (first != 0) + { + command = TRANSACT2_FINDFIRST; + WSET(param, 0, aSYSTEM | aHIDDEN | aDIR); + WSET(param, 2, max_matches); /* max count */ + WSET(param, 4, 8 + 4 + 2); /* resume required + + close on end + + continue */ + WSET(param, 6, info_level); + DSET(param, 8, 0); + } else + { + command = TRANSACT2_FINDNEXT; + DPRINTK("hand=0x%X resume=%d ff_lastname=%d mask=%s\n", + ff_dir_handle, ff_resume_key, ff_lastname, mask); + WSET(param, 0, ff_dir_handle); + WSET(param, 2, max_matches); /* max count */ + WSET(param, 4, info_level); + DSET(param, 6, ff_resume_key); /* ff_resume_key */ + WSET(param, 10, 8 + 4 + 2); /* resume required + + close on end + + continue */ +#ifdef CONFIG_SMB_WIN95 + /* Windows 95 is not able to deliver answers + to FIND_NEXT fast enough, so sleep 0.2 seconds */ + current->timeout = jiffies + HZ / 5; + current->state = TASK_INTERRUPTIBLE; + schedule(); + current->timeout = 0; +#endif + } + + result = smb_trans2_request(server, command, + 0, NULL, 12 + mask_len + 2, param, + &resp_data_len, &resp_data, + &resp_param_len, &resp_param); + + if (result < 0) + { + if (smb_retry(server)) + { + goto retry; + } + DPRINTK("smb_proc_readdir_long: " + "got error from trans2_request\n"); + break; + } + if (server->rcls != 0) + { + result = -EIO; + break; + } + /* parse out some important return info */ + if (first != 0) + { + ff_dir_handle = WVAL(resp_param, 0); + ff_searchcount = WVAL(resp_param, 2); + ff_eos = WVAL(resp_param, 4); + ff_lastname = WVAL(resp_param, 8); + } else + { + ff_searchcount = WVAL(resp_param, 0); + ff_eos = WVAL(resp_param, 2); + ff_lastname = WVAL(resp_param, 6); + } + + if (ff_searchcount == 0) + { + break; + } + /* point to the data bytes */ + p = resp_data; + + /* we might need the lastname for continuations */ + lastname = ""; + lastname_len = 0; + if (ff_lastname > 0) + { + switch (info_level) + { + case 260: + lastname = p + ff_lastname; + lastname_len = resp_data_len - ff_lastname; + ff_resume_key = 0; + break; + case 1: + lastname = p + ff_lastname + 1; + lastname_len = BVAL(p, ff_lastname); + ff_resume_key = 0; + break; + } + } + lastname_len = min(lastname_len, 256); + strncpy(mask, lastname, lastname_len); + mask[lastname_len] = '\0'; + + /* Now we are ready to parse smb directory entries. */ + + for (i = 0; i < ff_searchcount; i++) + { + struct smb_dirent *entry = &(cache[entries]); + + p = smb_decode_long_dirent(server, p, + entry, info_level); + + DDPRINTK("smb_readdir_long: got %s\n", entry->name); + + if ((entry->name[0] == '.') + && ((entry->name[1] == '\0') + || ((entry->name[1] == '.') + && (entry->name[2] == '\0')))) + { + /* ignore . and .. from the server */ + continue; + } + if (entries_seen >= fpos) + { + entry->f_pos = entries_seen; + entries += 1; + } + if (entries >= cache_size) + { + goto finished; + } + entries_seen += 1; + } + + DPRINTK("received %d entries (eos=%d resume=%d)\n", + ff_searchcount, ff_eos, ff_resume_key); + + first = 0; + } + + finished: + smb_unlock_server(server); + return entries; +} + +int +smb_proc_readdir(struct smb_server *server, struct inode *dir, int fpos, + int cache_size, struct smb_dirent *entry) +{ + if (server->protocol >= PROTOCOL_LANMAN2) + return smb_proc_readdir_long(server, dir, fpos, cache_size, + entry); + else + return smb_proc_readdir_short(server, dir, fpos, cache_size, + entry); +} + +static int +smb_proc_getattr_core(struct inode *dir, const char *name, int len, + struct smb_dirent *entry) +{ + int result; + char *p; + struct smb_server *server = SMB_SERVER(dir); + char *buf; + + smb_lock_server(server); + buf = server->packet; + + DDPRINTK("smb_proc_getattr: %s\n", name); + + retry: + p = smb_setup_header(server, SMBgetatr, 0, 0); + *p++ = 4; + p = smb_encode_path(server, p, SMB_INOP(dir), name, len); + smb_setup_bcc(server, p); + + if ((result = smb_request_ok(server, SMBgetatr, 10, 0)) < 0) + { + if (smb_retry(server)) + { + goto retry; + } + smb_unlock_server(server); + return result; + } + entry->attr = WVAL(buf, smb_vwv0); + entry->f_ctime = entry->f_atime = + entry->f_mtime = local2utc(DVAL(buf, smb_vwv1)); + + entry->f_size = DVAL(buf, smb_vwv3); + smb_unlock_server(server); + return 0; +} + +static int +smb_proc_getattr_trans2(struct inode *dir, const char *name, int len, + struct smb_dirent *entry) +{ + struct smb_server *server = SMB_SERVER(dir); + char param[SMB_MAXPATHLEN + 20]; + char *p; + int result; + + unsigned char *resp_data = NULL; + unsigned char *resp_param = NULL; + int resp_data_len = 0; + int resp_param_len = 0; + + WSET(param, 0, 1); /* Info level SMB_INFO_STANDARD */ + DSET(param, 2, 0); + p = smb_encode_path(server, param + 6, SMB_INOP(dir), name, len); + + smb_lock_server(server); + retry: + result = smb_trans2_request(server, TRANSACT2_QPATHINFO, + 0, NULL, p - param, param, + &resp_data_len, &resp_data, + &resp_param_len, &resp_param); + + if (server->rcls != 0) + { + smb_unlock_server(server); + return -smb_errno(server->rcls, server->err); + } + if (result < 0) + { + if (smb_retry(server)) + { + goto retry; + } + smb_unlock_server(server); + return result; + } + if (resp_data_len < 22) + { + smb_unlock_server(server); + return -ENOENT; + } + entry->f_ctime = date_dos2unix(WVAL(resp_data, 2), + WVAL(resp_data, 0)); + entry->f_atime = date_dos2unix(WVAL(resp_data, 6), + WVAL(resp_data, 4)); + entry->f_mtime = date_dos2unix(WVAL(resp_data, 10), + WVAL(resp_data, 8)); + entry->f_size = DVAL(resp_data, 12); + entry->attr = WVAL(resp_data, 20); + smb_unlock_server(server); + + return 0; +} + +int +smb_proc_getattr(struct inode *dir, const char *name, int len, + struct smb_dirent *entry) +{ + struct smb_server *server = SMB_SERVER(dir); + int result = 0; + + smb_init_dirent(server, entry); + + if (server->protocol >= PROTOCOL_LANMAN2) + { + result = smb_proc_getattr_trans2(dir, name, len, entry); + } + if ((server->protocol < PROTOCOL_LANMAN2) || (result < 0)) + { + result = smb_proc_getattr_core(dir, name, len, entry); + } + smb_finish_dirent(server, entry); + + entry->len = len; + memcpy(entry->name, name, len); + /* entry->name is null terminated from smb_init_dirent */ + + return result; +} + + +/* In core protocol, there is only 1 time to be set, we use + entry->f_mtime, to make touch work. */ +static int +smb_proc_setattr_core(struct smb_server *server, + struct inode *i, struct smb_dirent *new_finfo) +{ + char *p; + char *buf; + int result; + + smb_lock_server(server); + buf = server->packet; + + retry: + p = smb_setup_header(server, SMBsetatr, 8, 0); + WSET(buf, smb_vwv0, new_finfo->attr); + DSET(buf, smb_vwv1, utc2local(new_finfo->f_mtime)); + *p++ = 4; + p = smb_encode_path(server, p, + SMB_INOP(i)->dir, SMB_INOP(i)->finfo.name, + SMB_INOP(i)->finfo.len); + p = smb_encode_ascii(p, "", 0); + + smb_setup_bcc(server, p); + if ((result = smb_request_ok(server, SMBsetatr, 0, 0)) < 0) + { + if (smb_retry(server)) + { + goto retry; + } + } + smb_unlock_server(server); + return result; +} + +static int +smb_proc_setattr_trans2(struct smb_server *server, + struct inode *i, struct smb_dirent *new_finfo) +{ + char param[SMB_MAXPATHLEN + 20]; + char data[26]; + char *p; + int result; + + unsigned char *resp_data = NULL; + unsigned char *resp_param = NULL; + int resp_data_len = 0; + int resp_param_len = 0; + + WSET(param, 0, 1); /* Info level SMB_INFO_STANDARD */ + DSET(param, 2, 0); + p = smb_encode_path(server, param + 6, + SMB_INOP(i)->dir, SMB_INOP(i)->finfo.name, + SMB_INOP(i)->finfo.len); + + date_unix2dos(new_finfo->f_ctime, &(data[0]), &(data[2])); + date_unix2dos(new_finfo->f_atime, &(data[4]), &(data[6])); + date_unix2dos(new_finfo->f_mtime, &(data[8]), &(data[10])); + DSET(data, 12, new_finfo->f_size); + DSET(data, 16, new_finfo->f_blksize); + WSET(data, 20, new_finfo->attr); + WSET(data, 22, 0); + + smb_lock_server(server); + retry: + result = smb_trans2_request(server, TRANSACT2_SETPATHINFO, + 26, data, p - param, param, + &resp_data_len, &resp_data, + &resp_param_len, &resp_param); + + if (server->rcls != 0) + { + smb_unlock_server(server); + return -smb_errno(server->rcls, server->err); + } + if (result < 0) + { + if (smb_retry(server)) + { + goto retry; + } + } + smb_unlock_server(server); + return 0; +} + +int +smb_proc_setattr(struct smb_server *server, struct inode *inode, + struct smb_dirent *new_finfo) +{ + int result; + + if (server->protocol >= PROTOCOL_LANMAN2) + { + result = smb_proc_setattr_trans2(server, inode, new_finfo); + } + if ((server->protocol < PROTOCOL_LANMAN2) || (result < 0)) + { + result = smb_proc_setattr_core(server, inode, new_finfo); + } + return result; +} + +int +smb_proc_dskattr(struct super_block *super, struct smb_dskattr *attr) +{ + int error; + char *p; + struct smb_server *server = &(SMB_SBP(super)->s_server); + + smb_lock_server(server); + + retry: + smb_setup_header(server, SMBdskattr, 0, 0); + + if ((error = smb_request_ok(server, SMBdskattr, 5, 0)) < 0) + { + if (smb_retry(server)) + { + goto retry; + } + smb_unlock_server(server); + return error; + } + p = SMB_VWV(server->packet); + p = smb_decode_word(p, &attr->total); + p = smb_decode_word(p, &attr->allocblocks); + p = smb_decode_word(p, &attr->blocksize); + p = smb_decode_word(p, &attr->free); + smb_unlock_server(server); + return 0; +} + +/*****************************************************************************/ +/* */ +/* Mount/umount operations. */ +/* */ +/*****************************************************************************/ + +struct smb_prots +{ + enum smb_protocol prot; + const char *name; +}; + +/* smb_proc_reconnect: We expect the server to be locked, so that you + can call the routine from within smb_retry. The socket must be + created, like after a user-level socket()-call. It may not be + connected. */ + +int +smb_proc_reconnect(struct smb_server *server) +{ + struct smb_prots prots[] = + { + {PROTOCOL_CORE, "PC NETWORK PROGRAM 1.0"}, + {PROTOCOL_COREPLUS, "MICROSOFT NETWORKS 1.03"}, +#ifdef LANMAN1 + {PROTOCOL_LANMAN1, "MICROSOFT NETWORKS 3.0"}, + {PROTOCOL_LANMAN1, "LANMAN1.0"}, +#endif +#ifdef LANMAN2 + {PROTOCOL_LANMAN2, "LM1.2X002"}, +#endif +#ifdef NT1 + {PROTOCOL_NT1, "NT LM 0.12"}, + {PROTOCOL_NT1, "NT LANMAN 1.0"}, +#endif + {-1, NULL}}; + char dev[] = "A:"; + int i, plength; + int max_xmit = 1024; /* Space needed for first request. */ + int given_max_xmit = server->m.max_xmit; + int result; + byte *p; + + if ((result = smb_connect(server)) < 0) + { + DPRINTK("smb_proc_reconnect: could not smb_connect\n"); + goto fail; + } + /* Here we assume that the connection is valid */ + server->state = CONN_VALID; + + if (server->packet != NULL) + { + smb_vfree(server->packet); + server->packet_size = 0; + } + server->packet = smb_vmalloc(max_xmit); + + if (server->packet == NULL) + { + printk("smb_proc_connect: No memory! Bailing out.\n"); + result = -ENOMEM; + goto fail; + } + server->packet_size = server->max_xmit = max_xmit; + + /* + * Start with an RFC1002 session request packet. + */ + p = server->packet + 4; + + p = smb_name_mangle(p, server->m.server_name); + p = smb_name_mangle(p, server->m.client_name); + + smb_encode_smb_length(server->packet, + (void *) p - (void *) (server->packet)); + + server->packet[0] = 0x81; /* SESSION REQUEST */ + + if (smb_catch_keepalive(server) < 0) + { + printk("smb_proc_connect: could not catch_keepalives\n"); + } + if ((result = smb_request(server)) < 0) + { + DPRINTK("smb_proc_connect: Failed to send SESSION REQUEST.\n"); + smb_dont_catch_keepalive(server); + goto fail; + } + if (server->packet[0] != 0x82) + { + printk("smb_proc_connect: Did not receive positive response " + "(err = %x)\n", + server->packet[0]); + smb_dont_catch_keepalive(server); + result = -EIO; + goto fail; + } + DPRINTK("smb_proc_connect: Passed SESSION REQUEST.\n"); + + /* Now we are ready to send a SMB Negotiate Protocol packet. */ + memset(server->packet, 0, SMB_HEADER_LEN); + + plength = 0; + for (i = 0; prots[i].name != NULL; i++) + { + plength += strlen(prots[i].name) + 2; + } + + smb_setup_header(server, SMBnegprot, 0, plength); + + p = SMB_BUF(server->packet); + + for (i = 0; prots[i].name != NULL; i++) + { + *p++ = 2; + strcpy(p, prots[i].name); + p += strlen(prots[i].name) + 1; + } + + if ((result = smb_request_ok(server, SMBnegprot, 1, -1)) < 0) + { + DPRINTK("smb_proc_connect: Failure requesting SMBnegprot\n"); + smb_dont_catch_keepalive(server); + goto fail; + } else + { + DDPRINTK("smb_proc_connect: Request SMBnegprot.."); + } + + DDPRINTK("Verified!\n"); + + p = SMB_VWV(server->packet); + p = smb_decode_word(p, (word *) & i); + server->protocol = prots[i].prot; + + DPRINTK("smb_proc_connect: Server wants %s protocol.\n", + prots[i].name); + + if (server->protocol >= PROTOCOL_LANMAN1) + { + + word passlen = strlen(server->m.password); + word userlen = strlen(server->m.username); + + DPRINTK("smb_proc_connect: password = %s\n", + server->m.password); + DPRINTK("smb_proc_connect: usernam = %s\n", + server->m.username); + DPRINTK("smb_proc_connect: blkmode = %d\n", + WVAL(server->packet, smb_vwv5)); + + if (server->protocol >= PROTOCOL_NT1) + { + server->max_xmit = DVAL(server->packet, smb_vwv3 + 1); + server->maxmux = WVAL(server->packet, smb_vwv1 + 1); + server->maxvcs = WVAL(server->packet, smb_vwv2 + 1); + server->blkmode = DVAL(server->packet, smb_vwv9 + 1); + server->sesskey = DVAL(server->packet, smb_vwv7 + 1); + } else + { + server->max_xmit = WVAL(server->packet, smb_vwv2); + server->maxmux = WVAL(server->packet, smb_vwv3); + server->maxvcs = WVAL(server->packet, smb_vwv4); + server->blkmode = WVAL(server->packet, smb_vwv5); + server->sesskey = DVAL(server->packet, smb_vwv6); + } + + if (server->max_xmit < given_max_xmit) + { + /* We do not distinguish between the client + requests and the server response. */ + given_max_xmit = server->max_xmit; + } + if (server->protocol >= PROTOCOL_NT1) + { + char *workgroup = server->m.domain; + char *OS_id = "Unix"; + char *client_id = "ksmbfs"; + + smb_setup_header(server, SMBsesssetupX, 13, + 5 + userlen + passlen + + strlen(workgroup) + strlen(OS_id) + + strlen(client_id)); + + WSET(server->packet, smb_vwv0, 0x00ff); + WSET(server->packet, smb_vwv1, 0); + WSET(server->packet, smb_vwv2, given_max_xmit); + WSET(server->packet, smb_vwv3, 2); + WSET(server->packet, smb_vwv4, server->pid); + DSET(server->packet, smb_vwv5, server->sesskey); + WSET(server->packet, smb_vwv7, passlen + 1); + WSET(server->packet, smb_vwv8, 0); + WSET(server->packet, smb_vwv9, 0); + + p = SMB_BUF(server->packet); + strcpy(p, server->m.password); + p += passlen + 1; + strcpy(p, server->m.username); + p += userlen + 1; + strcpy(p, workgroup); + p += strlen(p) + 1; + strcpy(p, OS_id); + p += strlen(p) + 1; + strcpy(p, client_id); + } else + { + smb_setup_header(server, SMBsesssetupX, 10, + 2 + userlen + passlen); + + WSET(server->packet, smb_vwv0, 0x00ff); + WSET(server->packet, smb_vwv1, 0); + WSET(server->packet, smb_vwv2, given_max_xmit); + WSET(server->packet, smb_vwv3, 2); + WSET(server->packet, smb_vwv4, server->pid); + DSET(server->packet, smb_vwv5, server->sesskey); + WSET(server->packet, smb_vwv7, passlen + 1); + WSET(server->packet, smb_vwv8, 0); + WSET(server->packet, smb_vwv9, 0); + + p = SMB_BUF(server->packet); + strcpy(p, server->m.password); + p += passlen + 1; + strcpy(p, server->m.username); + } + + if ((result = smb_request_ok(server, SMBsesssetupX, 3, 0)) < 0) + { + DPRINTK("smb_proc_connect: SMBsessetupX failed\n"); + smb_dont_catch_keepalive(server); + goto fail; + } + smb_decode_word(server->packet + 32, &(server->server_uid)); + } else + { + server->max_xmit = 0; + server->maxmux = 0; + server->maxvcs = 0; + server->blkmode = 0; + server->sesskey = 0; + } + + /* Fine! We have a connection, send a tcon message. */ + + smb_setup_header(server, SMBtcon, 0, + 6 + strlen(server->m.service) + + strlen(server->m.password) + strlen(dev)); + + p = SMB_BUF(server->packet); + p = smb_encode_ascii(p, server->m.service, strlen(server->m.service)); + p = smb_encode_ascii(p, server->m.password, strlen(server->m.password)); + p = smb_encode_ascii(p, dev, strlen(dev)); + + if ((result = smb_request_ok(server, SMBtcon, 2, 0)) < 0) + { + DPRINTK("smb_proc_connect: SMBtcon not verified.\n"); + smb_dont_catch_keepalive(server); + goto fail; + } + DDPRINTK("OK! Managed to set up SMBtcon!\n"); + + p = SMB_VWV(server->packet); + + if (server->protocol <= PROTOCOL_COREPLUS) + { + word max_xmit; + + p = smb_decode_word(p, &max_xmit); + server->max_xmit = max_xmit; + + if (server->max_xmit > given_max_xmit) + { + server->max_xmit = given_max_xmit; + } + } else + { + p += 2; + } + + p = smb_decode_word(p, &server->tid); + + /* Ok, everything is fine. max_xmit does not include */ + /* the TCP-SMB header of 4 bytes. */ + server->max_xmit += 4; + + DPRINTK("max_xmit = %d, tid = %d\n", server->max_xmit, server->tid); + + /* Now make a new packet with the correct size. */ + smb_vfree(server->packet); + + server->packet = smb_vmalloc(server->max_xmit); + if (server->packet == NULL) + { + printk("smb_proc_connect: No memory left in end of " + "connection phase :-(\n"); + smb_dont_catch_keepalive(server); + goto fail; + } + server->packet_size = server->max_xmit; + + DPRINTK("smb_proc_connect: Normal exit\n"); + return 0; + + fail: + server->state = CONN_INVALID; + return result; +} + +/* smb_proc_reconnect: server->packet is allocated with + server->max_xmit bytes if and only if we return >= 0 */ +int +smb_proc_connect(struct smb_server *server) +{ + int result; + smb_lock_server(server); + + result = smb_proc_reconnect(server); + + if ((result < 0) && (server->packet != NULL)) + { + smb_vfree(server->packet); + server->packet = NULL; + } + smb_unlock_server(server); + return result; +} + +int +smb_proc_disconnect(struct smb_server *server) +{ + smb_setup_header_exclusive(server, SMBtdis, 0, 0); + return smb_request_ok_unlock(server, SMBtdis, 0, 0); +} diff --git a/fs/smbfs/sock.c b/fs/smbfs/sock.c new file mode 100644 index 000000000..ca6d8c269 --- /dev/null +++ b/fs/smbfs/sock.c @@ -0,0 +1,714 @@ +/* + * sock.c + * + * Copyright (C) 1995, 1996 by Paal-Kr. Engstad and Volker Lendecke + * + */ + +#include <linux/sched.h> +#include <linux/smb_fs.h> +#include <linux/errno.h> +#include <linux/socket.h> +#include <linux/fcntl.h> +#include <linux/stat.h> +#include <linux/in.h> +#include <linux/net.h> +#include <linux/mm.h> +#include <linux/netdevice.h> +#include <net/ip.h> + +#include <linux/smb.h> +#include <linux/smbno.h> + +#include <asm/uaccess.h> + +#define _S(nr) (1<<((nr)-1)) + +static int +_recvfrom(struct socket *sock, unsigned char *ubuf, int size, + int noblock, unsigned flags, struct sockaddr_in *sa, int *addr_len) +{ + struct iovec iov; + struct msghdr msg; + + iov.iov_base = ubuf; + iov.iov_len = size; + + msg.msg_name = (void *) sa; + msg.msg_namelen = 0; + if (addr_len) + msg.msg_namelen = *addr_len; + msg.msg_control = NULL; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + return sock->ops->recvmsg(sock, &msg, size, noblock, flags, addr_len); +} + +static int +_send(struct socket *sock, const void *buff, int len, + int nonblock, unsigned flags) +{ + struct iovec iov; + struct msghdr msg; + + iov.iov_base = (void *) buff; + iov.iov_len = len; + + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_control = NULL; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + return sock->ops->sendmsg(sock, &msg, len, nonblock, flags); +} + +static void +smb_data_callback(struct sock *sk, int len) +{ + struct socket *sock = sk->socket; + + if (!sk->dead) + { + unsigned char peek_buf[4]; + int result; + unsigned short fs; + + fs = get_fs(); + set_fs(get_ds()); + + result = _recvfrom(sock, (void *) peek_buf, 1, 1, + MSG_PEEK, NULL, NULL); + + while ((result != -EAGAIN) && (peek_buf[0] == 0x85)) + { + /* got SESSION KEEP ALIVE */ + result = _recvfrom(sock, (void *) peek_buf, + 4, 1, 0, NULL, NULL); + + DDPRINTK("smb_data_callback:" + " got SESSION KEEP ALIVE\n"); + + if (result == -EAGAIN) + { + break; + } + result = _recvfrom(sock, (void *) peek_buf, + 1, 1, MSG_PEEK, + NULL, NULL); + } + set_fs(fs); + + if (result != -EAGAIN) + { + wake_up_interruptible(sk->sleep); + } + } +} + +int +smb_catch_keepalive(struct smb_server *server) +{ + struct file *file; + struct inode *inode; + struct socket *sock; + struct sock *sk; + + if ((server == NULL) + || ((file = server->sock_file) == NULL) + || ((inode = file->f_inode) == NULL) + || (!S_ISSOCK(inode->i_mode))) + { + printk("smb_catch_keepalive: did not get valid server!\n"); + server->data_ready = NULL; + return -EINVAL; + } + sock = &(inode->u.socket_i); + + if (sock->type != SOCK_STREAM) + { + printk("smb_catch_keepalive: did not get SOCK_STREAM\n"); + server->data_ready = NULL; + return -EINVAL; + } + sk = (struct sock *) (sock->data); + + if (sk == NULL) + { + printk("smb_catch_keepalive: sk == NULL"); + server->data_ready = NULL; + return -EINVAL; + } + DDPRINTK("smb_catch_keepalive.: sk->d_r = %x, server->d_r = %x\n", + (unsigned int) (sk->data_ready), + (unsigned int) (server->data_ready)); + + if (sk->data_ready == smb_data_callback) + { + printk("smb_catch_keepalive: already done\n"); + return -EINVAL; + } + server->data_ready = sk->data_ready; + sk->data_ready = smb_data_callback; + return 0; +} + +int +smb_dont_catch_keepalive(struct smb_server *server) +{ + struct file *file; + struct inode *inode; + struct socket *sock; + struct sock *sk; + + if ((server == NULL) + || ((file = server->sock_file) == NULL) + || ((inode = file->f_inode) == NULL) + || (!S_ISSOCK(inode->i_mode))) + { + printk("smb_dont_catch_keepalive: " + "did not get valid server!\n"); + return -EINVAL; + } + sock = &(inode->u.socket_i); + + if (sock->type != SOCK_STREAM) + { + printk("smb_dont_catch_keepalive: did not get SOCK_STREAM\n"); + return -EINVAL; + } + sk = (struct sock *) (sock->data); + + if (sk == NULL) + { + printk("smb_dont_catch_keepalive: sk == NULL"); + return -EINVAL; + } + if (server->data_ready == NULL) + { + printk("smb_dont_catch_keepalive: " + "server->data_ready == NULL\n"); + return -EINVAL; + } + if (sk->data_ready != smb_data_callback) + { + printk("smb_dont_catch_keepalive: " + "sk->data_callback != smb_data_callback\n"); + return -EINVAL; + } + DDPRINTK("smb_dont_catch_keepalive: sk->d_r = %x, server->d_r = %x\n", + (unsigned int) (sk->data_ready), + (unsigned int) (server->data_ready)); + + sk->data_ready = server->data_ready; + server->data_ready = NULL; + return 0; +} + +static int +smb_send_raw(struct socket *sock, unsigned char *source, int length) +{ + int result; + int already_sent = 0; + + while (already_sent < length) + { + result = _send(sock, + (void *) (source + already_sent), + length - already_sent, 0, 0); + + if (result < 0) + { + DPRINTK("smb_send_raw: sendto error = %d\n", + -result); + return result; + } + already_sent += result; + } + return already_sent; +} + +static int +smb_receive_raw(struct socket *sock, unsigned char *target, int length) +{ + int result; + int already_read = 0; + + while (already_read < length) + { + result = _recvfrom(sock, + (void *) (target + already_read), + length - already_read, 0, 0, + NULL, NULL); + + if (result < 0) + { + DPRINTK("smb_receive_raw: recvfrom error = %d\n", + -result); + return result; + } + already_read += result; + } + return already_read; +} + +static int +smb_get_length(struct socket *sock, unsigned char *header) +{ + int result; + unsigned char peek_buf[4]; + unsigned short fs; + + re_recv: + fs = get_fs(); + set_fs(get_ds()); + result = smb_receive_raw(sock, peek_buf, 4); + set_fs(fs); + + if (result < 0) + { + DPRINTK("smb_get_length: recv error = %d\n", -result); + return result; + } + switch (peek_buf[0]) + { + case 0x00: + case 0x82: + break; + + case 0x85: + DPRINTK("smb_get_length: Got SESSION KEEP ALIVE\n"); + goto re_recv; + + default: + printk("smb_get_length: Invalid NBT packet\n"); + return -EIO; + } + + if (header != NULL) + { + memcpy(header, peek_buf, 4); + } + /* The length in the RFC NB header is the raw data length */ + return smb_len(peek_buf); +} + +static struct socket * +server_sock(struct smb_server *server) +{ + struct file *file; + struct inode *inode; + + if (server == NULL) + return NULL; + if ((file = server->sock_file) == NULL) + return NULL; + if ((inode = file->f_inode) == NULL) + return NULL; + return &(inode->u.socket_i); +} + +/* + * smb_receive + * fs points to the correct segment + */ +static int +smb_receive(struct smb_server *server) +{ + struct socket *sock = server_sock(server); + int len; + int result; + unsigned char peek_buf[4]; + + len = smb_get_length(sock, peek_buf); + + if (len < 0) + { + return len; + } + if (len + 4 > server->packet_size) + { + /* Some servers do not care about our max_xmit. They + send larger packets */ + DPRINTK("smb_receive: Increase packet size from %d to %d\n", + server->packet_size, len + 4); + smb_vfree(server->packet); + server->packet_size = 0; + server->packet = smb_vmalloc(len + 4); + if (server->packet == NULL) + { + return -ENOMEM; + } + server->packet_size = len + 4; + } + memcpy(server->packet, peek_buf, 4); + result = smb_receive_raw(sock, server->packet + 4, len); + + if (result < 0) + { + printk("smb_receive: receive error: %d\n", result); + return result; + } + server->rcls = BVAL(server->packet, 9); + server->err = WVAL(server->packet, 11); + + if (server->rcls != 0) + { + DPRINTK("smb_receive: rcls=%d, err=%d\n", + server->rcls, server->err); + } + return result; +} + +static int +smb_receive_trans2(struct smb_server *server, + int *ldata, unsigned char **data, + int *lparam, unsigned char **param) +{ + int total_data = 0; + int total_param = 0; + int result; + unsigned char *inbuf = server->packet; + unsigned char *rcv_buf; + int buf_len; + int data_len = 0; + int param_len = 0; + + if ((result = smb_receive(server)) < 0) + { + return result; + } + if (server->rcls != 0) + { + *param = *data = server->packet; + *ldata = *lparam = 0; + return 0; + } + total_data = WVAL(inbuf, smb_tdrcnt); + total_param = WVAL(inbuf, smb_tprcnt); + + DDPRINTK("smb_receive_trans2: td=%d,tp=%d\n", total_data, total_param); + + if ((total_data > TRANS2_MAX_TRANSFER) + || (total_param > TRANS2_MAX_TRANSFER)) + { + DPRINTK("smb_receive_trans2: data/param too long\n"); + return -EIO; + } + buf_len = total_data + total_param; + if (server->packet_size > buf_len) + { + buf_len = server->packet_size; + } + if ((rcv_buf = smb_vmalloc(buf_len)) == NULL) + { + DPRINTK("smb_receive_trans2: could not alloc data area\n"); + return -ENOMEM; + } + *param = rcv_buf; + *data = rcv_buf + total_param; + + while (1) + { + if (WVAL(inbuf, smb_prdisp) + WVAL(inbuf, smb_prcnt) + > total_param) + { + DPRINTK("smb_receive_trans2: invalid parameters\n"); + result = -EIO; + goto fail; + } + memcpy(*param + WVAL(inbuf, smb_prdisp), + smb_base(inbuf) + WVAL(inbuf, smb_proff), + WVAL(inbuf, smb_prcnt)); + param_len += WVAL(inbuf, smb_prcnt); + + if (WVAL(inbuf, smb_drdisp) + WVAL(inbuf, smb_drcnt) + > total_data) + { + DPRINTK("smb_receive_trans2: invalid data block\n"); + result = -EIO; + goto fail; + } + DDPRINTK("target: %X\n", *data + WVAL(inbuf, smb_drdisp)); + DDPRINTK("source: %X\n", + smb_base(inbuf) + WVAL(inbuf, smb_droff)); + DDPRINTK("disp: %d, off: %d, cnt: %d\n", + WVAL(inbuf, smb_drdisp), WVAL(inbuf, smb_droff), + WVAL(inbuf, smb_drcnt)); + + memcpy(*data + WVAL(inbuf, smb_drdisp), + smb_base(inbuf) + WVAL(inbuf, smb_droff), + WVAL(inbuf, smb_drcnt)); + data_len += WVAL(inbuf, smb_drcnt); + + if ((WVAL(inbuf, smb_tdrcnt) > total_data) + || (WVAL(inbuf, smb_tprcnt) > total_param)) + { + printk("smb_receive_trans2: data/params grew!\n"); + result = -EIO; + goto fail; + } + /* the total lengths might shrink! */ + total_data = WVAL(inbuf, smb_tdrcnt); + total_param = WVAL(inbuf, smb_tprcnt); + + if ((data_len >= total_data) && (param_len >= total_param)) + { + break; + } + if ((result = smb_receive(server)) < 0) + { + goto fail; + } + if (server->rcls != 0) + { + result = -EIO; + goto fail; + } + } + *ldata = data_len; + *lparam = param_len; + + smb_vfree(server->packet); + server->packet = rcv_buf; + server->packet_size = buf_len; + return 0; + + fail: + smb_vfree(rcv_buf); + return result; +} + +int +smb_release(struct smb_server *server) +{ + struct socket *sock = server_sock(server); + int result; + + if (sock == NULL) + { + return -EINVAL; + } + result = sock->ops->release(sock, NULL); + DPRINTK("smb_release: sock->ops->release = %d\n", result); + + /* inet_release does not set sock->state. Maybe someone is + confused about sock->state being SS_CONNECTED while there + is nothing behind it, so I set it to SS_UNCONNECTED. */ + sock->state = SS_UNCONNECTED; + + result = sock->ops->create(sock, 0); + DPRINTK("smb_release: sock->ops->create = %d\n", result); + return result; +} + +int +smb_connect(struct smb_server *server) +{ + struct socket *sock = server_sock(server); + if (sock == NULL) + { + return -EINVAL; + } + if (sock->state != SS_UNCONNECTED) + { + DPRINTK("smb_connect: socket is not unconnected: %d\n", + sock->state); + } + return sock->ops->connect(sock, (struct sockaddr *) &(server->m.addr), + sizeof(struct sockaddr_in), 0); +} + +int +smb_request(struct smb_server *server) +{ + unsigned long old_mask; + unsigned short fs; + int len, result; + + unsigned char *buffer = (server == NULL) ? NULL : server->packet; + + if (buffer == NULL) + { + printk("smb_request: Bad server!\n"); + return -EBADF; + } + if (server->state != CONN_VALID) + { + return -EIO; + } + if ((result = smb_dont_catch_keepalive(server)) != 0) + { + server->state = CONN_INVALID; + smb_invalidate_all_inodes(server); + return result; + } + len = smb_len(buffer) + 4; + + DDPRINTK("smb_request: len = %d cmd = 0x%X\n", len, buffer[8]); + + old_mask = current->blocked; + current->blocked |= ~(_S(SIGKILL) | _S(SIGSTOP)); + fs = get_fs(); + set_fs(get_ds()); + + result = smb_send_raw(server_sock(server), (void *) buffer, len); + if (result > 0) + { + result = smb_receive(server); + } + /* read/write errors are handled by errno */ + current->signal &= ~_S(SIGPIPE); + current->blocked = old_mask; + set_fs(fs); + + if (result >= 0) + { + int result2 = smb_catch_keepalive(server); + if (result2 < 0) + { + result = result2; + } + } + if (result < 0) + { + server->state = CONN_INVALID; + smb_invalidate_all_inodes(server); + } + DDPRINTK("smb_request: result = %d\n", result); + + return result; +} + +#define ROUND_UP(x) (((x)+3) & ~3) +static int +smb_send_trans2(struct smb_server *server, __u16 trans2_command, + int ldata, unsigned char *data, + int lparam, unsigned char *param) +{ + struct socket *sock = server_sock(server); + + /* 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 oparam = + ROUND_UP(SMB_HEADER_LEN + 2 * smb_parameters + 2 + 3); + const int odata = + ROUND_UP(oparam + lparam); + const int bcc = + odata + ldata - (SMB_HEADER_LEN + 2 * smb_parameters + 2); + const int packet_length = + SMB_HEADER_LEN + 2 * smb_parameters + bcc + 2; + + unsigned char padding[4] = + {0,}; + char *p; + + struct iovec iov[4]; + struct msghdr msg; + + if ((bcc + oparam) > server->max_xmit) + { + return -ENOMEM; + } + p = smb_setup_header(server, SMBtrans2, smb_parameters, bcc); + + WSET(server->packet, smb_tpscnt, lparam); + WSET(server->packet, smb_tdscnt, ldata); + WSET(server->packet, smb_mprcnt, TRANS2_MAX_TRANSFER); + WSET(server->packet, smb_mdrcnt, TRANS2_MAX_TRANSFER); + WSET(server->packet, smb_msrcnt, 0); + WSET(server->packet, smb_flags, 0); + DSET(server->packet, smb_timeout, 0); + WSET(server->packet, smb_pscnt, lparam); + WSET(server->packet, smb_psoff, oparam - 4); + WSET(server->packet, smb_dscnt, ldata); + WSET(server->packet, smb_dsoff, odata - 4); + WSET(server->packet, smb_suwcnt, 1); + WSET(server->packet, smb_setup0, trans2_command); + *p++ = 0; /* null smb_name for trans2 */ + *p++ = 'D'; /* this was added because OS/2 does it */ + *p++ = ' '; + + iov[0].iov_base = (void *) server->packet; + iov[0].iov_len = oparam; + iov[1].iov_base = (param == NULL) ? padding : param; + iov[1].iov_len = lparam; + iov[2].iov_base = padding; + iov[2].iov_len = odata - oparam - lparam; + iov[3].iov_base = (data == NULL) ? padding : data; + iov[3].iov_len = ldata; + + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_control = NULL; + msg.msg_iov = iov; + msg.msg_iovlen = 4; + + return sock->ops->sendmsg(sock, &msg, packet_length, 0, 0); +} + +/* + * This is not really a trans2 request, we assume that you only have + * one packet to send. + */ +int +smb_trans2_request(struct smb_server *server, __u16 trans2_command, + int ldata, unsigned char *data, + int lparam, unsigned char *param, + int *lrdata, unsigned char **rdata, + int *lrparam, unsigned char **rparam) +{ + unsigned long old_mask; + unsigned short fs; + int result; + + DDPRINTK("smb_trans2_request: com=%d, ld=%d, lp=%d\n", + trans2_command, ldata, lparam); + + if (server->state != CONN_VALID) + { + return -EIO; + } + if ((result = smb_dont_catch_keepalive(server)) != 0) + { + server->state = CONN_INVALID; + smb_invalidate_all_inodes(server); + return result; + } + old_mask = current->blocked; + current->blocked |= ~(_S(SIGKILL) | _S(SIGSTOP)); + fs = get_fs(); + set_fs(get_ds()); + + result = smb_send_trans2(server, trans2_command, + ldata, data, lparam, param); + if (result >= 0) + { + result = smb_receive_trans2(server, + lrdata, rdata, lrparam, rparam); + } + /* read/write errors are handled by errno */ + current->signal &= ~_S(SIGPIPE); + current->blocked = old_mask; + set_fs(fs); + + if (result >= 0) + { + int result2 = smb_catch_keepalive(server); + if (result2 < 0) + { + result = result2; + } + } + if (result < 0) + { + server->state = CONN_INVALID; + smb_invalidate_all_inodes(server); + } + DDPRINTK("smb_trans2_request: result = %d\n", result); + + return result; +} |