patch-2.1.43 linux/fs/namei.c

Next file: linux/fs/nametrans.c
Previous file: linux/fs/msdos/namei.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.42/linux/fs/namei.c linux/fs/namei.c
@@ -8,6 +8,11 @@
  * Some corrections by tytso.
  */
 
+/* [Feb 1997 T. Schoebel-Theuer] Complete rewrite of the pathname
+ * lookup logic.
+ */
+
+#include <linux/config.h>
 #include <linux/errno.h>
 #include <linux/sched.h>
 #include <linux/kernel.h>
@@ -15,18 +20,114 @@
 #include <linux/fcntl.h>
 #include <linux/stat.h>
 #include <linux/mm.h>
+#include <linux/dalloc.h>
+#include <linux/nametrans.h>
+#include <linux/proc_fs.h>
+#include <linux/omirr.h>
 #include <linux/smp.h>
 #include <linux/smp_lock.h>
 
 #include <asm/uaccess.h>
 #include <asm/unaligned.h>
+#include <asm/semaphore.h>
 #include <asm/namei.h>
 
+/* This can be removed after the beta phase. */
+#define CACHE_SUPERVISE	/* debug the correctness of dcache entries */
+#undef DEBUG		/* some other debugging */
+
+
+/* local flags for __namei() */
+#define NAM_SEMLOCK        8 /* set a semlock on the last dir */
+#define NAM_TRANSCREATE   16 /* last component may be created, try "=CREATE#" suffix*/
+#define NAM_NO_TRAILSLASH 32 /* disallow trailing slashes by returning EISDIR */
 #define ACC_MODE(x) ("\000\004\002\006"[(x)&O_ACCMODE])
 
+/* [Feb-1997 T. Schoebel-Theuer]
+ * Fundamental changes in the pathname lookup mechanisms (namei)
+ * were necessary because of omirr.  The reason is that omirr needs
+ * to know the _real_ pathname, not the user-supplied one, in case
+ * of symlinks (and also when transname replacements occur).
+ *
+ * The new code replaces the old recursive symlink resolution with
+ * an iterative one (in case of non-nested symlink chains).  It does
+ * this by looking up the symlink name from the particular filesystem,
+ * and then follows this name as if it were a user-supplied one.  This
+ * is done solely in the VFS level, such that <fs>_follow_link() is not
+ * used any more and could be removed in future.  As a side effect,
+ * dir_namei(), _namei() and follow_link() are now replaced with a single
+ * function __namei() that can handle all the special cases of the former
+ * code.
+ *
+ * With the new dcache, the pathname is stored at each inode, at least as
+ * long as the refcount of the inode is positive.  As a side effect, the
+ * size of the dcache depends on the inode cache and thus is dynamic.
+ */
 
-/*
- * In order to reduce some races, while at the same time doing additional
+/* [24-Feb-97 T. Schoebel-Theuer] Side effects caused by new implementation:
+ * New symlink semantics: when open() is called with flags O_CREAT | O_EXCL
+ * and the name already exists in form of a symlink, try to create the new
+ * name indicated by the symlink. The old code always complained that the
+ * name already exists, due to not following the symlink even if its target
+ * is non-existant.  The new semantics affects also mknod() and link() when
+ * the name is a symlink pointing to a non-existant name.
+ *
+ * I don't know which semantics is the right one, since I have no access
+ * to standards. But I found by trial that HP-UX 9.0 has the full "new"
+ * semantics implemented, while SunOS 4.1.1 and Solaris (SunOS 5.4) have the
+ * "old" one. Personally, I think the new semantics is much more logical.
+ * Note that "ln old new" where "new" is a symlink pointing to a non-existing
+ * file does succeed in both HP-UX and SunOs, but not in Solaris
+ * and in the old Linux semantics.
+ */
+
+static char * quicklist = NULL;
+static int quickcount = 0;
+struct semaphore quicklock = MUTEX;
+
+/* Tuning: increase locality by reusing same pages again...
+ * if quicklist becomes too long on low memory machines, either a limit
+ * should be added or after a number of cycles some pages should
+ * be released again ...
+ */
+static inline char * get_page(void)
+{
+	char * res;
+	down(&quicklock);
+	res = quicklist;
+	if(res) {
+#ifdef DEBUG
+		char * tmp = res;
+		int i;
+		for(i=0; i<quickcount; i++)
+			tmp = *(char**)tmp;
+		if(tmp)
+			printk("bad quicklist %x\n", (int)tmp);
+#endif
+		quicklist = *(char**)res;
+		quickcount--;
+	}
+	else
+		res = (char*)__get_free_page(GFP_KERNEL);
+	up(&quicklock);
+	return res;
+}
+
+inline void putname(char * name)
+{
+	if(name) {
+		down(&quicklock);
+		*(char**)name = quicklist;
+		quicklist = name;
+		quickcount++;
+		up(&quicklock);
+	}
+	/* if a quicklist limit is necessary to introduce, call
+	 * free_page((unsigned long) name);
+	 */
+}
+
+/* In order to reduce some races, while at the same time doing additional
  * checking and hopefully speeding things up, we copy filenames to the
  * kernel data space before using them..
  *
@@ -53,44 +154,22 @@
 	return retval;
 }
 
-/*
- * This is a single page for faster getname.
- *   If the page is available when entering getname, use it.
- *   If the page is not available, call __get_free_page instead.
- * This works even though do_getname can block (think about it).
- * -- Michael Chastain, based on idea of Linus Torvalds, 1 Dec 1996.
- */
-static unsigned long name_page_cache = 0;
-
 int getname(const char * filename, char **result)
 {
-	unsigned long page;
+	char *tmp;
 	int retval;
 
-	page = name_page_cache;
-	name_page_cache = 0;
-	if (!page) {
-		page = __get_free_page(GFP_KERNEL);
-		if (!page)
-			return -ENOMEM;
-	}
-
-	retval = do_getname(filename, (char *) page);
+	tmp = get_page();
+	if(!tmp)
+		return -ENOMEM;
+	retval = do_getname(filename, tmp);
 	if (retval < 0)
-		putname( (char *) page );
+		putname(tmp);
 	else
-		*result = (char *) page;
+		*result = tmp;
 	return retval;
 }
 
-void putname(char * name)
-{
-	if (name_page_cache == 0)
-		name_page_cache = (unsigned long) name;
-	else
-		free_page((unsigned long) name);
-}
-
 /*
  *	permission()
  *
@@ -143,155 +222,416 @@
 	inode->i_writecount--;
 }
 
-/*
- * lookup() looks up one part of a pathname, using the fs-dependent
- * routines (currently minix_lookup) for it. It also checks for
- * fathers (pseudo-roots, mount-points)
+static /*inline */ int concat(struct qstr * name, struct qstr * appendix, char * buf)
+{
+	int totallen = name->len;
+	if(name->len > MAX_TRANS_FILELEN ||
+	   appendix->len > MAX_TRANS_SUFFIX) {
+		return -ENAMETOOLONG;
+	}
+	memcpy(buf, name->name, name->len);
+	memcpy(buf + name->len, appendix->name, appendix->len);
+	totallen += appendix->len;
+	buf[totallen] = '\0';
+	return totallen;
+}
+
+/* Internal lookup() using the new generic dcache.
+ * buf must only be supplied if appendix!=NULL.
  */
-int lookup(struct inode * dir,const char * name, int len,
-           struct inode ** result)
+static int cached_lookup(struct inode * dir, struct qstr * name,
+			 struct qstr * appendix, char * buf,
+			 struct qstr * res_name, struct dentry ** res_entry,
+			 struct inode ** result)
 {
-	struct super_block * sb;
-	int perm;
+	struct qstr tmp = { name->name, name->len };
+	int error;
+	struct dentry * cached;
 
 	*result = NULL;
-	if (!dir)
-		return -ENOENT;
-/* check permissions before traversing mount-points */
-	perm = permission(dir,MAY_EXEC);
-	if (len==2 && get_unaligned((u16 *) name) == 0x2e2e) {
-		if (dir == current->fs->root) {
-			*result = dir;
-			return 0;
-		} else if ((sb = dir->i_sb) && (dir == sb->s_mounted)) {
-			iput(dir);
-			dir = sb->s_covered;
-			if (!dir)
-				return -ENOENT;
-			dir->i_count++;
-		}
-	}
-	if (!dir->i_op || !dir->i_op->lookup) {
-		iput(dir);
-		return -ENOTDIR;
-	}
- 	if (perm != 0) {
-		iput(dir);
-		return perm;
-	}
-	if (!len) {
-		*result = dir;
-		return 0;
+	if(name->len >= D_MAXLEN)
+		return -ENAMETOOLONG;
+	vfs_lock();
+	cached = d_lookup(dir, name, appendix);
+	if(cached) {
+		struct inode *inode = NULL;
+
+		if(cached->u.d_inode && (inode = d_inode(&cached))) {
+			error = 0;
+			if(appendix && res_name) {
+				tmp.len = error = concat(name, appendix, buf);
+				tmp.name = buf;
+				if(error > 0)
+					error = 0;
+			}
+		} else {
+			error = -ENOENT;
+		}
+		vfs_unlock();
+		if(res_entry)
+			*res_entry = cached;
+
+		/* Since we are bypassing the iget() mechanism, we have to
+		 * fabricate the act of crossing any mount points.
+		 */
+		if(!error && inode && inode->i_mount) {
+			do {
+				struct inode *mnti = inode->i_mount;
+				iinc(mnti);
+				iput(inode);
+				inode = mnti;
+			} while(inode->i_mount);
+		}
+		*result = inode;
+		goto done;
+	} else
+		vfs_unlock();
+
+	if(appendix) {
+		tmp.len = error = concat(name, appendix, buf);
+		tmp.name = buf;
+		if(error < 0)
+			goto done;
+	}
+	atomic_inc(&dir->i_count);
+	error = dir->i_op->lookup(dir, tmp.name, tmp.len, result);
+	if(dir->i_dentry && tmp.len &&
+	   (!error || (error == -ENOENT && (!dir->i_sb || !dir->i_sb->s_type ||
+	    !(dir->i_sb->s_type->fs_flags & FS_NO_DCACHE))))) {
+		struct dentry * res;
+		vfs_lock();
+		res = d_entry(dir->i_dentry, &tmp, error ? NULL : *result);
+		vfs_unlock();
+		if(res_entry)
+			*res_entry = res;
+	}
+done:
+	if(res_name) {
+                if(error) {
+		        res_name->name = name->name;
+		        res_name->len = name->len;
+                } else {
+		        res_name->name = tmp.name;
+		        res_name->len = tmp.len;
+                }
 	}
-	return dir->i_op->lookup(dir, name, len, result);
+	return error;
 }
 
-int follow_link(struct inode * dir, struct inode * inode,
-	int flag, int mode, struct inode ** res_inode)
-{
-	if (!dir || !inode) {
-		iput(dir);
-		iput(inode);
-		*res_inode = NULL;
-		return -ENOENT;
+#ifdef CONFIG_TRANS_NAMES
+/* If a normal filename is seen, try to determine whether a
+ * "#keyword=context#" file exists and return the new filename.
+ * If the name is to be created (create_mode), check whether a
+ * "#keyword=CREATE" name exists and optionally return the corresponding
+ * context name even if it didn't exist before.
+ */
+static int check_suffixes(struct inode * dir, struct qstr * name,
+			  int create_mode, char * buf,
+			  struct qstr * res_name, struct dentry ** res_entry,
+			  struct inode ** result)
+{
+	struct translations * trans;
+	char * env;
+	struct qstr * suffixes;
+	int i;
+	int error = -ENOENT;
+
+	if(!buf)
+		panic("buf==NULL");
+	env = env_transl();
+#ifdef CONFIG_TRANS_RESTRICT
+	if(!env && dir->i_gid != CONFIG_TRANS_GID) {
+		return error;
+        }
+#endif
+	trans = get_translations(env);
+	suffixes = create_mode ? trans->c_name : trans->name;
+	for(i = 0; i < trans->count; i++) {
+		error = cached_lookup(dir, name, &suffixes[i],
+				      buf, res_name, res_entry, result);
+		if(!error) {
+			if(res_name && create_mode) {
+				/* buf == res_name->name, but is writable */
+				memcpy(buf + name->len,
+				       trans->name[i].name,
+				       trans->name[i].len);
+				res_name->len = name->len + trans->name[i].len;
+                                buf[res_name->len] = '\0';
+			}
+			break;
+		}
 	}
-	if (!inode->i_op || !inode->i_op->follow_link) {
-		iput(dir);
-		*res_inode = inode;
-		return 0;
+	if(env)
+		free_page((unsigned long)trans);
+	return error;
+}
+
+#endif
+
+/* Any operations involving reserved names at the VFS level should go here. */
+static /*inline*/ int reserved_lookup(struct inode * dir, struct qstr * name,
+				      int create_mode, char * buf,
+				      struct inode ** result)
+{
+	int error = -ENOENT;
+	if(name->name[0] == '.') {
+		if(name->len == 1) {
+			*result = dir;
+			error = 0;
+		} else if (name->len==2 && name->name[1] == '.') {
+			if (dir == current->fs->root) {
+				*result = dir;
+				error = 0;
+			}
+			else if(dir->i_dentry) {
+				error = 0;
+				*result = dir->i_dentry->d_parent->u.d_inode;
+				if(!*result) {
+					printk("dcache parent directory is lost");
+					error = -ESTALE;	/* random error */
+				}
+			}
+		}
+		if(!error)
+			atomic_inc(&(*result)->i_count);
 	}
-	return inode->i_op->follow_link(dir,inode,flag,mode,res_inode);
+	return error;
 }
 
-/*
- *	dir_namei()
- *
- * dir_namei() returns the inode of the directory of the
- * specified name, and the name within that directory.
+/* In difference to the former version, lookup() no longer eats the dir. */
+static /*inline*/ int lookup(struct inode * dir, struct qstr * name, int create_mode,
+			     char * buf, struct qstr * res_name,
+			     struct dentry ** res_entry, struct inode ** result)
+{
+	int perm;
+
+	*result = NULL;
+	perm = -ENOENT;
+ 	if (!dir)
+		goto done;
+
+	/* Check permissions before traversing mount-points. */
+	perm = permission(dir,MAY_EXEC);
+ 	if (perm)
+		goto done;
+	perm = reserved_lookup(dir, name, create_mode, buf, result);
+	if(!perm) {
+		if(res_name) {
+			res_name->name = name->name;
+			res_name->len = name->len;
+		}
+		goto done;
+	}
+	perm = -ENOTDIR;
+	if (!dir->i_op || !dir->i_op->lookup)
+		goto done;
+#ifdef CONFIG_TRANS_NAMES /* try suffixes */
+	perm = check_suffixes(dir, name, 0, buf, res_name, res_entry, result);
+	if(perm) /* try original name */
+#endif
+		perm = cached_lookup(dir, name, NULL, buf, res_name, res_entry, result);
+#ifdef CONFIG_TRANS_NAMES
+	if(perm == -ENOENT && create_mode) { /* try the =CREATE# suffix */
+		struct inode * dummy;
+		if(!check_suffixes(dir, name, 1, buf, res_name, NULL, &dummy)) {
+			iput(dummy);
+		}
+	}
+#endif
+done:
+	return perm;
+}
+
+/* [8-Feb-97 T. Schoebel-Theuer] follow_link() modified for generic operation
+ * on the VFS layer: first call <fs>_readlink() and then open_namei().
+ * All <fs>_follow_link() are not used any more and may be eliminated
+ * (by Linus; I refrained in order to not break other patches).
+ * Single exeption is procfs, where proc_follow_link() is used
+ * internally (and perhaps should be rewritten).
+ * Note: [partly obsolete] I removed parameters flag and mode, since now
+ * __namei() is called instead of open_namei(). In the old semantics,
+ * the _last_ instance of open_namei() did the real create() if O_CREAT was
+ * set and the name existed already in form of a symlink. This has been
+ * simplified now, and also the semantics when combined with O_EXCL has changed.
+ ****************************************************************************
+ * [13-Feb-97] Complete rewrite -> functionality of reading symlinks factored
+ * out into _read_link(). The above notes remain valid in principle.
  */
-static int dir_namei(const char *pathname, int *namelen, const char **name,
-                     struct inode * base, struct inode **res_inode)
+static /*inline*/ int _read_link(struct inode * inode, char ** linkname, int loopcount)
 {
-	unsigned char c;
-	const char * thisname;
-	int len,error;
-	struct inode * inode;
+	unsigned long old_fs;
+	int error;
 
-	*res_inode = NULL;
-	if (!base) {
-		base = current->fs->pwd;
-		base->i_count++;
+	error = -ENOSYS;
+	if (!inode->i_op || !inode->i_op->readlink)
+		goto done;
+	error = -ELOOP;
+	if (current->link_count + loopcount > 10)
+		goto done;
+	error = -ENOMEM;
+	if(!*linkname && !(*linkname = get_page()))
+		goto done;
+	if (DO_UPDATE_ATIME(inode)) {
+		inode->i_atime = CURRENT_TIME;
+		inode->i_dirt = 1;
+	}
+	atomic_inc(&inode->i_count);
+	old_fs = get_fs();
+	set_fs(KERNEL_DS);
+	error = inode->i_op->readlink(inode, *linkname, PAGE_SIZE);
+	set_fs(old_fs);
+	if(!error) {
+		error = -ENOENT; /* ? or other error code ? */
+	} else if(error > 0) {
+		(*linkname)[error] = '\0';
+		error = 0;
 	}
-	if ((c = *pathname) == '/') {
-		iput(base);
+done:
+	iput(inode);
+	return error;
+}
+
+/* [13-Feb-97 T. Schoebel-Theuer] complete rewrite:
+ * merged dir_name(), _namei() and follow_link() into one new routine
+ * that obeys all the special cases hidden in the old routines in a
+ * (hopefully) systematic way:
+ * parameter retrieve_mode is bitwise or'ed of the ST_* flags.
+ * if res_inode is a NULL pointer, dont try to retrieve the last component
+ * at all. Parameters with prefix last_ are used only if res_inode is
+ * non-NULL and refer to the last component of the path only.
+ */
+int __namei(int retrieve_mode, const char * name, struct inode * base,
+	    char * buf, struct inode ** res_dir, struct inode ** res_inode,
+	    struct qstr * last_name, struct dentry ** last_entry,
+	    int * last_error)
+{
+	char c;
+	struct qstr this;
+	char * linkname = NULL;
+	char * oldlinkname = NULL;
+	int trail_flag = 0;
+	int loopcount = 0;
+	int error;
+#ifdef DEBUG
+	if(last_name) {
+		last_name->name = "(Uninitialized)";
+		last_name->len = 15;
+	}
+#endif
+again:
+	error = -ENOENT;
+	this.name = name;
+	if (this.name[0] == '/') {
+		if(base)
+			iput(base);
+		if (__prefix_namei(retrieve_mode, this.name, base, buf,
+				   res_dir, res_inode,
+				   last_name, last_entry, last_error) == 0)
+			return 0;
 		base = current->fs->root;
-		pathname++;
-		base->i_count++;
+		atomic_inc(&base->i_count);
+		this.name++;
+	} else if (!base) {
+		base = current->fs->pwd;
+		atomic_inc(&base->i_count);
 	}
-	while (1) {
-		thisname = pathname;
-		for(len=0;(c = *(pathname++))&&(c != '/');len++)
-			/* nothing */ ;
-		if (!c)
+	for(;;) {
+		struct inode * inode;
+		const char * tmp = this.name;
+		int len;
+
+		for(len = 0; (c = *tmp++) && (c != '/'); len++) ;
+		this.len = len;
+		if(!c)
 			break;
-		base->i_count++;
-		error = lookup(base, thisname, len, &inode);
-		if (error) {
-			iput(base);
-			return error;
+		while((c = *tmp) == '/') /* remove embedded/trailing slashes */
+			tmp++;
+		if(!c) {
+			trail_flag = 1;
+			if(retrieve_mode & NAM_NO_TRAILSLASH) {
+				error = -EISDIR;
+				goto alldone;
+			}
+			break;		
 		}
-		error = follow_link(base,inode,0,0,&base);
+#if 0
+		if(atomic_read(&base->i_count) == 0)
+			printk("vor lookup this=%s tmp=%s\n", this.name, tmp);
+#endif
+		error = lookup(base, &this, 0, buf, NULL, NULL, &inode);
+#if 0
+		if(atomic_read(&base->i_count) == 0)
+			printk("nach lookup this=%s tmp=%s\n", this.name, tmp);
+#endif
 		if (error)
-			return error;
-	}
-	if (!base->i_op || !base->i_op->lookup) {
+			goto alldone;
+		if(S_ISLNK(inode->i_mode)) {
+			error = _read_link(inode, &linkname, loopcount);
+			if(error)
+				goto alldone;
+			current->link_count++;
+			error = __namei((retrieve_mode & 
+					 ~(NAM_SEMLOCK|NAM_TRANSCREATE|NAM_NO_TRAILSLASH))
+					| NAM_FOLLOW_LINK,
+					linkname, base, buf,
+					&base, &inode, NULL, NULL, NULL);
+			current->link_count--;
+			if(error)
+				goto alldone;
+		}
+#if 0
+		if(atomic_read(&base->i_count) == 0)
+			printk("this=%s tmp=%s\n", this.name, tmp);
+#endif
+		this.name = tmp;
 		iput(base);
-		return -ENOTDIR;
+		base = inode;
 	}
-	*name = thisname;
-	*namelen = len;
-	*res_inode = base;
-	return 0;
-}
-
-static int _namei(const char * pathname, struct inode * base,
-                  int follow_links, struct inode ** res_inode)
-{
-	const char *basename;
-	int namelen,error;
-	struct inode * inode;
-
-	translate_namei(pathname, base, follow_links, res_inode);
-	*res_inode = NULL;
-	error = dir_namei(pathname, &namelen, &basename, base, &base);
-	if (error)
-		return error;
-	base->i_count++;	/* lookup uses up base */
-	error = lookup(base, basename, namelen, &inode);
-	if (error) {
-		iput(base);
-		return error;
+	if(res_inode) {
+		if(retrieve_mode & NAM_SEMLOCK)
+			down(&base->i_sem);
+		error = lookup(base, &this, retrieve_mode & NAM_TRANSCREATE,
+			       buf, last_name, last_entry, res_inode);
+		if(!error && S_ISLNK((*res_inode)->i_mode) &&
+		   ((retrieve_mode & NAM_FOLLOW_LINK) ||
+		    (trail_flag && (retrieve_mode & NAM_FOLLOW_TRAILSLASH)))) {
+			char * tmp;
+
+			error = _read_link(*res_inode, &linkname, loopcount);
+			if(error)
+				goto lastdone;
+			if(retrieve_mode & NAM_SEMLOCK)
+				up(&base->i_sem);
+			/* exchange pages */
+			name = tmp = linkname;
+			linkname = oldlinkname; oldlinkname = tmp;
+			loopcount++;
+			goto again; /* Tail recursion elimination "by hand",
+				     * uses less dynamic memory.
+				     */
+
+			/* Note that trail_flag is not reset, so it
+			 * does not matter in a symlink chain where a
+			 * trailing slash indicates a directory endpoint.
+			 */
+		}
+		if(!error && trail_flag && !S_ISDIR((*res_inode)->i_mode)) {
+			iput(*res_inode);
+			error = -ENOTDIR;
+		}
+	lastdone:
+		if(last_error) {
+			*last_error = error;
+			error = 0;
+		}
 	}
-	if (follow_links) {
-		error = follow_link(base, inode, 0, 0, &inode);
-		if (error)
-			return error;
-	} else
+alldone:
+	if(!error && res_dir)
+		*res_dir = base;
+	else
 		iput(base);
-	*res_inode = inode;
-	return 0;
-}
-
-int lnamei(const char *pathname, struct inode **res_inode)
-{
-	int error;
-	char * tmp;
-
-	error = getname(pathname, &tmp);
-	if (!error) {
-		error = _namei(tmp, NULL, 0, res_inode);
-		putname(tmp);
-	}
+	putname(linkname);
+	putname(oldlinkname);
 	return error;
 }
 
@@ -302,14 +642,20 @@
  * Open, link etc use their own routines, but this is enough for things
  * like 'chmod' etc.
  */
-int namei(const char *pathname, struct inode **res_inode)
+
+/* [Feb 1997 T.Schoebel-Theuer] lnamei() completely removed; can be
+ * simulated when calling with retrieve_mode==NAM_FOLLOW_TRAILSLASH.
+ */
+int namei(int retrieve_mode, const char *pathname, struct inode **res_inode)
 {
 	int error;
 	char * tmp;
 
 	error = getname(pathname, &tmp);
 	if (!error) {
-		error = _namei(tmp, NULL, 1, res_inode);
+		char buf[MAX_TRANS_FILELEN+MAX_TRANS_SUFFIX+2];
+		error = __namei(retrieve_mode, tmp, NULL,
+				buf, NULL, res_inode, NULL, NULL, NULL);
 		putname(tmp);
 	}
 	return error;
@@ -328,40 +674,30 @@
  * which is a lot more logical, and also allows the "no perm" needed
  * for symlinks (where the permissions are checked later).
  */
-int
-open_namei(const char * pathname, int flag, int mode,
+int open_namei(const char * pathname, int flag, int mode,
                struct inode ** res_inode, struct inode * base)
 {
-	const char * basename;
-	int namelen,error;
-	struct inode * dir, *inode;
+	char buf[MAX_TRANS_FILELEN+MAX_TRANS_SUFFIX+2];
+	struct qstr last;
+	int error;
+	int lasterror;
+	struct inode * dir, * inode;
+	int namei_mode;
 
-	translate_open_namei(pathname, flag, mode, res_inode, base);
 	mode &= S_IALLUGO & ~current->fs->umask;
 	mode |= S_IFREG;
-	error = dir_namei(pathname, &namelen, &basename, base, &dir);
+
+	namei_mode = NAM_FOLLOW_LINK;
+	if(flag & O_CREAT)
+		namei_mode |= NAM_SEMLOCK|NAM_TRANSCREATE|NAM_NO_TRAILSLASH;
+	error = __namei(namei_mode, pathname, base, buf,
+			&dir, &inode, &last, NULL, &lasterror);
 	if (error)
-		return error;
-	if (!namelen) {			/* special case: '/usr/' etc */
-		if (flag & 2) {
-			iput(dir);
-			return -EISDIR;
-		}
-		/* thanks to Paul Pluzhnikov for noticing this was missing.. */
-		if ((error = permission(dir,ACC_MODE(flag))) != 0) {
-			iput(dir);
-			return error;
-		}
-		*res_inode=dir;
-		return 0;
-	}
-	dir->i_count++;		/* lookup eats the dir */
+		goto exit;
+	error = lasterror;
 	if (flag & O_CREAT) {
-		down(&dir->i_sem);
-		error = lookup(dir, basename, namelen, &inode);
 		if (!error) {
 			if (flag & O_EXCL) {
-				iput(inode);
 				error = -EEXIST;
 			}
 		} else if (IS_RDONLY(dir))
@@ -371,31 +707,31 @@
 		else if ((error = permission(dir,MAY_WRITE | MAY_EXEC)) != 0)
 			;	/* error is already set! */
 		else {
-			dir->i_count++;		/* create eats the dir */
+			d_del(d_lookup(dir, &last, NULL), D_REMOVE);
+			atomic_inc(&dir->i_count);	/* create eats the dir */
 			if (dir->i_sb && dir->i_sb->dq_op)
 				dir->i_sb->dq_op->initialize(dir, -1);
-			error = dir->i_op->create(dir, basename, namelen, mode, res_inode);
+			error = dir->i_op->create(dir, last.name, last.len,
+						  mode, res_inode);
+#ifdef CONFIG_OMIRR
+			if(!error)
+				omirr_print(dir->i_dentry, NULL, &last,
+					    " c %ld %d ", CURRENT_TIME, mode);
+#endif
 			up(&dir->i_sem);
-			iput(dir);
-			return error;
+			goto exit_dir;
 		}
 		up(&dir->i_sem);
-	} else
-		error = lookup(dir, basename, namelen, &inode);
-	if (error) {
-		iput(dir);
-		return error;
 	}
-	error = follow_link(dir,inode,flag,mode,&inode);
 	if (error)
-		return error;
+		goto exit_inode;
+
 	if (S_ISDIR(inode->i_mode) && (flag & 2)) {
-		iput(inode);
-		return -EISDIR;
+		error = -EISDIR;
+		goto exit_inode;
 	}
 	if ((error = permission(inode,ACC_MODE(flag))) != 0) {
-		iput(inode);
-		return error;
+		goto exit_inode;
 	}
 	if (S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {
 		/*
@@ -410,86 +746,102 @@
 	}
 	else if (S_ISBLK(inode->i_mode) || S_ISCHR(inode->i_mode)) {
 		if (IS_NODEV(inode)) {
-			iput(inode);
-			return -EACCES;
+			error = -EACCES;
+			goto exit_inode;
 		}
 		flag &= ~O_TRUNC;
 	} else {
 		if (IS_RDONLY(inode) && (flag & 2)) {
-			iput(inode);
-			return -EROFS;
+			error = -EROFS;
+			goto exit_inode;
 		}
 	}
 	/*
-	 * An append-only file must be opened in append mode for writing
+	 * An append-only file must be opened in append mode for writing.
 	 */
 	if (IS_APPEND(inode) && ((flag & FMODE_WRITE) && !(flag & O_APPEND))) {
-		iput(inode);
-		return -EPERM;
+		error = -EPERM;
+		goto exit_inode;
 	}
 	if (flag & O_TRUNC) {
-		if ((error = get_write_access(inode))) {
-			iput(inode);
-			return error;
-		}
+		if ((error = get_write_access(inode)))
+			goto exit_inode;
 		/*
-		 * Refuse to truncate files with mandatory locks held on them
+		 * Refuse to truncate files with mandatory locks held on them.
 		 */
 		error = locks_verify_locked(inode);
-		if (error) {
-			iput(inode);
-			return error;
-		}
+		if (error)
+			goto exit_inode;
 		if (inode->i_sb && inode->i_sb->dq_op)
 			inode->i_sb->dq_op->initialize(inode, -1);
 			
 		error = do_truncate(inode, 0);
 		put_write_access(inode);
-		if (error) {
-			iput(inode);
-			return error;
-		}
 	} else
 		if (flag & FMODE_WRITE)
 			if (inode->i_sb && inode->i_sb->dq_op)
 				inode->i_sb->dq_op->initialize(inode, -1);
-	*res_inode = inode;
-	return 0;
+exit_inode:
+	if(error) {
+		if(!lasterror)
+			iput(inode);
+	} else
+		*res_inode = inode;
+exit_dir:
+	iput(dir);
+exit:
+	return error;
 }
 
 int do_mknod(const char * filename, int mode, dev_t dev)
 {
-	const char * basename;
-	int namelen, error;
+	char buf[MAX_TRANS_FILELEN+MAX_TRANS_SUFFIX+2];
+	struct qstr last;
+	int error, lasterror;
 	struct inode * dir;
+	struct inode * inode;
 
 	mode &= ~current->fs->umask;
-	error = dir_namei(filename, &namelen, &basename, NULL, &dir);
+	error = __namei(NAM_FOLLOW_LINK|NAM_TRANSCREATE|NAM_NO_TRAILSLASH,
+			filename, NULL, buf,
+			&dir, &inode, &last, NULL, &lasterror);
 	if (error)
-		return error;
-	if (!namelen) {
-		iput(dir);
-		return -ENOENT;
+		goto exit;
+	if(!lasterror) {
+		error = -EEXIST;
+		goto exit_inode;
+	}
+	if (!last.len) {
+		error = -ENOENT;
+		goto exit_inode;
 	}
 	if (IS_RDONLY(dir)) {
-		iput(dir);
-		return -EROFS;
-	}
-	if ((error = permission(dir,MAY_WRITE | MAY_EXEC)) != 0) {
-		iput(dir);
-		return error;
+		error = -EROFS;
+		goto exit_inode;
 	}
+	if ((error = permission(dir,MAY_WRITE | MAY_EXEC)) != 0)
+		goto exit_inode;
 	if (!dir->i_op || !dir->i_op->mknod) {
-		iput(dir);
-		return -EPERM;
+		error = -ENOSYS; /* instead of EPERM, what does Posix say? */
+		goto exit_inode;
 	}
-	dir->i_count++;
+	atomic_inc(&dir->i_count);
 	if (dir->i_sb && dir->i_sb->dq_op)
 		dir->i_sb->dq_op->initialize(dir, -1);
 	down(&dir->i_sem);
-	error = dir->i_op->mknod(dir,basename,namelen,mode,dev);
+	d_del(d_lookup(dir, &last, NULL), D_REMOVE);
+	error = dir->i_op->mknod(dir, last.name, last.len, mode, dev);
+#ifdef CONFIG_OMIRR
+	if(!error)
+		omirr_print(dir->i_dentry, NULL, &last, " n %ld %d %d ",
+			    CURRENT_TIME, mode, dev);
+#endif
 	up(&dir->i_sem);
+exit_inode:
+	if(!lasterror)
+		iput(inode);
 	iput(dir);
+exit:
 	return error;
 }
 
@@ -522,75 +874,59 @@
 	return error;
 }
 
-/*
- * Some operations need to remove trailing slashes for POSIX.1
- * conformance. For rename we also need to change the behaviour
- * depending on whether we had a trailing slash or not.. (we
- * cannot rename normal files with trailing slashes, only dirs)
- *
- * "dummy" is used to make sure we don't do "/" -> "".
+/* [Feb-97 T. Schoebel-Theuer] remove_trailing_slashes() is now obsolete,
+ * its functionality is handled by observing trailing slashes in __namei().
  */
-static int remove_trailing_slashes(char * name)
+static inline int do_mkdir(const char * pathname, int mode)
 {
-	int result;
-	char dummy[1];
-	char *remove = dummy+1;
-
-	for (;;) {
-		char c = *name;
-		name++;
-		if (!c)
-			break;
-		if (c != '/') {
-			remove = NULL;
-			continue;
-		}
-		if (remove)
-			continue;
-		remove = name;
-	}
-
-	result = 0;
-	if (remove) {
-		remove[-1] = 0;
-		result = 1;
-	}
-
-	return result;
-}
-
-static int do_mkdir(const char * pathname, int mode)
-{
-	const char * basename;
-	int namelen, error;
+	char buf[MAX_TRANS_FILELEN+MAX_TRANS_SUFFIX+2];
+	struct qstr last;
+	int error, lasterror;
 	struct inode * dir;
+	struct inode * inode;
 
-	error = dir_namei(pathname, &namelen, &basename, NULL, &dir);
+	mode &= 0777 & ~current->fs->umask;
+
+	error = __namei(NAM_FOLLOW_LINK|NAM_TRANSCREATE, pathname, NULL, buf,
+			&dir, &inode, &last, NULL, &lasterror);
 	if (error)
-		return error;
-	if (!namelen) {
-		iput(dir);
-		return -ENOENT;
+		goto exit;
+	if(!lasterror) {
+		error = -EEXIST;
+		goto exit_inode;
+	}
+	if (!last.len) {
+		error = -ENOENT;
+		goto exit_inode;
 	}
 	if (IS_RDONLY(dir)) {
-		iput(dir);
-		return -EROFS;
-	}
-	if ((error = permission(dir,MAY_WRITE | MAY_EXEC)) != 0) {
-		iput(dir);
-		return error;
+		error = -EROFS;
+		goto exit_inode;
 	}
+	if ((error = permission(dir,MAY_WRITE | MAY_EXEC)) != 0)
+		goto exit_inode;
 	if (!dir->i_op || !dir->i_op->mkdir) {
-		iput(dir);
-		return -EPERM;
+		error = -ENOSYS; /* instead of EPERM, what does Posix say? */
+		goto exit_inode;
 	}
-	dir->i_count++;
+	atomic_inc(&dir->i_count);
 	if (dir->i_sb && dir->i_sb->dq_op)
 		dir->i_sb->dq_op->initialize(dir, -1);
 	down(&dir->i_sem);
-	error = dir->i_op->mkdir(dir, basename, namelen, mode & 01777 & ~current->fs->umask);
+	d_del(d_lookup(dir, &last, NULL), D_REMOVE);
+	mode &= 01777 & ~current->fs->umask;
+	error = dir->i_op->mkdir(dir, last.name, last.len, mode);
+#ifdef CONFIG_OMIRR
+	if(!error)
+		omirr_print(dir->i_dentry, NULL, &last, " d %ld %d ",
+			    CURRENT_TIME, mode);
+#endif
 	up(&dir->i_sem);
+exit_inode:
+	if(!lasterror)
+		iput(inode);
 	iput(dir);
+exit:
 	return error;
 }
 
@@ -602,7 +938,6 @@
 	lock_kernel();
 	error = getname(pathname,&tmp);
 	if (!error) {
-		remove_trailing_slashes(tmp);
 		error = do_mkdir(tmp,mode);
 		putname(tmp);
 	}
@@ -610,43 +945,125 @@
 	return error;
 }
 
-static int do_rmdir(const char * name)
-{
-	const char * basename;
-	int namelen, error;
+#if 0 /* We need a "deletefs", someone please write it.  -DaveM */
+/* Perhaps this could be moved out into a new file. */
+static void basket_name(struct inode * dir, struct dentry * entry)
+{
+	char prefix[32];
+	struct qstr prename = { prefix, 14 };
+	struct qstr entname = { entry->d_name, entry->d_len };
+	struct inode * inode;
+	struct dentry * old = entry; /* dummy */
+	int i;
+	if(!entry || !(inode = d_inode(&entry)))
+		return;
+#if 0
+	if(atomic_read(&inode->i_count) > 2) {
+		extern void printpath(struct dentry *entry);
+
+		printk("Caution: in use ");
+		if(inode->i_dentry)
+			printpath(inode->i_dentry);
+		printk(" i_nlink=%d i_count=%d i_ddir_count=%d i_dent_count=%d\n",
+		       inode->i_nlink, atomic_read(&inode->i_count),
+		       inode->i_ddir_count, inode->i_dent_count);
+	}
+#endif
+        vfs_lock();
+	for(i = 1; old; i++) {
+		sprintf(prefix, ".deleted-%04d.", i);
+		old = d_lookup(dir, &prename, &entname);
+	}
+	d_move(entry, dir, &prename, &entname);
+        vfs_unlock();
+	iput(inode);
+}
+#endif
+
+static inline int do_rmdir(const char * name)
+{
+	char buf[MAX_TRANS_FILELEN+MAX_TRANS_SUFFIX+2];
+	struct qstr last;
+	struct dentry * lastent = NULL;
+	int error;
 	struct inode * dir;
+	struct inode * inode;
 
-	error = dir_namei(name, &namelen, &basename, NULL, &dir);
+	/* [T.Schoebel-Theuer] I'm not sure which flags to use here.
+	 *  Try the following on different platforms:
+	 * [0] rm -rf test test2
+	 * [1] ln -s test2 test
+	 * [2] mkdir test  || mkdir test2
+	 * [3] rmdir test  && mkdir test2
+	 * [4] rmdir test/
+	 * Now the rusults:
+	 * cmd  |   HP-UX   |  SunOS   |  Solaris | Old Linux | New Linux |
+	 * ----------------------------------------------------------------
+	 * [2]  |   (OK)    |  EEXIST  |  EEXIST  |  EEXIST   |  (OK)
+	 * [3]  |  ENOTDIR  |  ENOTDIR |  ENOTDIR |  ENOTDIR  |  ENOTDIR
+	 * [4]  |   (OK)    |  EINVAL  |  ENOTDIR |  ENOTDIR  |  (OK)
+	 * So I implemented the HP-UX semantics. If this is not right
+	 * for Posix compliancy, change the flags accordingly. If Posix
+	 * let the question open, I'd suggest to stay at the new semantics.
+	 * I'd even make case [3] work by adding 2 to the flags parameter
+	 * if Posix tolerates that.
+	 */
+	error = __namei(NAM_FOLLOW_TRAILSLASH, name, NULL, buf,
+			&dir, &inode, &last, &lastent, NULL);
 	if (error)
-		return error;
-	if (!namelen) {
-		iput(dir);
-		return -ENOENT;
-	}
+		goto exit;
 	if (IS_RDONLY(dir)) {
-		iput(dir);
-		return -EROFS;
-	}
-	if ((error = permission(dir,MAY_WRITE | MAY_EXEC)) != 0) {
-		iput(dir);
-		return error;
+		error = -EROFS;
+		goto exit_dir;
 	}
+	if ((error = permission(dir,MAY_WRITE | MAY_EXEC)) != 0)
+		goto exit_dir;
 	/*
-	 * A subdirectory cannot be removed from an append-only directory
+	 * A subdirectory cannot be removed from an append-only directory.
 	 */
 	if (IS_APPEND(dir)) {
-		iput(dir);
-		return -EPERM;
+		error = -EPERM;
+		goto exit_dir;
 	}
 	if (!dir->i_op || !dir->i_op->rmdir) {
-		iput(dir);
-		return -EPERM;
+		error = -ENOSYS; /* was EPERM */
+		goto exit_dir;
+	}
+	/* Disallow removals of mountpoints. */
+	if(inode->i_mount) {
+		error = -EBUSY;
+		goto exit_dir;
 	}
 	if (dir->i_sb && dir->i_sb->dq_op)
 		dir->i_sb->dq_op->initialize(dir, -1);
-	down(&dir->i_sem);
-	error = dir->i_op->rmdir(dir,basename,namelen);
-	up(&dir->i_sem);
+
+        down(&dir->i_sem);
+#if 0
+	if(lastent && d_isbasket(lastent)) {
+		d_del(lastent, D_REMOVE);
+		error = 0;
+		goto exit_lock;
+	}
+#endif
+	atomic_inc(&dir->i_count);
+	error = dir->i_op->rmdir(dir, last.name, last.len);
+#ifdef CONFIG_OMIRR
+	if(!error)
+		omirr_print(lastent, NULL, NULL, " r %ld ", CURRENT_TIME);
+#endif
+#if 0
+	if(!error && lastent)
+		basket_name(dir, lastent);
+exit_lock:
+#else
+	if(!error && lastent)
+		d_del(lastent, D_REMOVE);
+#endif
+        up(&dir->i_sem);
+exit_dir:
+	iput(inode);
+	iput(dir);
+exit:
 	return error;
 }
 
@@ -658,7 +1075,6 @@
 	lock_kernel();
 	error = getname(pathname,&tmp);
 	if (!error) {
-		remove_trailing_slashes(tmp);
 		error = do_rmdir(tmp);
 		putname(tmp);
 	}
@@ -666,43 +1082,93 @@
 	return error;
 }
 
-static int do_unlink(const char * name)
+static inline int do_unlink(const char * name)
 {
-	const char * basename;
-	int namelen, error;
+	char buf[MAX_TRANS_FILELEN+MAX_TRANS_SUFFIX+2];
+	struct qstr last;
+	struct dentry * lastent = NULL;
+	int error;
 	struct inode * dir;
+	struct inode * inode;
 
-	error = dir_namei(name, &namelen, &basename, NULL, &dir);
+	/* HP-UX shows a strange behaviour:
+	 * touch y; ln -s y x; rm x/
+	 * this succeeds and removes the file y, not the symlink x!
+	 * Solaris and old Linux remove the symlink instead, and
+	 * old SunOS complains ENOTDIR.
+	 * I chose the SunOS behaviour (by not using NAM_FOLLOW_TRAILSLASH),
+	 * but I'm not shure whether I should.
+	 * The current code generally prohibits using trailing slashes with
+	 * non-directories if the name already exists, but not if
+	 * it is to be newly created. 
+	 * Perhaps this should be further strengthened (by introducing
+	 * an additional flag bit indicating whether trailing slashes are
+	 * allowed) to get it as consistant as possible, but I don't know
+	 * what Posix says.
+	 */
+	error = __namei(NAM_NO_TRAILSLASH, name, NULL, buf,
+			&dir, &inode, &last, &lastent, NULL);
 	if (error)
-		return error;
-	if (!namelen) {
-		iput(dir);
-		return -EPERM;
-	}
+		goto exit;
 	if (IS_RDONLY(dir)) {
-		iput(dir);
-		return -EROFS;
-	}
-	if ((error = permission(dir,MAY_WRITE | MAY_EXEC)) != 0) {
-		iput(dir);
-		return error;
+		error = -EROFS;
+		goto exit_dir;
 	}
+	if ((error = permission(dir,MAY_WRITE | MAY_EXEC)) != 0)
+		goto exit_dir;
 	/*
-	 * A file cannot be removed from an append-only directory
+	 * A file cannot be removed from an append-only directory.
 	 */
 	if (IS_APPEND(dir)) {
-		iput(dir);
-		return -EPERM;
+		error = -EPERM;
+		goto exit_dir;
 	}
 	if (!dir->i_op || !dir->i_op->unlink) {
-		iput(dir);
-		return -EPERM;
+		error = -ENOSYS; /* was EPERM */
+		goto exit_dir;
 	}
 	if (dir->i_sb && dir->i_sb->dq_op)
 		dir->i_sb->dq_op->initialize(dir, -1);
-	down(&dir->i_sem);
-	error = dir->i_op->unlink(dir,basename,namelen);
-	up(&dir->i_sem);
+
+        down(&dir->i_sem);
+#if 0
+	if(atomic_read(&inode->i_count) > 1) {
+		extern void printpath(struct dentry *entry);
+
+		printk("Fire ");
+		if(lastent)
+			printpath(lastent);
+		printk(" i_nlink=%d i_count=%d i_ddir_count=%d i_dent_count=%d\n",
+		       inode->i_nlink, atomic_read(&inode->i_count),
+		       inode->i_ddir_count, inode->i_dent_count);
+	}
+#endif
+#if 0
+	if(lastent && d_isbasket(lastent)) {
+		d_del(lastent, D_REMOVE);
+		error = 0;
+		goto exit_lock;
+	}
+#endif
+	atomic_inc(&dir->i_count);
+	error = dir->i_op->unlink(dir, last.name, last.len);
+#ifdef CONFIG_OMIRR
+	if(!error)
+		omirr_print(lastent, NULL, NULL, " u %ld ", CURRENT_TIME);
+#endif
+#if 0
+	if(!error && lastent)
+		basket_name(dir, lastent);
+exit_lock:
+#else
+	if(!error && lastent)
+		d_del(lastent, D_REMOVE);
+#endif
+        up(&dir->i_sem);
+exit_dir:
+	iput(inode);
+	iput(dir);
+exit:
 	return error;
 }
 
@@ -721,38 +1187,65 @@
 	return error;
 }
 
-static int do_symlink(const char * oldname, const char * newname)
+static inline int do_symlink(const char * oldname, const char * newname)
 {
+	char buf[MAX_TRANS_FILELEN+MAX_TRANS_SUFFIX+2];
+	struct qstr last;
+	int error, lasterror;
 	struct inode * dir;
-	const char * basename;
-	int namelen, error;
+	struct inode * inode;
 
-	error = dir_namei(newname, &namelen, &basename, NULL, &dir);
+	/* The following works on HP-UX and Solaris, by producing
+	 * a symlink chain:
+	 * rm -rf ? ; mkdir z ; ln -s z y ; ln -s y x/
+	 * Under old SunOS, the following occurs:
+	 * ln: x/: No such file or directory
+	 * Under old Linux, very strange things occur:
+	 * ln: cannot create symbolic link `x//y' to `y': No such file or directory
+	 * This is very probably a bug, but may be caused by the ln program
+	 * when checking for a directory target.
+	 *
+	 * I'm not shure whether to add NAM_NO_TRAILSLASH to inhibit trailing
+	 * slashes in the target generally.
+	 */
+	error = __namei(NAM_TRANSCREATE, newname, NULL, buf,
+			&dir, &inode, &last, NULL, &lasterror);
 	if (error)
-		return error;
-	if (!namelen) {
-		iput(dir);
-		return -ENOENT;
+		goto exit;
+	if(!lasterror) {
+		iput(inode);
+		error = -EEXIST;
+		goto exit_dir;
 	}
-	if (IS_RDONLY(dir)) {
-		iput(dir);
-		return -EROFS;
+	if (!last.len) {
+		error = -ENOENT;
+		goto exit_dir;
 	}
-	if ((error = permission(dir,MAY_WRITE | MAY_EXEC)) != 0) {
-		iput(dir);
-		return error;
+	if (IS_RDONLY(dir)) {
+		error = -EROFS;
+		goto exit_dir;
 	}
+	if ((error = permission(dir,MAY_WRITE | MAY_EXEC)) != 0)
+		goto exit_dir;
 	if (!dir->i_op || !dir->i_op->symlink) {
-		iput(dir);
-		return -EPERM;
+		error = -ENOSYS; /* was EPERM */
+		goto exit_dir;
 	}
-	dir->i_count++;
+	atomic_inc(&dir->i_count);
 	if (dir->i_sb && dir->i_sb->dq_op)
 		dir->i_sb->dq_op->initialize(dir, -1);
 	down(&dir->i_sem);
-	error = dir->i_op->symlink(dir,basename,namelen,oldname);
+	d_del(d_lookup(dir, &last, NULL), D_REMOVE);
+	error = dir->i_op->symlink(dir, last.name, last.len, oldname);
+#ifdef CONFIG_OMIRR
+	if(!error)
+		omirr_print(dir->i_dentry, NULL, &last,
+			    " s %ld %s\0", CURRENT_TIME, oldname);
+#endif
 	up(&dir->i_sem);
+exit_dir:
 	iput(dir);
+exit:
 	return error;
 }
 
@@ -775,149 +1268,198 @@
 	return error;
 }
 
-static int do_link(struct inode * oldinode, const char * newname)
+static inline int do_link(const char * oldname, const char * newname)
 {
-	struct inode * dir;
-	const char * basename;
-	int namelen, error;
+	char oldbuf[MAX_TRANS_FILELEN+MAX_TRANS_SUFFIX+2];
+	char newbuf[MAX_TRANS_FILELEN+MAX_TRANS_SUFFIX+2];
+	struct qstr oldlast;
+	struct qstr newlast;
+	struct dentry * oldent = NULL;
+	struct inode * oldinode;
+	struct inode * newinode;
+	struct inode * newdir;
+	int error, lasterror;
+
+	error = __namei(NAM_FOLLOW_LINK|NAM_NO_TRAILSLASH,
+			oldname, NULL, oldbuf,
+			NULL, &oldinode, &oldlast, &oldent, NULL);
+	if (error)
+		goto exit;
 
-	error = dir_namei(newname, &namelen, &basename, NULL, &dir);
-	if (error) {
-		iput(oldinode);
-		return error;
-	}
-	if (!namelen) {
-		iput(oldinode);
-		iput(dir);
-		return -EPERM;
-	}
-	if (IS_RDONLY(dir)) {
-		iput(oldinode);
-		iput(dir);
-		return -EROFS;
-	}
-	if (dir->i_dev != oldinode->i_dev) {
-		iput(dir);
-		iput(oldinode);
-		return -EXDEV;
-	}
-	if ((error = permission(dir,MAY_WRITE | MAY_EXEC)) != 0) {
-		iput(dir);
-		iput(oldinode);
-		return error;
+	error = __namei(NAM_FOLLOW_LINK|NAM_TRANSCREATE, newname, NULL, newbuf,
+			&newdir, &newinode, &newlast, NULL, &lasterror);
+	if (error)
+		goto old_exit;
+	if(!lasterror) {
+		iput(newinode);
+		error = -EEXIST;
+		goto new_exit;
+	}
+	if (!newlast.len) {
+		error = -EPERM;
+		goto new_exit;
+	}
+	if (IS_RDONLY(newdir)) {
+		error = -EROFS;
+		goto new_exit;
+	}
+	if (newdir->i_dev != oldinode->i_dev) {
+		error = -EXDEV;
+		goto new_exit;
 	}
+	if ((error = permission(newdir,MAY_WRITE | MAY_EXEC)) != 0)
+		goto new_exit;
 	/*
-	 * A link to an append-only or immutable file cannot be created
+	 * A link to an append-only or immutable file cannot be created.
 	 */
 	if (IS_APPEND(oldinode) || IS_IMMUTABLE(oldinode)) {
-		iput(dir);
-		iput(oldinode);
-		return -EPERM;
-	}
-	if (!dir->i_op || !dir->i_op->link) {
-		iput(dir);
-		iput(oldinode);
-		return -EPERM;
+		error = -EPERM;
+		goto new_exit;
 	}
-	dir->i_count++;
-	if (dir->i_sb && dir->i_sb->dq_op)
-		dir->i_sb->dq_op->initialize(dir, -1);
-	down(&dir->i_sem);
-	error = dir->i_op->link(oldinode, dir, basename, namelen);
-	up(&dir->i_sem);
-	iput(dir);
+	if (!newdir->i_op || !newdir->i_op->link) {
+		error = -ENOSYS; /* was EPERM */
+		goto new_exit;
+	}
+	atomic_inc(&oldinode->i_count);
+	atomic_inc(&newdir->i_count);
+	if (newdir->i_sb && newdir->i_sb->dq_op)
+		newdir->i_sb->dq_op->initialize(newdir, -1);
+	down(&newdir->i_sem);
+	d_del(d_lookup(newdir, &newlast, NULL), D_REMOVE);
+	error = newdir->i_op->link(oldinode, newdir, newlast.name, newlast.len);
+#ifdef CONFIG_OMIRR
+	if(!error)
+		omirr_print(oldent, newdir->i_dentry, &newlast,
+			    " l %ld ", CURRENT_TIME);
+#endif
+	up(&newdir->i_sem);
+new_exit:
+	iput(newdir);
+old_exit:
+	iput(oldinode);
+exit:
 	return error;
 }
 
 asmlinkage int sys_link(const char * oldname, const char * newname)
 {
 	int error;
-	char * to;
-	struct inode * oldinode;
+	char * from, * to;
 
 	lock_kernel();
-	error = lnamei(oldname, &oldinode);
-	if (error)
-		goto out;
-	error = getname(newname,&to);
-	if (error) {
-		iput(oldinode);
-		goto out;
+	error = getname(oldname,&from);
+	if (!error) {
+		error = getname(newname,&to);
+		if (!error) {
+			error = do_link(from,to);
+			putname(to);
+		}
+		putname(from);
 	}
-	error = do_link(oldinode,to);
-	putname(to);
-out:
 	unlock_kernel();
 	return error;
 }
 
-static int do_rename(const char * oldname, const char * newname, int must_be_dir)
+static inline int do_rename(const char * oldname, const char * newname)
 {
-	struct inode * old_dir, * new_dir;
-	const char * old_base, * new_base;
-	int old_len, new_len, error;
+	char oldbuf[MAX_TRANS_FILELEN+MAX_TRANS_SUFFIX+2];
+	struct qstr oldlast;
+	char newbuf[MAX_TRANS_FILELEN+MAX_TRANS_SUFFIX+2];
+	struct qstr newlast;
+	struct dentry * oldent = NULL;
+	struct inode * olddir, * newdir;
+	struct inode * oldinode, * newinode;
+	int error, newlasterror;
 
-	error = dir_namei(oldname, &old_len, &old_base, NULL, &old_dir);
+	error = __namei(NAM_FOLLOW_TRAILSLASH, oldname, NULL, oldbuf,
+			&olddir, &oldinode, &oldlast, &oldent, NULL);
 	if (error)
-		return error;
-	if ((error = permission(old_dir,MAY_WRITE | MAY_EXEC)) != 0) {
-		iput(old_dir);
-		return error;
+		goto exit;
+	if ((error = permission(olddir,MAY_WRITE | MAY_EXEC)) != 0)
+		goto old_exit;
+	if (!oldlast.len || (oldlast.name[0] == '.' &&
+	    (oldlast.len == 1 || (oldlast.name[1] == '.' &&
+	     oldlast.len == 2)))) {
+		error = -EPERM;
+		goto old_exit;
+	}
+	/* Disallow moves of mountpoints. */
+	if(oldinode->i_mount) {
+		error = -EBUSY;
+		goto old_exit;
 	}
-	if (!old_len || (old_base[0] == '.' &&
-	    (old_len == 1 || (old_base[1] == '.' &&
-	     old_len == 2)))) {
-		iput(old_dir);
-		return -EPERM;
-	}
-	error = dir_namei(newname, &new_len, &new_base, NULL, &new_dir);
-	if (error) {
-		iput(old_dir);
-		return error;
-	}
-	if ((error = permission(new_dir,MAY_WRITE | MAY_EXEC)) != 0){
-		iput(old_dir);
-		iput(new_dir);
-		return error;
-	}
-	if (!new_len || (new_base[0] == '.' &&
-	    (new_len == 1 || (new_base[1] == '.' &&
-	     new_len == 2)))) {
-		iput(old_dir);
-		iput(new_dir);
-		return -EPERM;
-	}
-	if (new_dir->i_dev != old_dir->i_dev) {
-		iput(old_dir);
-		iput(new_dir);
-		return -EXDEV;
-	}
-	if (IS_RDONLY(new_dir) || IS_RDONLY(old_dir)) {
-		iput(old_dir);
-		iput(new_dir);
-		return -EROFS;
+
+	error = __namei(NAM_FOLLOW_LINK|NAM_TRANSCREATE, newname, NULL, newbuf,
+			&newdir, &newinode, &newlast, NULL, &newlasterror);
+	if (error)
+		goto old_exit;
+	if ((error = permission(newdir,MAY_WRITE | MAY_EXEC)) != 0)
+		goto new_exit;
+	if (!newlast.len || (newlast.name[0] == '.' &&
+	    (newlast.len == 1 || (newlast.name[1] == '.' &&
+	     newlast.len == 2)))) {
+		error = -EPERM;
+		goto new_exit;
+	}
+	if (newdir->i_dev != olddir->i_dev) {
+		error = -EXDEV;
+		goto new_exit;
+	}
+	if (IS_RDONLY(newdir) || IS_RDONLY(olddir)) {
+		error = -EROFS;
+		goto new_exit;
 	}
 	/*
-	 * A file cannot be removed from an append-only directory
+	 * A file cannot be removed from an append-only directory.
+	 */
+	if (IS_APPEND(olddir)) {
+		error = -EPERM;
+		goto new_exit;
+	}
+	if (!olddir->i_op || !olddir->i_op->rename) {
+		error = -ENOSYS; /* was EPERM */
+		goto new_exit;
+	}
+#ifdef CONFIG_TRANS_NAMES
+	/* if oldname has been translated, but newname not (and
+	 * has not already a suffix), take over the suffix from oldname.
 	 */
-	if (IS_APPEND(old_dir)) {
-		iput(old_dir);
-		iput(new_dir);
-		return -EPERM;
-	}
-	if (!old_dir->i_op || !old_dir->i_op->rename) {
-		iput(old_dir);
-		iput(new_dir);
-		return -EPERM;
-	}
-	new_dir->i_count++;
-	if (new_dir->i_sb && new_dir->i_sb->dq_op)
-		new_dir->i_sb->dq_op->initialize(new_dir, -1);
-	down(&new_dir->i_sem);
-	error = old_dir->i_op->rename(old_dir, old_base, old_len, 
-		new_dir, new_base, new_len, must_be_dir);
-	up(&new_dir->i_sem);
-	iput(new_dir);
+	if(oldlast.name == oldbuf && newlast.name != newbuf &&
+	   newlast.name[newlast.len-1] != '#') {
+		int i = oldlast.len - 2;
+		while (i > 0 && oldlast.name[i] != '#')
+			i--;
+		memcpy(newbuf, newlast.name, newlast.len);
+		memcpy(newbuf+newlast.len, oldlast.name+i, oldlast.len - i);
+		newlast.len += oldlast.len - i;
+		newlast.name = newbuf;
+	}
+#endif
+	atomic_inc(&olddir->i_count);
+	atomic_inc(&newdir->i_count);
+	if (newdir->i_sb && newdir->i_sb->dq_op)
+		newdir->i_sb->dq_op->initialize(newdir, -1);
+	down(&newdir->i_sem);
+	error = olddir->i_op->rename(olddir, oldlast.name, oldlast.len, 
+		newdir, newlast.name, newlast.len);
+#ifdef CONFIG_OMIRR
+	if(!error)
+		omirr_print(oldent, newdir->i_dentry, &newlast,
+			    " m %ld ", CURRENT_TIME);
+#endif
+	if(!error) {
+		d_del(d_lookup(newdir, &newlast, NULL), D_REMOVE);
+		d_move(d_lookup(olddir, &oldlast, NULL), newdir, &newlast, NULL);
+	}
+	up(&newdir->i_sem);
+new_exit:
+	if(!newlasterror)
+		iput(newinode);
+	iput(newdir);
+old_exit:
+	iput(oldinode);
+	iput(olddir);
+exit:
 	return error;
 }
 
@@ -931,9 +1473,7 @@
 	if (!error) {
 		error = getname(newname,&to);
 		if (!error) {
-			error = do_rename(from,to,
-				remove_trailing_slashes(from) |
-				remove_trailing_slashes(to));
+			error = do_rename(from,to);
 			putname(to);
 		}
 		putname(from);

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov