/*
* 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 <asm/segment.h>
#include <asm/semaphore.h>
#include <linux/errno.h>
static int
smb_dir_read(struct inode *inode, struct file *filp, char *buf, int 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 int
smb_dir_read(struct inode *inode, struct file *filp, char *buf, int 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))))
{
printk("smb_iget: iget failed!");
/*
* If we blocked in iget(), another task may have referenced
* the info structure ... clean up with smb_free_inode_info.
*/
smb_free_inode_info(new_inode_info);
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;
}
if (new_inode_info == NULL)
{
iput(dir);
return -ENOMEM;
}
new_inode_info->finfo = finfo;
DPRINTK("attr: %x\n", finfo.attr);
if ((*result = smb_iget(dir, new_inode_info)) == NULL)
{
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)
{
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;
}