 5e993e2534
			
		
	
	
	5e993e2534
	
	
	
		
			
			What we want is to have non-counting references to children in pagecache of parent directory, and avoid picking them after a child has been freed. Fine, so let's just have ->d_prune() clear parent's inode "has directory contents in page cache" flag. That way we don't need ->d_fsdata for storing offsets, so we can use it as a quick and dirty "is it referenced from page cache" flag. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
		
			
				
	
	
		
			1237 lines
		
	
	
	
		
			31 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1237 lines
		
	
	
	
		
			31 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  *  dir.c
 | |
|  *
 | |
|  *  Copyright (C) 1995, 1996 by Volker Lendecke
 | |
|  *  Modified for big endian by J.F. Chadima and David S. Miller
 | |
|  *  Modified 1997 Peter Waltenberg, Bill Hawes, David Woodhouse for 2.1 dcache
 | |
|  *  Modified 1998, 1999 Wolfram Pienkoss for NLS
 | |
|  *  Modified 1999 Wolfram Pienkoss for directory caching
 | |
|  *  Modified 2000 Ben Harris, University of Cambridge for NFS NS meta-info
 | |
|  *
 | |
|  */
 | |
| 
 | |
| 
 | |
| #include <linux/time.h>
 | |
| #include <linux/errno.h>
 | |
| #include <linux/stat.h>
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/vmalloc.h>
 | |
| #include <linux/mm.h>
 | |
| #include <linux/namei.h>
 | |
| #include <asm/uaccess.h>
 | |
| #include <asm/byteorder.h>
 | |
| 
 | |
| #include "ncp_fs.h"
 | |
| 
 | |
| static void ncp_read_volume_list(struct file *, struct dir_context *,
 | |
| 				struct ncp_cache_control *);
 | |
| static void ncp_do_readdir(struct file *, struct dir_context *,
 | |
| 				struct ncp_cache_control *);
 | |
| 
 | |
| static int ncp_readdir(struct file *, struct dir_context *);
 | |
| 
 | |
| static int ncp_create(struct inode *, struct dentry *, umode_t, bool);
 | |
| static struct dentry *ncp_lookup(struct inode *, struct dentry *, unsigned int);
 | |
| static int ncp_unlink(struct inode *, struct dentry *);
 | |
| static int ncp_mkdir(struct inode *, struct dentry *, umode_t);
 | |
| static int ncp_rmdir(struct inode *, struct dentry *);
 | |
| static int ncp_rename(struct inode *, struct dentry *,
 | |
| 	  	      struct inode *, struct dentry *);
 | |
| static int ncp_mknod(struct inode * dir, struct dentry *dentry,
 | |
| 		     umode_t mode, dev_t rdev);
 | |
| #if defined(CONFIG_NCPFS_EXTRAS) || defined(CONFIG_NCPFS_NFS_NS)
 | |
| extern int ncp_symlink(struct inode *, struct dentry *, const char *);
 | |
| #else
 | |
| #define ncp_symlink NULL
 | |
| #endif
 | |
| 		      
 | |
| const struct file_operations ncp_dir_operations =
 | |
| {
 | |
| 	.llseek		= generic_file_llseek,
 | |
| 	.read		= generic_read_dir,
 | |
| 	.iterate	= ncp_readdir,
 | |
| 	.unlocked_ioctl	= ncp_ioctl,
 | |
| #ifdef CONFIG_COMPAT
 | |
| 	.compat_ioctl	= ncp_compat_ioctl,
 | |
| #endif
 | |
| };
 | |
| 
 | |
| const struct inode_operations ncp_dir_inode_operations =
 | |
| {
 | |
| 	.create		= ncp_create,
 | |
| 	.lookup		= ncp_lookup,
 | |
| 	.unlink		= ncp_unlink,
 | |
| 	.symlink	= ncp_symlink,
 | |
| 	.mkdir		= ncp_mkdir,
 | |
| 	.rmdir		= ncp_rmdir,
 | |
| 	.mknod		= ncp_mknod,
 | |
| 	.rename		= ncp_rename,
 | |
| 	.setattr	= ncp_notify_change,
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * Dentry operations routines
 | |
|  */
 | |
| static int ncp_lookup_validate(struct dentry *, unsigned int);
 | |
| static int ncp_hash_dentry(const struct dentry *, struct qstr *);
 | |
| static int ncp_compare_dentry(const struct dentry *, const struct dentry *,
 | |
| 		unsigned int, const char *, const struct qstr *);
 | |
| static int ncp_delete_dentry(const struct dentry *);
 | |
| static void ncp_d_prune(struct dentry *dentry);
 | |
| 
 | |
| const struct dentry_operations ncp_dentry_operations =
 | |
| {
 | |
| 	.d_revalidate	= ncp_lookup_validate,
 | |
| 	.d_hash		= ncp_hash_dentry,
 | |
| 	.d_compare	= ncp_compare_dentry,
 | |
| 	.d_delete	= ncp_delete_dentry,
 | |
| 	.d_prune	= ncp_d_prune,
 | |
| };
 | |
| 
 | |
| #define ncp_namespace(i)	(NCP_SERVER(i)->name_space[NCP_FINFO(i)->volNumber])
 | |
| 
 | |
| static inline int ncp_preserve_entry_case(struct inode *i, __u32 nscreator)
 | |
| {
 | |
| #ifdef CONFIG_NCPFS_SMALLDOS
 | |
| 	int ns = ncp_namespace(i);
 | |
| 
 | |
| 	if ((ns == NW_NS_DOS)
 | |
| #ifdef CONFIG_NCPFS_OS2_NS
 | |
| 		|| ((ns == NW_NS_OS2) && (nscreator == NW_NS_DOS))
 | |
| #endif /* CONFIG_NCPFS_OS2_NS */
 | |
| 	   )
 | |
| 		return 0;
 | |
| #endif /* CONFIG_NCPFS_SMALLDOS */
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| #define ncp_preserve_case(i)	(ncp_namespace(i) != NW_NS_DOS)
 | |
| 
 | |
| static inline int ncp_case_sensitive(const struct inode *i)
 | |
| {
 | |
| #ifdef CONFIG_NCPFS_NFS_NS
 | |
| 	return ncp_namespace(i) == NW_NS_NFS;
 | |
| #else
 | |
| 	return 0;
 | |
| #endif /* CONFIG_NCPFS_NFS_NS */
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Note: leave the hash unchanged if the directory
 | |
|  * is case-sensitive.
 | |
|  *
 | |
|  * Accessing the parent inode can be racy under RCU pathwalking.
 | |
|  * Use ACCESS_ONCE() to make sure we use _one_ particular inode,
 | |
|  * the callers will handle races.
 | |
|  */
 | |
| static int 
 | |
| ncp_hash_dentry(const struct dentry *dentry, struct qstr *this)
 | |
| {
 | |
| 	struct inode *inode = ACCESS_ONCE(dentry->d_inode);
 | |
| 
 | |
| 	if (!inode)
 | |
| 		return 0;
 | |
| 
 | |
| 	if (!ncp_case_sensitive(inode)) {
 | |
| 		struct super_block *sb = dentry->d_sb;
 | |
| 		struct nls_table *t;
 | |
| 		unsigned long hash;
 | |
| 		int i;
 | |
| 
 | |
| 		t = NCP_IO_TABLE(sb);
 | |
| 		hash = init_name_hash();
 | |
| 		for (i=0; i<this->len ; i++)
 | |
| 			hash = partial_name_hash(ncp_tolower(t, this->name[i]),
 | |
| 									hash);
 | |
| 		this->hash = end_name_hash(hash);
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Accessing the parent inode can be racy under RCU pathwalking.
 | |
|  * Use ACCESS_ONCE() to make sure we use _one_ particular inode,
 | |
|  * the callers will handle races.
 | |
|  */
 | |
| static int
 | |
| ncp_compare_dentry(const struct dentry *parent, const struct dentry *dentry,
 | |
| 		unsigned int len, const char *str, const struct qstr *name)
 | |
| {
 | |
| 	struct inode *pinode;
 | |
| 
 | |
| 	if (len != name->len)
 | |
| 		return 1;
 | |
| 
 | |
| 	pinode = ACCESS_ONCE(parent->d_inode);
 | |
| 	if (!pinode)
 | |
| 		return 1;
 | |
| 
 | |
| 	if (ncp_case_sensitive(pinode))
 | |
| 		return strncmp(str, name->name, len);
 | |
| 
 | |
| 	return ncp_strnicmp(NCP_IO_TABLE(pinode->i_sb), str, name->name, len);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * This is the callback from dput() when d_count is going to 0.
 | |
|  * We use this to unhash dentries with bad inodes.
 | |
|  * Closing files can be safely postponed until iput() - it's done there anyway.
 | |
|  */
 | |
| static int
 | |
| ncp_delete_dentry(const struct dentry * dentry)
 | |
| {
 | |
| 	struct inode *inode = dentry->d_inode;
 | |
| 
 | |
| 	if (inode) {
 | |
| 		if (is_bad_inode(inode))
 | |
| 			return 1;
 | |
| 	} else
 | |
| 	{
 | |
| 	/* N.B. Unhash negative dentries? */
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static inline int
 | |
| ncp_single_volume(struct ncp_server *server)
 | |
| {
 | |
| 	return (server->m.mounted_vol[0] != '\0');
 | |
| }
 | |
| 
 | |
| static inline int ncp_is_server_root(struct inode *inode)
 | |
| {
 | |
| 	return !ncp_single_volume(NCP_SERVER(inode)) &&
 | |
| 		is_root_inode(inode);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * This is the callback when the dcache has a lookup hit.
 | |
|  */
 | |
| 
 | |
| 
 | |
| #ifdef CONFIG_NCPFS_STRONG
 | |
| /* try to delete a readonly file (NW R bit set) */
 | |
| 
 | |
| static int
 | |
| ncp_force_unlink(struct inode *dir, struct dentry* dentry)
 | |
| {
 | |
|         int res=0x9c,res2;
 | |
| 	struct nw_modify_dos_info info;
 | |
| 	__le32 old_nwattr;
 | |
| 	struct inode *inode;
 | |
| 
 | |
| 	memset(&info, 0, sizeof(info));
 | |
| 	
 | |
|         /* remove the Read-Only flag on the NW server */
 | |
| 	inode = dentry->d_inode;
 | |
| 
 | |
| 	old_nwattr = NCP_FINFO(inode)->nwattr;
 | |
| 	info.attributes = old_nwattr & ~(aRONLY|aDELETEINHIBIT|aRENAMEINHIBIT);
 | |
| 	res2 = ncp_modify_file_or_subdir_dos_info_path(NCP_SERVER(inode), inode, NULL, DM_ATTRIBUTES, &info);
 | |
| 	if (res2)
 | |
| 		goto leave_me;
 | |
| 
 | |
|         /* now try again the delete operation */
 | |
|         res = ncp_del_file_or_subdir2(NCP_SERVER(dir), dentry);
 | |
| 
 | |
|         if (res)  /* delete failed, set R bit again */
 | |
|         {
 | |
| 		info.attributes = old_nwattr;
 | |
| 		res2 = ncp_modify_file_or_subdir_dos_info_path(NCP_SERVER(inode), inode, NULL, DM_ATTRIBUTES, &info);
 | |
| 		if (res2)
 | |
|                         goto leave_me;
 | |
|         }
 | |
| leave_me:
 | |
|         return(res);
 | |
| }
 | |
| #endif	/* CONFIG_NCPFS_STRONG */
 | |
| 
 | |
| #ifdef CONFIG_NCPFS_STRONG
 | |
| static int
 | |
| ncp_force_rename(struct inode *old_dir, struct dentry* old_dentry, char *_old_name,
 | |
|                  struct inode *new_dir, struct dentry* new_dentry, char *_new_name)
 | |
| {
 | |
| 	struct nw_modify_dos_info info;
 | |
|         int res=0x90,res2;
 | |
| 	struct inode *old_inode = old_dentry->d_inode;
 | |
| 	__le32 old_nwattr = NCP_FINFO(old_inode)->nwattr;
 | |
| 	__le32 new_nwattr = 0; /* shut compiler warning */
 | |
| 	int old_nwattr_changed = 0;
 | |
| 	int new_nwattr_changed = 0;
 | |
| 
 | |
| 	memset(&info, 0, sizeof(info));
 | |
| 	
 | |
|         /* remove the Read-Only flag on the NW server */
 | |
| 
 | |
| 	info.attributes = old_nwattr & ~(aRONLY|aRENAMEINHIBIT|aDELETEINHIBIT);
 | |
| 	res2 = ncp_modify_file_or_subdir_dos_info_path(NCP_SERVER(old_inode), old_inode, NULL, DM_ATTRIBUTES, &info);
 | |
| 	if (!res2)
 | |
| 		old_nwattr_changed = 1;
 | |
| 	if (new_dentry && new_dentry->d_inode) {
 | |
| 		new_nwattr = NCP_FINFO(new_dentry->d_inode)->nwattr;
 | |
| 		info.attributes = new_nwattr & ~(aRONLY|aRENAMEINHIBIT|aDELETEINHIBIT);
 | |
| 		res2 = ncp_modify_file_or_subdir_dos_info_path(NCP_SERVER(new_dir), new_dir, _new_name, DM_ATTRIBUTES, &info);
 | |
| 		if (!res2)
 | |
| 			new_nwattr_changed = 1;
 | |
| 	}
 | |
|         /* now try again the rename operation */
 | |
| 	/* but only if something really happened */
 | |
| 	if (new_nwattr_changed || old_nwattr_changed) {
 | |
| 	        res = ncp_ren_or_mov_file_or_subdir(NCP_SERVER(old_dir),
 | |
|         	                                    old_dir, _old_name,
 | |
|                 	                            new_dir, _new_name);
 | |
| 	} 
 | |
| 	if (res)
 | |
| 		goto leave_me;
 | |
| 	/* file was successfully renamed, so:
 | |
| 	   do not set attributes on old file - it no longer exists
 | |
| 	   copy attributes from old file to new */
 | |
| 	new_nwattr_changed = old_nwattr_changed;
 | |
| 	new_nwattr = old_nwattr;
 | |
| 	old_nwattr_changed = 0;
 | |
| 	
 | |
| leave_me:;
 | |
| 	if (old_nwattr_changed) {
 | |
| 		info.attributes = old_nwattr;
 | |
| 		res2 = ncp_modify_file_or_subdir_dos_info_path(NCP_SERVER(old_inode), old_inode, NULL, DM_ATTRIBUTES, &info);
 | |
| 		/* ignore errors */
 | |
| 	}
 | |
| 	if (new_nwattr_changed)	{
 | |
| 		info.attributes = new_nwattr;
 | |
| 		res2 = ncp_modify_file_or_subdir_dos_info_path(NCP_SERVER(new_dir), new_dir, _new_name, DM_ATTRIBUTES, &info);
 | |
| 		/* ignore errors */
 | |
| 	}
 | |
|         return(res);
 | |
| }
 | |
| #endif	/* CONFIG_NCPFS_STRONG */
 | |
| 
 | |
| 
 | |
| static int
 | |
| ncp_lookup_validate(struct dentry *dentry, unsigned int flags)
 | |
| {
 | |
| 	struct ncp_server *server;
 | |
| 	struct dentry *parent;
 | |
| 	struct inode *dir;
 | |
| 	struct ncp_entry_info finfo;
 | |
| 	int res, val = 0, len;
 | |
| 	__u8 __name[NCP_MAXPATHLEN + 1];
 | |
| 
 | |
| 	if (dentry == dentry->d_sb->s_root)
 | |
| 		return 1;
 | |
| 
 | |
| 	if (flags & LOOKUP_RCU)
 | |
| 		return -ECHILD;
 | |
| 
 | |
| 	parent = dget_parent(dentry);
 | |
| 	dir = parent->d_inode;
 | |
| 
 | |
| 	if (!dentry->d_inode)
 | |
| 		goto finished;
 | |
| 
 | |
| 	server = NCP_SERVER(dir);
 | |
| 
 | |
| 	/*
 | |
| 	 * Inspired by smbfs:
 | |
| 	 * The default validation is based on dentry age:
 | |
| 	 * We set the max age at mount time.  (But each
 | |
| 	 * successful server lookup renews the timestamp.)
 | |
| 	 */
 | |
| 	val = NCP_TEST_AGE(server, dentry);
 | |
| 	if (val)
 | |
| 		goto finished;
 | |
| 
 | |
| 	ncp_dbg(2, "%pd2 not valid, age=%ld, server lookup\n",
 | |
| 		dentry, NCP_GET_AGE(dentry));
 | |
| 
 | |
| 	len = sizeof(__name);
 | |
| 	if (ncp_is_server_root(dir)) {
 | |
| 		res = ncp_io2vol(server, __name, &len, dentry->d_name.name,
 | |
| 				 dentry->d_name.len, 1);
 | |
| 		if (!res) {
 | |
| 			res = ncp_lookup_volume(server, __name, &(finfo.i));
 | |
| 			if (!res)
 | |
| 				ncp_update_known_namespace(server, finfo.i.volNumber, NULL);
 | |
| 		}
 | |
| 	} else {
 | |
| 		res = ncp_io2vol(server, __name, &len, dentry->d_name.name,
 | |
| 				 dentry->d_name.len, !ncp_preserve_case(dir));
 | |
| 		if (!res)
 | |
| 			res = ncp_obtain_info(server, dir, __name, &(finfo.i));
 | |
| 	}
 | |
| 	finfo.volume = finfo.i.volNumber;
 | |
| 	ncp_dbg(2, "looked for %pd/%s, res=%d\n",
 | |
| 		dentry->d_parent, __name, res);
 | |
| 	/*
 | |
| 	 * If we didn't find it, or if it has a different dirEntNum to
 | |
| 	 * what we remember, it's not valid any more.
 | |
| 	 */
 | |
| 	if (!res) {
 | |
| 		struct inode *inode = dentry->d_inode;
 | |
| 
 | |
| 		mutex_lock(&inode->i_mutex);
 | |
| 		if (finfo.i.dirEntNum == NCP_FINFO(inode)->dirEntNum) {
 | |
| 			ncp_new_dentry(dentry);
 | |
| 			val=1;
 | |
| 		} else
 | |
| 			ncp_dbg(2, "found, but dirEntNum changed\n");
 | |
| 
 | |
| 		ncp_update_inode2(inode, &finfo);
 | |
| 		mutex_unlock(&inode->i_mutex);
 | |
| 	}
 | |
| 
 | |
| finished:
 | |
| 	ncp_dbg(2, "result=%d\n", val);
 | |
| 	dput(parent);
 | |
| 	return val;
 | |
| }
 | |
| 
 | |
| static time_t ncp_obtain_mtime(struct dentry *dentry)
 | |
| {
 | |
| 	struct inode *inode = dentry->d_inode;
 | |
| 	struct ncp_server *server = NCP_SERVER(inode);
 | |
| 	struct nw_info_struct i;
 | |
| 
 | |
| 	if (!ncp_conn_valid(server) || ncp_is_server_root(inode))
 | |
| 		return 0;
 | |
| 
 | |
| 	if (ncp_obtain_info(server, inode, NULL, &i))
 | |
| 		return 0;
 | |
| 
 | |
| 	return ncp_date_dos2unix(i.modifyTime, i.modifyDate);
 | |
| }
 | |
| 
 | |
| static inline void
 | |
| ncp_invalidate_dircache_entries(struct dentry *parent)
 | |
| {
 | |
| 	struct ncp_server *server = NCP_SERVER(parent->d_inode);
 | |
| 	struct dentry *dentry;
 | |
| 
 | |
| 	spin_lock(&parent->d_lock);
 | |
| 	list_for_each_entry(dentry, &parent->d_subdirs, d_child) {
 | |
| 		dentry->d_fsdata = NULL;
 | |
| 		ncp_age_dentry(server, dentry);
 | |
| 	}
 | |
| 	spin_unlock(&parent->d_lock);
 | |
| }
 | |
| 
 | |
| static int ncp_readdir(struct file *file, struct dir_context *ctx)
 | |
| {
 | |
| 	struct dentry *dentry = file->f_path.dentry;
 | |
| 	struct inode *inode = dentry->d_inode;
 | |
| 	struct page *page = NULL;
 | |
| 	struct ncp_server *server = NCP_SERVER(inode);
 | |
| 	union  ncp_dir_cache *cache = NULL;
 | |
| 	struct ncp_cache_control ctl;
 | |
| 	int result, mtime_valid = 0;
 | |
| 	time_t mtime = 0;
 | |
| 
 | |
| 	ctl.page  = NULL;
 | |
| 	ctl.cache = NULL;
 | |
| 
 | |
| 	ncp_dbg(2, "reading %pD2, pos=%d\n", file, (int)ctx->pos);
 | |
| 
 | |
| 	result = -EIO;
 | |
| 	/* Do not generate '.' and '..' when server is dead. */
 | |
| 	if (!ncp_conn_valid(server))
 | |
| 		goto out;
 | |
| 
 | |
| 	result = 0;
 | |
| 	if (!dir_emit_dots(file, ctx))
 | |
| 		goto out;
 | |
| 
 | |
| 	page = grab_cache_page(&inode->i_data, 0);
 | |
| 	if (!page)
 | |
| 		goto read_really;
 | |
| 
 | |
| 	ctl.cache = cache = kmap(page);
 | |
| 	ctl.head  = cache->head;
 | |
| 
 | |
| 	if (!PageUptodate(page) || !ctl.head.eof)
 | |
| 		goto init_cache;
 | |
| 
 | |
| 	if (ctx->pos == 2) {
 | |
| 		if (jiffies - ctl.head.time >= NCP_MAX_AGE(server))
 | |
| 			goto init_cache;
 | |
| 
 | |
| 		mtime = ncp_obtain_mtime(dentry);
 | |
| 		mtime_valid = 1;
 | |
| 		if ((!mtime) || (mtime != ctl.head.mtime))
 | |
| 			goto init_cache;
 | |
| 	}
 | |
| 
 | |
| 	if (ctx->pos > ctl.head.end)
 | |
| 		goto finished;
 | |
| 
 | |
| 	ctl.fpos = ctx->pos + (NCP_DIRCACHE_START - 2);
 | |
| 	ctl.ofs  = ctl.fpos / NCP_DIRCACHE_SIZE;
 | |
| 	ctl.idx  = ctl.fpos % NCP_DIRCACHE_SIZE;
 | |
| 
 | |
| 	for (;;) {
 | |
| 		if (ctl.ofs != 0) {
 | |
| 			ctl.page = find_lock_page(&inode->i_data, ctl.ofs);
 | |
| 			if (!ctl.page)
 | |
| 				goto invalid_cache;
 | |
| 			ctl.cache = kmap(ctl.page);
 | |
| 			if (!PageUptodate(ctl.page))
 | |
| 				goto invalid_cache;
 | |
| 		}
 | |
| 		while (ctl.idx < NCP_DIRCACHE_SIZE) {
 | |
| 			struct dentry *dent;
 | |
| 			bool over;
 | |
| 
 | |
| 			spin_lock(&dentry->d_lock);
 | |
| 			if (!(NCP_FINFO(inode)->flags & NCPI_DIR_CACHE)) { 
 | |
| 				spin_unlock(&dentry->d_lock);
 | |
| 				goto invalid_cache;
 | |
| 			}
 | |
| 			dent = ctl.cache->dentry[ctl.idx];
 | |
| 			if (unlikely(!lockref_get_not_dead(&dent->d_lockref))) {
 | |
| 				spin_unlock(&dentry->d_lock);
 | |
| 				goto invalid_cache;
 | |
| 			}
 | |
| 			spin_unlock(&dentry->d_lock);
 | |
| 			if (!dent->d_inode) {
 | |
| 				dput(dent);
 | |
| 				goto invalid_cache;
 | |
| 			}
 | |
| 			over = !dir_emit(ctx, dent->d_name.name,
 | |
| 					dent->d_name.len,
 | |
| 					dent->d_inode->i_ino, DT_UNKNOWN);
 | |
| 			dput(dent);
 | |
| 			if (over)
 | |
| 				goto finished;
 | |
| 			ctx->pos += 1;
 | |
| 			ctl.idx += 1;
 | |
| 			if (ctx->pos > ctl.head.end)
 | |
| 				goto finished;
 | |
| 		}
 | |
| 		if (ctl.page) {
 | |
| 			kunmap(ctl.page);
 | |
| 			SetPageUptodate(ctl.page);
 | |
| 			unlock_page(ctl.page);
 | |
| 			page_cache_release(ctl.page);
 | |
| 			ctl.page = NULL;
 | |
| 		}
 | |
| 		ctl.idx  = 0;
 | |
| 		ctl.ofs += 1;
 | |
| 	}
 | |
| invalid_cache:
 | |
| 	if (ctl.page) {
 | |
| 		kunmap(ctl.page);
 | |
| 		unlock_page(ctl.page);
 | |
| 		page_cache_release(ctl.page);
 | |
| 		ctl.page = NULL;
 | |
| 	}
 | |
| 	ctl.cache = cache;
 | |
| init_cache:
 | |
| 	ncp_invalidate_dircache_entries(dentry);
 | |
| 	if (!mtime_valid) {
 | |
| 		mtime = ncp_obtain_mtime(dentry);
 | |
| 		mtime_valid = 1;
 | |
| 	}
 | |
| 	ctl.head.mtime = mtime;
 | |
| 	ctl.head.time = jiffies;
 | |
| 	ctl.head.eof = 0;
 | |
| 	ctl.fpos = 2;
 | |
| 	ctl.ofs = 0;
 | |
| 	ctl.idx = NCP_DIRCACHE_START;
 | |
| 	ctl.filled = 0;
 | |
| 	ctl.valid  = 1;
 | |
| read_really:
 | |
| 	spin_lock(&dentry->d_lock);
 | |
| 	NCP_FINFO(inode)->flags |= NCPI_DIR_CACHE;
 | |
| 	spin_unlock(&dentry->d_lock);
 | |
| 	if (ncp_is_server_root(inode)) {
 | |
| 		ncp_read_volume_list(file, ctx, &ctl);
 | |
| 	} else {
 | |
| 		ncp_do_readdir(file, ctx, &ctl);
 | |
| 	}
 | |
| 	ctl.head.end = ctl.fpos - 1;
 | |
| 	ctl.head.eof = ctl.valid;
 | |
| finished:
 | |
| 	if (ctl.page) {
 | |
| 		kunmap(ctl.page);
 | |
| 		SetPageUptodate(ctl.page);
 | |
| 		unlock_page(ctl.page);
 | |
| 		page_cache_release(ctl.page);
 | |
| 	}
 | |
| 	if (page) {
 | |
| 		cache->head = ctl.head;
 | |
| 		kunmap(page);
 | |
| 		SetPageUptodate(page);
 | |
| 		unlock_page(page);
 | |
| 		page_cache_release(page);
 | |
| 	}
 | |
| out:
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| static void ncp_d_prune(struct dentry *dentry)
 | |
| {
 | |
| 	if (!dentry->d_fsdata)	/* not referenced from page cache */
 | |
| 		return;
 | |
| 	NCP_FINFO(dentry->d_parent->d_inode)->flags &= ~NCPI_DIR_CACHE;
 | |
| }
 | |
| 
 | |
| static int
 | |
| ncp_fill_cache(struct file *file, struct dir_context *ctx,
 | |
| 		struct ncp_cache_control *ctrl, struct ncp_entry_info *entry,
 | |
| 		int inval_childs)
 | |
| {
 | |
| 	struct dentry *newdent, *dentry = file->f_path.dentry;
 | |
| 	struct inode *dir = dentry->d_inode;
 | |
| 	struct ncp_cache_control ctl = *ctrl;
 | |
| 	struct qstr qname;
 | |
| 	int valid = 0;
 | |
| 	int hashed = 0;
 | |
| 	ino_t ino = 0;
 | |
| 	__u8 __name[NCP_MAXPATHLEN + 1];
 | |
| 
 | |
| 	qname.len = sizeof(__name);
 | |
| 	if (ncp_vol2io(NCP_SERVER(dir), __name, &qname.len,
 | |
| 			entry->i.entryName, entry->i.nameLen,
 | |
| 			!ncp_preserve_entry_case(dir, entry->i.NSCreator)))
 | |
| 		return 1; /* I'm not sure */
 | |
| 
 | |
| 	qname.name = __name;
 | |
| 
 | |
| 	newdent = d_hash_and_lookup(dentry, &qname);
 | |
| 	if (unlikely(IS_ERR(newdent)))
 | |
| 		goto end_advance;
 | |
| 	if (!newdent) {
 | |
| 		newdent = d_alloc(dentry, &qname);
 | |
| 		if (!newdent)
 | |
| 			goto end_advance;
 | |
| 	} else {
 | |
| 		hashed = 1;
 | |
| 
 | |
| 		/* If case sensitivity changed for this volume, all entries below this one
 | |
| 		   should be thrown away.  This entry itself is not affected, as its case
 | |
| 		   sensitivity is controlled by its own parent. */
 | |
| 		if (inval_childs)
 | |
| 			shrink_dcache_parent(newdent);
 | |
| 
 | |
| 		/*
 | |
| 		 * NetWare's OS2 namespace is case preserving yet case
 | |
| 		 * insensitive.  So we update dentry's name as received from
 | |
| 		 * server. Parent dir's i_mutex is locked because we're in
 | |
| 		 * readdir.
 | |
| 		 */
 | |
| 		dentry_update_name_case(newdent, &qname);
 | |
| 	}
 | |
| 
 | |
| 	if (!newdent->d_inode) {
 | |
| 		struct inode *inode;
 | |
| 
 | |
| 		entry->opened = 0;
 | |
| 		entry->ino = iunique(dir->i_sb, 2);
 | |
| 		inode = ncp_iget(dir->i_sb, entry);
 | |
| 		if (inode) {
 | |
| 			d_instantiate(newdent, inode);
 | |
| 			if (!hashed)
 | |
| 				d_rehash(newdent);
 | |
| 		} else {
 | |
| 			spin_lock(&dentry->d_lock);
 | |
| 			NCP_FINFO(inode)->flags &= ~NCPI_DIR_CACHE;
 | |
| 			spin_unlock(&dentry->d_lock);
 | |
| 		}
 | |
| 	} else {
 | |
| 		struct inode *inode = newdent->d_inode;
 | |
| 
 | |
| 		mutex_lock_nested(&inode->i_mutex, I_MUTEX_CHILD);
 | |
| 		ncp_update_inode2(inode, entry);
 | |
| 		mutex_unlock(&inode->i_mutex);
 | |
| 	}
 | |
| 
 | |
| 	if (ctl.idx >= NCP_DIRCACHE_SIZE) {
 | |
| 		if (ctl.page) {
 | |
| 			kunmap(ctl.page);
 | |
| 			SetPageUptodate(ctl.page);
 | |
| 			unlock_page(ctl.page);
 | |
| 			page_cache_release(ctl.page);
 | |
| 		}
 | |
| 		ctl.cache = NULL;
 | |
| 		ctl.idx  -= NCP_DIRCACHE_SIZE;
 | |
| 		ctl.ofs  += 1;
 | |
| 		ctl.page  = grab_cache_page(&dir->i_data, ctl.ofs);
 | |
| 		if (ctl.page)
 | |
| 			ctl.cache = kmap(ctl.page);
 | |
| 	}
 | |
| 	if (ctl.cache) {
 | |
| 		if (newdent->d_inode) {
 | |
| 			newdent->d_fsdata = newdent;
 | |
| 			ctl.cache->dentry[ctl.idx] = newdent;
 | |
| 			ino = newdent->d_inode->i_ino;
 | |
| 			ncp_new_dentry(newdent);
 | |
| 		}
 | |
|  		valid = 1;
 | |
| 	}
 | |
| 	dput(newdent);
 | |
| end_advance:
 | |
| 	if (!valid)
 | |
| 		ctl.valid = 0;
 | |
| 	if (!ctl.filled && (ctl.fpos == ctx->pos)) {
 | |
| 		if (!ino)
 | |
| 			ino = iunique(dir->i_sb, 2);
 | |
| 		ctl.filled = !dir_emit(ctx, qname.name, qname.len,
 | |
| 				     ino, DT_UNKNOWN);
 | |
| 		if (!ctl.filled)
 | |
| 			ctx->pos += 1;
 | |
| 	}
 | |
| 	ctl.fpos += 1;
 | |
| 	ctl.idx  += 1;
 | |
| 	*ctrl = ctl;
 | |
| 	return (ctl.valid || !ctl.filled);
 | |
| }
 | |
| 
 | |
| static void
 | |
| ncp_read_volume_list(struct file *file, struct dir_context *ctx,
 | |
| 			struct ncp_cache_control *ctl)
 | |
| {
 | |
| 	struct inode *inode = file_inode(file);
 | |
| 	struct ncp_server *server = NCP_SERVER(inode);
 | |
| 	struct ncp_volume_info info;
 | |
| 	struct ncp_entry_info entry;
 | |
| 	int i;
 | |
| 
 | |
| 	ncp_dbg(1, "pos=%ld\n", (unsigned long)ctx->pos);
 | |
| 
 | |
| 	for (i = 0; i < NCP_NUMBER_OF_VOLUMES; i++) {
 | |
| 		int inval_dentry;
 | |
| 
 | |
| 		if (ncp_get_volume_info_with_number(server, i, &info) != 0)
 | |
| 			return;
 | |
| 		if (!strlen(info.volume_name))
 | |
| 			continue;
 | |
| 
 | |
| 		ncp_dbg(1, "found vol: %s\n", info.volume_name);
 | |
| 
 | |
| 		if (ncp_lookup_volume(server, info.volume_name,
 | |
| 					&entry.i)) {
 | |
| 			ncp_dbg(1, "could not lookup vol %s\n",
 | |
| 				info.volume_name);
 | |
| 			continue;
 | |
| 		}
 | |
| 		inval_dentry = ncp_update_known_namespace(server, entry.i.volNumber, NULL);
 | |
| 		entry.volume = entry.i.volNumber;
 | |
| 		if (!ncp_fill_cache(file, ctx, ctl, &entry, inval_dentry))
 | |
| 			return;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void
 | |
| ncp_do_readdir(struct file *file, struct dir_context *ctx,
 | |
| 						struct ncp_cache_control *ctl)
 | |
| {
 | |
| 	struct inode *dir = file_inode(file);
 | |
| 	struct ncp_server *server = NCP_SERVER(dir);
 | |
| 	struct nw_search_sequence seq;
 | |
| 	struct ncp_entry_info entry;
 | |
| 	int err;
 | |
| 	void* buf;
 | |
| 	int more;
 | |
| 	size_t bufsize;
 | |
| 
 | |
| 	ncp_dbg(1, "%pD2, fpos=%ld\n", file, (unsigned long)ctx->pos);
 | |
| 	ncp_vdbg("init %pD, volnum=%d, dirent=%u\n",
 | |
| 		 file, NCP_FINFO(dir)->volNumber, NCP_FINFO(dir)->dirEntNum);
 | |
| 
 | |
| 	err = ncp_initialize_search(server, dir, &seq);
 | |
| 	if (err) {
 | |
| 		ncp_dbg(1, "init failed, err=%d\n", err);
 | |
| 		return;
 | |
| 	}
 | |
| 	/* We MUST NOT use server->buffer_size handshaked with server if we are
 | |
| 	   using UDP, as for UDP server uses max. buffer size determined by
 | |
| 	   MTU, and for TCP server uses hardwired value 65KB (== 66560 bytes). 
 | |
| 	   So we use 128KB, just to be sure, as there is no way how to know
 | |
| 	   this value in advance. */
 | |
| 	bufsize = 131072;
 | |
| 	buf = vmalloc(bufsize);
 | |
| 	if (!buf)
 | |
| 		return;
 | |
| 	do {
 | |
| 		int cnt;
 | |
| 		char* rpl;
 | |
| 		size_t rpls;
 | |
| 
 | |
| 		err = ncp_search_for_fileset(server, &seq, &more, &cnt, buf, bufsize, &rpl, &rpls);
 | |
| 		if (err)		/* Error */
 | |
| 			break;
 | |
| 		if (!cnt)		/* prevent endless loop */
 | |
| 			break;
 | |
| 		while (cnt--) {
 | |
| 			size_t onerpl;
 | |
| 			
 | |
| 			if (rpls < offsetof(struct nw_info_struct, entryName))
 | |
| 				break;	/* short packet */
 | |
| 			ncp_extract_file_info(rpl, &entry.i);
 | |
| 			onerpl = offsetof(struct nw_info_struct, entryName) + entry.i.nameLen;
 | |
| 			if (rpls < onerpl)
 | |
| 				break;	/* short packet */
 | |
| 			(void)ncp_obtain_nfs_info(server, &entry.i);
 | |
| 			rpl += onerpl;
 | |
| 			rpls -= onerpl;
 | |
| 			entry.volume = entry.i.volNumber;
 | |
| 			if (!ncp_fill_cache(file, ctx, ctl, &entry, 0))
 | |
| 				break;
 | |
| 		}
 | |
| 	} while (more);
 | |
| 	vfree(buf);
 | |
| 	return;
 | |
| }
 | |
| 
 | |
| int ncp_conn_logged_in(struct super_block *sb)
 | |
| {
 | |
| 	struct ncp_server* server = NCP_SBP(sb);
 | |
| 	int result;
 | |
| 
 | |
| 	if (ncp_single_volume(server)) {
 | |
| 		int len;
 | |
| 		struct dentry* dent;
 | |
| 		__u32 volNumber;
 | |
| 		__le32 dirEntNum;
 | |
| 		__le32 DosDirNum;
 | |
| 		__u8 __name[NCP_MAXPATHLEN + 1];
 | |
| 
 | |
| 		len = sizeof(__name);
 | |
| 		result = ncp_io2vol(server, __name, &len, server->m.mounted_vol,
 | |
| 				    strlen(server->m.mounted_vol), 1);
 | |
| 		if (result)
 | |
| 			goto out;
 | |
| 		result = -ENOENT;
 | |
| 		if (ncp_get_volume_root(server, __name, &volNumber, &dirEntNum, &DosDirNum)) {
 | |
| 			ncp_vdbg("%s not found\n", server->m.mounted_vol);
 | |
| 			goto out;
 | |
| 		}
 | |
| 		dent = sb->s_root;
 | |
| 		if (dent) {
 | |
| 			struct inode* ino = dent->d_inode;
 | |
| 			if (ino) {
 | |
| 				ncp_update_known_namespace(server, volNumber, NULL);
 | |
| 				NCP_FINFO(ino)->volNumber = volNumber;
 | |
| 				NCP_FINFO(ino)->dirEntNum = dirEntNum;
 | |
| 				NCP_FINFO(ino)->DosDirNum = DosDirNum;
 | |
| 				result = 0;
 | |
| 			} else {
 | |
| 				ncp_dbg(1, "sb->s_root->d_inode == NULL!\n");
 | |
| 			}
 | |
| 		} else {
 | |
| 			ncp_dbg(1, "sb->s_root == NULL!\n");
 | |
| 		}
 | |
| 	} else
 | |
| 		result = 0;
 | |
| 
 | |
| out:
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| static struct dentry *ncp_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags)
 | |
| {
 | |
| 	struct ncp_server *server = NCP_SERVER(dir);
 | |
| 	struct inode *inode = NULL;
 | |
| 	struct ncp_entry_info finfo;
 | |
| 	int error, res, len;
 | |
| 	__u8 __name[NCP_MAXPATHLEN + 1];
 | |
| 
 | |
| 	error = -EIO;
 | |
| 	if (!ncp_conn_valid(server))
 | |
| 		goto finished;
 | |
| 
 | |
| 	ncp_vdbg("server lookup for %pd2\n", dentry);
 | |
| 
 | |
| 	len = sizeof(__name);
 | |
| 	if (ncp_is_server_root(dir)) {
 | |
| 		res = ncp_io2vol(server, __name, &len, dentry->d_name.name,
 | |
| 				 dentry->d_name.len, 1);
 | |
| 		if (!res)
 | |
| 			res = ncp_lookup_volume(server, __name, &(finfo.i));
 | |
| 		if (!res)
 | |
| 			ncp_update_known_namespace(server, finfo.i.volNumber, NULL);
 | |
| 	} else {
 | |
| 		res = ncp_io2vol(server, __name, &len, dentry->d_name.name,
 | |
| 				 dentry->d_name.len, !ncp_preserve_case(dir));
 | |
| 		if (!res)
 | |
| 			res = ncp_obtain_info(server, dir, __name, &(finfo.i));
 | |
| 	}
 | |
| 	ncp_vdbg("looked for %pd2, res=%d\n", dentry, res);
 | |
| 	/*
 | |
| 	 * If we didn't find an entry, make a negative dentry.
 | |
| 	 */
 | |
| 	if (res)
 | |
| 		goto add_entry;
 | |
| 
 | |
| 	/*
 | |
| 	 * Create an inode for the entry.
 | |
| 	 */
 | |
| 	finfo.opened = 0;
 | |
| 	finfo.ino = iunique(dir->i_sb, 2);
 | |
| 	finfo.volume = finfo.i.volNumber;
 | |
| 	error = -EACCES;
 | |
| 	inode = ncp_iget(dir->i_sb, &finfo);
 | |
| 
 | |
| 	if (inode) {
 | |
| 		ncp_new_dentry(dentry);
 | |
| add_entry:
 | |
| 		d_add(dentry, inode);
 | |
| 		error = 0;
 | |
| 	}
 | |
| 
 | |
| finished:
 | |
| 	ncp_vdbg("result=%d\n", error);
 | |
| 	return ERR_PTR(error);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * This code is common to create, mkdir, and mknod.
 | |
|  */
 | |
| static int ncp_instantiate(struct inode *dir, struct dentry *dentry,
 | |
| 			struct ncp_entry_info *finfo)
 | |
| {
 | |
| 	struct inode *inode;
 | |
| 	int error = -EINVAL;
 | |
| 
 | |
| 	finfo->ino = iunique(dir->i_sb, 2);
 | |
| 	inode = ncp_iget(dir->i_sb, finfo);
 | |
| 	if (!inode)
 | |
| 		goto out_close;
 | |
| 	d_instantiate(dentry,inode);
 | |
| 	error = 0;
 | |
| out:
 | |
| 	return error;
 | |
| 
 | |
| out_close:
 | |
| 	ncp_vdbg("%pd2 failed, closing file\n", dentry);
 | |
| 	ncp_close_file(NCP_SERVER(dir), finfo->file_handle);
 | |
| 	goto out;
 | |
| }
 | |
| 
 | |
| int ncp_create_new(struct inode *dir, struct dentry *dentry, umode_t mode,
 | |
| 		   dev_t rdev, __le32 attributes)
 | |
| {
 | |
| 	struct ncp_server *server = NCP_SERVER(dir);
 | |
| 	struct ncp_entry_info finfo;
 | |
| 	int error, result, len;
 | |
| 	int opmode;
 | |
| 	__u8 __name[NCP_MAXPATHLEN + 1];
 | |
| 	
 | |
| 	ncp_vdbg("creating %pd2, mode=%hx\n", dentry, mode);
 | |
| 
 | |
| 	ncp_age_dentry(server, dentry);
 | |
| 	len = sizeof(__name);
 | |
| 	error = ncp_io2vol(server, __name, &len, dentry->d_name.name,
 | |
| 			   dentry->d_name.len, !ncp_preserve_case(dir));
 | |
| 	if (error)
 | |
| 		goto out;
 | |
| 
 | |
| 	error = -EACCES;
 | |
| 	
 | |
| 	if (S_ISREG(mode) && 
 | |
| 	    (server->m.flags & NCP_MOUNT_EXTRAS) && 
 | |
| 	    (mode & S_IXUGO))
 | |
| 		attributes |= aSYSTEM | aSHARED;
 | |
| 	
 | |
| 	result = ncp_open_create_file_or_subdir(server, dir, __name,
 | |
| 				OC_MODE_CREATE | OC_MODE_OPEN | OC_MODE_REPLACE,
 | |
| 				attributes, AR_READ | AR_WRITE, &finfo);
 | |
| 	opmode = O_RDWR;
 | |
| 	if (result) {
 | |
| 		result = ncp_open_create_file_or_subdir(server, dir, __name,
 | |
| 				OC_MODE_CREATE | OC_MODE_OPEN | OC_MODE_REPLACE,
 | |
| 				attributes, AR_WRITE, &finfo);
 | |
| 		if (result) {
 | |
| 			if (result == 0x87)
 | |
| 				error = -ENAMETOOLONG;
 | |
| 			else if (result < 0)
 | |
| 				error = result;
 | |
| 			ncp_dbg(1, "%pd2 failed\n", dentry);
 | |
| 			goto out;
 | |
| 		}
 | |
| 		opmode = O_WRONLY;
 | |
| 	}
 | |
| 	finfo.access = opmode;
 | |
| 	if (ncp_is_nfs_extras(server, finfo.volume)) {
 | |
| 		finfo.i.nfs.mode = mode;
 | |
| 		finfo.i.nfs.rdev = new_encode_dev(rdev);
 | |
| 		if (ncp_modify_nfs_info(server, finfo.volume,
 | |
| 					finfo.i.dirEntNum,
 | |
| 					mode, new_encode_dev(rdev)) != 0)
 | |
| 			goto out;
 | |
| 	}
 | |
| 
 | |
| 	error = ncp_instantiate(dir, dentry, &finfo);
 | |
| out:
 | |
| 	return error;
 | |
| }
 | |
| 
 | |
| static int ncp_create(struct inode *dir, struct dentry *dentry, umode_t mode,
 | |
| 		bool excl)
 | |
| {
 | |
| 	return ncp_create_new(dir, dentry, mode, 0, 0);
 | |
| }
 | |
| 
 | |
| static int ncp_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
 | |
| {
 | |
| 	struct ncp_entry_info finfo;
 | |
| 	struct ncp_server *server = NCP_SERVER(dir);
 | |
| 	int error, len;
 | |
| 	__u8 __name[NCP_MAXPATHLEN + 1];
 | |
| 
 | |
| 	ncp_dbg(1, "making %pd2\n", dentry);
 | |
| 
 | |
| 	ncp_age_dentry(server, dentry);
 | |
| 	len = sizeof(__name);
 | |
| 	error = ncp_io2vol(server, __name, &len, dentry->d_name.name,
 | |
| 			   dentry->d_name.len, !ncp_preserve_case(dir));
 | |
| 	if (error)
 | |
| 		goto out;
 | |
| 
 | |
| 	error = ncp_open_create_file_or_subdir(server, dir, __name,
 | |
| 					   OC_MODE_CREATE, aDIR,
 | |
| 					   cpu_to_le16(0xffff),
 | |
| 					   &finfo);
 | |
| 	if (error == 0) {
 | |
| 		if (ncp_is_nfs_extras(server, finfo.volume)) {
 | |
| 			mode |= S_IFDIR;
 | |
| 			finfo.i.nfs.mode = mode;
 | |
| 			if (ncp_modify_nfs_info(server,
 | |
| 						finfo.volume,
 | |
| 						finfo.i.dirEntNum,
 | |
| 						mode, 0) != 0)
 | |
| 				goto out;
 | |
| 		}
 | |
| 		error = ncp_instantiate(dir, dentry, &finfo);
 | |
| 	} else if (error > 0) {
 | |
| 		error = -EACCES;
 | |
| 	}
 | |
| out:
 | |
| 	return error;
 | |
| }
 | |
| 
 | |
| static int ncp_rmdir(struct inode *dir, struct dentry *dentry)
 | |
| {
 | |
| 	struct ncp_server *server = NCP_SERVER(dir);
 | |
| 	int error, result, len;
 | |
| 	__u8 __name[NCP_MAXPATHLEN + 1];
 | |
| 
 | |
| 	ncp_dbg(1, "removing %pd2\n", dentry);
 | |
| 
 | |
| 	len = sizeof(__name);
 | |
| 	error = ncp_io2vol(server, __name, &len, dentry->d_name.name,
 | |
| 			   dentry->d_name.len, !ncp_preserve_case(dir));
 | |
| 	if (error)
 | |
| 		goto out;
 | |
| 
 | |
| 	result = ncp_del_file_or_subdir(server, dir, __name);
 | |
| 	switch (result) {
 | |
| 		case 0x00:
 | |
| 			error = 0;
 | |
| 			break;
 | |
| 		case 0x85:	/* unauthorized to delete file */
 | |
| 		case 0x8A:	/* unauthorized to delete file */
 | |
| 			error = -EACCES;
 | |
| 			break;
 | |
| 		case 0x8F:
 | |
| 		case 0x90:	/* read only */
 | |
| 			error = -EPERM;
 | |
| 			break;
 | |
| 		case 0x9F:	/* in use by another client */
 | |
| 			error = -EBUSY;
 | |
| 			break;
 | |
| 		case 0xA0:	/* directory not empty */
 | |
| 			error = -ENOTEMPTY;
 | |
| 			break;
 | |
| 		case 0xFF:	/* someone deleted file */
 | |
| 			error = -ENOENT;
 | |
| 			break;
 | |
| 		default:
 | |
| 			error = result < 0 ? result : -EACCES;
 | |
| 			break;
 | |
|        	}
 | |
| out:
 | |
| 	return error;
 | |
| }
 | |
| 
 | |
| static int ncp_unlink(struct inode *dir, struct dentry *dentry)
 | |
| {
 | |
| 	struct inode *inode = dentry->d_inode;
 | |
| 	struct ncp_server *server;
 | |
| 	int error;
 | |
| 
 | |
| 	server = NCP_SERVER(dir);
 | |
| 	ncp_dbg(1, "unlinking %pd2\n", dentry);
 | |
| 	
 | |
| 	/*
 | |
| 	 * Check whether to close the file ...
 | |
| 	 */
 | |
| 	if (inode) {
 | |
| 		ncp_vdbg("closing file\n");
 | |
| 		ncp_make_closed(inode);
 | |
| 	}
 | |
| 
 | |
| 	error = ncp_del_file_or_subdir2(server, dentry);
 | |
| #ifdef CONFIG_NCPFS_STRONG
 | |
| 	/* 9C is Invalid path.. It should be 8F, 90 - read only, but
 | |
| 	   it is not :-( */
 | |
| 	if ((error == 0x9C || error == 0x90) && server->m.flags & NCP_MOUNT_STRONG) { /* R/O */
 | |
| 		error = ncp_force_unlink(dir, dentry);
 | |
| 	}
 | |
| #endif
 | |
| 	switch (error) {
 | |
| 		case 0x00:
 | |
| 			ncp_dbg(1, "removed %pd2\n", dentry);
 | |
| 			break;
 | |
| 		case 0x85:
 | |
| 		case 0x8A:
 | |
| 			error = -EACCES;
 | |
| 			break;
 | |
| 		case 0x8D:	/* some files in use */
 | |
| 		case 0x8E:	/* all files in use */
 | |
| 			error = -EBUSY;
 | |
| 			break;
 | |
| 		case 0x8F:	/* some read only */
 | |
| 		case 0x90:	/* all read only */
 | |
| 		case 0x9C:	/* !!! returned when in-use or read-only by NW4 */
 | |
| 			error = -EPERM;
 | |
| 			break;
 | |
| 		case 0xFF:
 | |
| 			error = -ENOENT;
 | |
| 			break;
 | |
| 		default:
 | |
| 			error = error < 0 ? error : -EACCES;
 | |
| 			break;
 | |
| 	}
 | |
| 	return error;
 | |
| }
 | |
| 
 | |
| static int ncp_rename(struct inode *old_dir, struct dentry *old_dentry,
 | |
| 		      struct inode *new_dir, struct dentry *new_dentry)
 | |
| {
 | |
| 	struct ncp_server *server = NCP_SERVER(old_dir);
 | |
| 	int error;
 | |
| 	int old_len, new_len;
 | |
| 	__u8 __old_name[NCP_MAXPATHLEN + 1], __new_name[NCP_MAXPATHLEN + 1];
 | |
| 
 | |
| 	ncp_dbg(1, "%pd2 to %pd2\n", old_dentry, new_dentry);
 | |
| 
 | |
| 	ncp_age_dentry(server, old_dentry);
 | |
| 	ncp_age_dentry(server, new_dentry);
 | |
| 
 | |
| 	old_len = sizeof(__old_name);
 | |
| 	error = ncp_io2vol(server, __old_name, &old_len,
 | |
| 			   old_dentry->d_name.name, old_dentry->d_name.len,
 | |
| 			   !ncp_preserve_case(old_dir));
 | |
| 	if (error)
 | |
| 		goto out;
 | |
| 
 | |
| 	new_len = sizeof(__new_name);
 | |
| 	error = ncp_io2vol(server, __new_name, &new_len,
 | |
| 			   new_dentry->d_name.name, new_dentry->d_name.len,
 | |
| 			   !ncp_preserve_case(new_dir));
 | |
| 	if (error)
 | |
| 		goto out;
 | |
| 
 | |
| 	error = ncp_ren_or_mov_file_or_subdir(server, old_dir, __old_name,
 | |
| 						      new_dir, __new_name);
 | |
| #ifdef CONFIG_NCPFS_STRONG
 | |
| 	if ((error == 0x90 || error == 0x8B || error == -EACCES) &&
 | |
| 			server->m.flags & NCP_MOUNT_STRONG) {	/* RO */
 | |
| 		error = ncp_force_rename(old_dir, old_dentry, __old_name,
 | |
| 					 new_dir, new_dentry, __new_name);
 | |
| 	}
 | |
| #endif
 | |
| 	switch (error) {
 | |
| 		case 0x00:
 | |
| 			ncp_dbg(1, "renamed %pd -> %pd\n",
 | |
| 				old_dentry, new_dentry);
 | |
| 			break;
 | |
| 		case 0x9E:
 | |
| 			error = -ENAMETOOLONG;
 | |
| 			break;
 | |
| 		case 0xFF:
 | |
| 			error = -ENOENT;
 | |
| 			break;
 | |
| 		default:
 | |
| 			error = error < 0 ? error : -EACCES;
 | |
| 			break;
 | |
| 	}
 | |
| out:
 | |
| 	return error;
 | |
| }
 | |
| 
 | |
| static int ncp_mknod(struct inode * dir, struct dentry *dentry,
 | |
| 		     umode_t mode, dev_t rdev)
 | |
| {
 | |
| 	if (!new_valid_dev(rdev))
 | |
| 		return -EINVAL;
 | |
| 	if (ncp_is_nfs_extras(NCP_SERVER(dir), NCP_FINFO(dir)->volNumber)) {
 | |
| 		ncp_dbg(1, "mode = 0%ho\n", mode);
 | |
| 		return ncp_create_new(dir, dentry, mode, rdev, 0);
 | |
| 	}
 | |
| 	return -EPERM; /* Strange, but true */
 | |
| }
 | |
| 
 | |
| /* The following routines are taken directly from msdos-fs */
 | |
| 
 | |
| /* Linear day numbers of the respective 1sts in non-leap years. */
 | |
| 
 | |
| static int day_n[] =
 | |
| {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 0, 0, 0, 0};
 | |
| /* Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec */
 | |
| 
 | |
| static int utc2local(int time)
 | |
| {
 | |
| 	return time - sys_tz.tz_minuteswest * 60;
 | |
| }
 | |
| 
 | |
| static int local2utc(int time)
 | |
| {
 | |
| 	return time + sys_tz.tz_minuteswest * 60;
 | |
| }
 | |
| 
 | |
| /* Convert a MS-DOS time/date pair to a UNIX date (seconds since 1 1 70). */
 | |
| int
 | |
| ncp_date_dos2unix(__le16 t, __le16 d)
 | |
| {
 | |
| 	unsigned short time = le16_to_cpu(t), date = le16_to_cpu(d);
 | |
| 	int month, year, secs;
 | |
| 
 | |
| 	/* first subtract and mask after that... Otherwise, if
 | |
| 	   date == 0, bad things happen */
 | |
| 	month = ((date >> 5) - 1) & 15;
 | |
| 	year = date >> 9;
 | |
| 	secs = (time & 31) * 2 + 60 * ((time >> 5) & 63) + (time >> 11) * 3600 +
 | |
| 		86400 * ((date & 31) - 1 + day_n[month] + (year / 4) + 
 | |
| 		year * 365 - ((year & 3) == 0 && month < 2 ? 1 : 0) + 3653);
 | |
| 	/* days since 1.1.70 plus 80's leap day */
 | |
| 	return local2utc(secs);
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Convert linear UNIX date to a MS-DOS time/date pair. */
 | |
| void
 | |
| ncp_date_unix2dos(int unix_date, __le16 *time, __le16 *date)
 | |
| {
 | |
| 	int day, year, nl_day, month;
 | |
| 
 | |
| 	unix_date = utc2local(unix_date);
 | |
| 	*time = cpu_to_le16(
 | |
| 		(unix_date % 60) / 2 + (((unix_date / 60) % 60) << 5) +
 | |
| 		(((unix_date / 3600) % 24) << 11));
 | |
| 	day = unix_date / 86400 - 3652;
 | |
| 	year = day / 365;
 | |
| 	if ((year + 3) / 4 + 365 * year > day)
 | |
| 		year--;
 | |
| 	day -= (year + 3) / 4 + 365 * year;
 | |
| 	if (day == 59 && !(year & 3)) {
 | |
| 		nl_day = day;
 | |
| 		month = 2;
 | |
| 	} else {
 | |
| 		nl_day = (year & 3) || day <= 59 ? day : day - 1;
 | |
| 		for (month = 1; month < 12; month++)
 | |
| 			if (day_n[month] > nl_day)
 | |
| 				break;
 | |
| 	}
 | |
| 	*date = cpu_to_le16(nl_day - day_n[month - 1] + 1 + (month << 5) + (year << 9));
 | |
| }
 |