patch-2.1.122 linux/fs/umsdos/namei.c

Next file: linux/fs/umsdos/rdir.c
Previous file: linux/fs/umsdos/mangle.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.121/linux/fs/umsdos/namei.c linux/fs/umsdos/namei.c
@@ -224,8 +224,8 @@
  * 
  * Return the status of the operation. 0 mean success.
  *
- * #Specification: create / file exist in DOS
- * Here is a situation. Trying to create a file with
+ * #Specification: create / file exists in DOS
+ * Here is a situation: we are trying to create a file with
  * UMSDOS. The file is unknown to UMSDOS but already
  * exists in the DOS directory.
  * 
@@ -254,19 +254,9 @@
 	int ret;
 	struct umsdos_info info;
 
-if (dentry->d_inode)
-printk("umsdos_create_any: %s/%s not negative!\n",
-dentry->d_parent->d_name.name, dentry->d_name.name);
-
-Printk (("umsdos_create_any /mn/: create %.*s in dir=%lu - nevercreat=/",
-(int) dentry->d_name.len, dentry->d_name.name, dir->i_ino));
-
-	check_dentry_path (dentry, "umsdos_create_any");
 	ret = umsdos_nevercreat (dir, dentry, -EEXIST);
-	if (ret) {
-Printk (("%d/\n", ret));
+	if (ret)
 		goto out;
-	}
 
 	ret = umsdos_parse (dentry->d_name.name, dentry->d_name.len, &info);
 	if (ret)
@@ -284,12 +274,12 @@
 	if (ret)
 		goto out_unlock;
 
-	/* create short name dentry */
+	/* do a real lookup to get the short name dentry */
 	fake = umsdos_lookup_dentry(dentry->d_parent, info.fake.fname, 
-					info.fake.len);
+					info.fake.len, 1);
 	ret = PTR_ERR(fake);
 	if (IS_ERR(fake))
-		goto out_unlock;
+		goto out_remove;
 
 	/* should not exist yet ... */
 	ret = -EEXIST;
@@ -300,39 +290,25 @@
 	if (ret)
 		goto out_remove;
 
-	inode = fake->d_inode;
-	umsdos_lookup_patch_new(fake, &info.entry, info.f_pos);
-
-Printk (("inode %p[%lu], count=%d ", inode, inode->i_ino, inode->i_count));
-Printk (("Creation OK: [dir %lu] %.*s pid=%d pos %ld\n",
-dir->i_ino, info.fake.len, info.fake.fname, current->pid, info.f_pos));
-
-	check_dentry_path (dentry, "umsdos_create_any: BEG dentry");
-	check_dentry_path (fake, "umsdos_create_any: BEG fake");
-
 	/*
 	 * Note! The long and short name might be the same,
 	 * so check first before doing the instantiate ...
 	 */
 	if (dentry != fake) {
-		/* long name also gets inode info */
+		inode = fake->d_inode;
 		inode->i_count++;
 		d_instantiate (dentry, inode);
 	}
-
-	check_dentry_path (dentry, "umsdos_create_any: END dentry");
-	check_dentry_path (fake, "umsdos_create_any: END fake");
+	umsdos_lookup_patch_new(dentry, &info.entry, info.f_pos);
 	goto out_dput;
 
 out_remove:
-if (ret == -EEXIST)
-printk("UMSDOS: out of sync, error [%ld], deleting %.*s %d %d pos %ld\n",
-dir->i_ino ,info.fake.len, info.fake.fname, -ret, current->pid, info.f_pos);
-	umsdos_delentry (dentry->d_parent, &info, 0);
+	if (ret == -EEXIST)
+		printk(KERN_WARNING "UMSDOS: out of sync, deleting %s/%s\n",
+			dentry->d_parent->d_name.name, info.fake.fname);
+	umsdos_delentry (dentry->d_parent, &info, S_ISDIR (info.entry.mode));
 
 out_dput:
-Printk (("umsdos_create %.*s ret = %d pos %ld\n",
-info.fake.len, info.fake.fname, ret, info.f_pos));
 	/* N.B. any value in keeping short name dentries? */
 	if (dentry != fake)
 		d_drop(fake);
@@ -345,11 +321,24 @@
 }
 
 /*
+ * Add a new file into the alternate directory.
+ * The file is added to the real MSDOS directory. If successful, it
+ * is then added to the EMD file.
+ * 
+ * Return the status of the operation. 0 mean success.
+ */
+int UMSDOS_create (struct inode *dir, struct dentry *dentry, int mode)
+{
+	return umsdos_create_any (dir, dentry, mode, 0, 0);
+}
+
+
+/*
  * Initialise the new_entry from the old for a rename operation.
  * (Only useful for umsdos_rename_f() below).
  */
 static void umsdos_ren_init (struct umsdos_info *new_info,
-			     struct umsdos_info *old_info, int flags)
+			     struct umsdos_info *old_info)
 {
 	/* != 0, this is the value of flags */
 	new_info->entry.mode = old_info->entry.mode;
@@ -359,10 +348,11 @@
 	new_info->entry.ctime = old_info->entry.ctime;
 	new_info->entry.atime = old_info->entry.atime;
 	new_info->entry.mtime = old_info->entry.mtime;
-	new_info->entry.flags = flags ? flags : old_info->entry.flags;
+	new_info->entry.flags = old_info->entry.flags;
 	new_info->entry.nlink = old_info->entry.nlink;
 }
 
+#ifdef OBSOLETE
 #define chkstk() \
 if (STACK_MAGIC != *(unsigned long *)current->kernel_stack_page){\
     printk(KERN_ALERT "UMSDOS: %s magic %x != %lx ligne %d\n" \
@@ -373,6 +363,7 @@
 
 #undef chkstk
 #define chkstk() do { } while (0);
+#endif
 
 /*
  * Rename a file (move) in the file system.
@@ -382,124 +373,199 @@
 			    struct inode *new_dir, struct dentry *new_dentry,
 			    int flags)
 {
-	int old_ret, new_ret;
-	struct dentry *old, *new, *dret;
-	struct inode *oldid = NULL;
-	int ret = -EPERM;
+	struct dentry *old, *new, *old_emd, *new_target = NULL;
+	int err, ret, rehash = 0;
 	struct umsdos_info old_info;
 	struct umsdos_info new_info;
 
-	old_ret = umsdos_parse (old_dentry->d_name.name,
+ 	ret = -EPERM;
+	err = umsdos_parse (old_dentry->d_name.name,
 				old_dentry->d_name.len, &old_info);
-	if (old_ret)
+	if (err)
 		goto out;
-	new_ret = umsdos_parse (new_dentry->d_name.name,
+	err = umsdos_parse (new_dentry->d_name.name,
 				new_dentry->d_name.len, &new_info);
-	if (new_ret)
+	if (err)
 		goto out;
 
-	check_dentry_path (old_dentry, "umsdos_rename_f OLD");
-	check_dentry_path (new_dentry, "umsdos_rename_f OLD");
+	/* Get the EMD dentry for the old parent */
+	old_emd = umsdos_get_emd_dentry(old_dentry->d_parent);
+	ret = PTR_ERR(old_emd);
+	if (IS_ERR(old_emd))
+		goto out;
 
-	chkstk ();
-Printk (("umsdos_rename %d %d ", old_ret, new_ret));
 	umsdos_lockcreate2 (old_dir, new_dir);
-	chkstk ();
 
-	ret = umsdos_findentry(old_dentry->d_parent, &old_info, 0);
-	chkstk ();
+	ret = umsdos_findentry(old_emd->d_parent, &old_info, 0);
 	if (ret) {
-Printk (("ret %d ", ret));
+		printk(KERN_ERR
+			"umsdos_rename_f: old entry %s/%s not in EMD, ret=%d\n",
+			old_dentry->d_parent->d_name.name, old_info.entry.name,
+			ret);
 		goto out_unlock;
 	}
 
 	/* check sticky bit on old_dir */
 	ret = -EPERM;
-	if (is_sticky(old_dir, old_info.entry.uid)) {
-		Printk (("sticky set on old "));
+	if (is_sticky(old_dir, old_info.entry.uid))
 		goto out_unlock;
-	}
 
-	/* Does new_name already exist? */
-	new_ret = umsdos_findentry(new_dentry->d_parent, &new_info, 0);
-	/* if destination file exists, are we allowed to replace it ? */
-	if (new_ret == 0 && is_sticky(new_dir, new_info.entry.uid)) {
-		Printk (("sticky set on new "));
-		goto out_unlock;
+	/*
+	 * Check whether the new_name already exists, and
+	 * if so whether we're allowed to replace it.
+	 */
+	err = umsdos_findentry(new_dentry->d_parent, &new_info, 0);
+	if (err == 0) {
+	 	/* Are we allowed to replace it? */
+		if (is_sticky(new_dir, new_info.entry.uid)) {
+Printk (("sticky set on new "));
+			goto out_unlock;
+		}
+		/* check whether it _really_ exists ... */
+		ret = -EEXIST;
+		if (new_dentry->d_inode)
+			goto out_unlock;
+
+		/* bogus lookup? complain and fix up the EMD ... */
+		printk(KERN_WARNING
+			"umsdos_rename_f: entry %s/%s exists, inode NULL??\n",
+			new_dentry->d_parent->d_name.name, new_info.entry.name);
+		err = umsdos_delentry(new_dentry->d_parent, &new_info,
+					S_ISDIR(new_info.entry.mode));
 	}
 
-	Printk (("new newentry "));
-	umsdos_ren_init (&new_info, &old_info, flags);
+Printk (("new newentry "));
+	/* create the new entry ... */
+	umsdos_ren_init (&new_info, &old_info);
+	if (flags)
+		new_info.entry.flags = flags;
 	ret = umsdos_newentry (new_dentry->d_parent, &new_info);
-	chkstk ();
 	if (ret) {
-Printk (("ret %d %d ", ret, new_info.fake.len));
+		printk(KERN_WARNING
+			"umsdos_rename_f: newentry %s/%s failed, ret=%d\n",
+			new_dentry->d_parent->d_name.name, new_info.entry.name,
+			ret);
 		goto out_unlock;
 	}
 
-	dret = umsdos_lookup_dentry(old_dentry->d_parent, old_info.fake.fname, 
-					old_info.fake.len);
-	ret = PTR_ERR(dret);
-	if (IS_ERR(dret))
+	/* If we're moving a hardlink, drop it first */
+	if (old_info.entry.flags & UMSDOS_HLINK) {
+		rehash = !list_empty(&old_dentry->d_hash);
+		d_drop(old_dentry);
+printk("umsdos_rename_f: moving hardlink %s/%s, rehash=%d\n",
+old_dentry->d_parent->d_name.name, old_info.entry.name, rehash);
+	}
+
+	/* Do a real lookup to get the old short name dentry */
+	old = umsdos_lookup_dentry(old_dentry->d_parent, old_info.fake.fname, 
+					old_info.fake.len, 1);
+	ret = PTR_ERR(old);
+	if (IS_ERR(old)) {
+		printk(KERN_WARNING
+			"umsdos_rename_f: lookup old dentry %s/%s, ret=%d\n",
+			old_dentry->d_parent->d_name.name, old_info.fake.fname,
+			ret);
 		goto out_unlock;
-#if 0
-	/* This is the same as dret */
-	oldid = dret->d_inode;
-	old = creat_dentry (old_info.fake.fname, old_info.fake.len,
-				oldid, old_dentry->d_parent);
-#endif
-	old = dret;
+	}
+	/* short and long name dentries match? */
+	if (old == old_dentry)
+		dput(old);
+
 	new = umsdos_lookup_dentry(new_dentry->d_parent, new_info.fake.fname, 
-					new_info.fake.len);
+					new_info.fake.len, 1);
 	ret = PTR_ERR(new);
-	if (IS_ERR(new))
+	if (IS_ERR(new)) {
+		printk(KERN_WARNING
+			"umsdos_rename_f: lookup new dentry %s/%s, ret=%d\n",
+			new_dentry->d_parent->d_name.name, new_info.fake.fname,
+			ret);
 		goto out_dput;
+	}
+	/*
+	 * Note! If the new short- and long-name dentries are
+	 * aliases, the target name will be destroyed by the
+	 * msdos-level rename. If in addition the old dentries
+	 * _aren't_ aliased, we'll need the original new name
+	 * for the final d_move, and so must make a copy here.
+	 *
+	 * Welcome to the mysteries of the dcache ...
+	 */
+	if (new == new_dentry) {
+		dput(new);
+		if (old != old_dentry) {
+			/* make a copy of the target dentry */
+			ret = -ENOMEM;
+			new_target = d_alloc(new_dentry->d_parent,
+						&new_dentry->d_name);
+			if (!new_target)
+				goto out_dput;
+		}
+	}
 
-	Printk (("msdos_rename "));
-	check_dentry_path (old, "umsdos_rename_f OLD2");
-	check_dentry_path (new, "umsdos_rename_f NEW2");
+	/* Do the msdos-level rename */
 	ret = msdos_rename (old_dir, old, new_dir, new);
-	chkstk ();
-printk("after m_rename ret %d ", ret);
-	/* dput(old); */
-	dput(new);
+Printk(("umsdos_rename_f: now %s/%s, ret=%d\n", 
+old->d_parent->d_name.name, old->d_name.name, ret));
+
+	if (new != new_dentry)
+		dput(new);
 
+	/* If the rename failed, remove the new EMD entry */
 	if (ret != 0) {
+Printk(("umsdos_rename_f: rename failed, ret=%d, removing %s/%s\n",
+ret, new_dentry->d_parent->d_name.name, new_info.entry.name));
 		umsdos_delentry (new_dentry->d_parent, &new_info,
 				 S_ISDIR (new_info.entry.mode));
-		chkstk ();
 		goto out_dput;
 	}
 
-	ret = umsdos_delentry (old_dentry->d_parent, &old_info,
-				 S_ISDIR (old_info.entry.mode));
-	chkstk ();
-	if (ret)
-		goto out_dput;
-#if 0
 	/*
-	 * Update f_pos so notify_change will succeed
-	 * if the file was already in use.
+	 * Rename successful ... remove the old name from the EMD.
+	 * Note that we use the EMD parent here, as the old dentry
+	 * may have moved to a new parent ...
 	 */
-	umsdos_set_dirinfo (new_dentry->d_inode, new_dir, new_info.f_pos);
-#endif
-	if (old_dentry == dret) {
-printk("umsdos_rename_f: old dentries match -- skipping d_move\n");
-		goto out_dput;
+	err = umsdos_delentry (old_emd->d_parent, &old_info,
+				S_ISDIR (old_info.entry.mode));
+	if (err) {
+		/* Failed? Complain a bit, but don't fail the operation */
+		printk(KERN_WARNING 
+			"umsdos_rename_f: delentry %s/%s failed, error=%d\n",
+			old_emd->d_parent->d_name.name, old_info.entry.name,
+			err);
 	}
-	d_move (old_dentry, new_dentry);
 
+	/* dput() the dentry if we haven't already */
 out_dput:
-	dput(dret);
+	if (old_dentry != old)
+		dput(old);
+	if (ret)
+		goto out_unlock;
+	/*
+	 * Check whether to update the dcache ... if both 
+	 * old and new dentries match, it's already correct.
+	 */
+	if (new_dentry != new) {
+		d_move(old_dentry, new_dentry);
+	} else if (old_dentry != old) {
+		/* new dentry was destroyed ... */
+		d_drop(new_dentry);
+		d_add(new_target, NULL);
+		d_move(old_dentry, new_target);
+	}
+
+	/*
+	 * Update f_pos so notify_change will succeed
+	 * if the file was already in use.
+	 */
+	umsdos_set_dirinfo_new(old_dentry, new_info.f_pos);
 
 out_unlock:
-	Printk ((KERN_DEBUG "umsdos_rename_f: unlocking dirs...\n"));
+	dput(new_target);
+	dput(old_emd);
 	umsdos_unlockcreate (old_dir);
 	umsdos_unlockcreate (new_dir);
 
 out:
-	check_dentry_path (old_dentry, "umsdos_rename_f OLD3");
-	check_dentry_path (new_dentry, "umsdos_rename_f NEW3");
 	Printk ((" _ret=%d\n", ret));
 	return ret;
 }
@@ -509,11 +575,11 @@
  * Return a negative error code or 0 if OK.
  */
 /* #Specification: symbolic links / strategy
- * A symbolic link is simply a file which hold a path. It is
+ * A symbolic link is simply a file which holds a path. It is
  * implemented as a normal MSDOS file (not very space efficient :-()
  * 
- * I see 2 different way to do it. One is to place the link data
- * in unused entry of the EMD file. The other is to have a separate
+ * I see two different ways to do this: One is to place the link data
+ * in unused entries of the EMD file; the other is to have a separate
  * file dedicated to hold all symbolic links data.
  * 
  * Let's go for simplicity...
@@ -525,37 +591,33 @@
 	int ret, len;
 	struct file filp;
 
+Printk(("umsdos_symlink: %s/%s to %s\n",
+dentry->d_parent->d_name.name, dentry->d_name.name, symname));
+
 	ret = umsdos_create_any (dir, dentry, mode, 0, flags);
 	if (ret) {
-Printk (("umsdos_symlink ret %d ", ret));
+printk("umsdos_symlink: create failed, ret=%d\n", ret);
 		goto out;
 	}
 
-	len = strlen (symname);
-
 	fill_new_filp (&filp, dentry);
-	filp.f_pos = 0;
-
-	/* Make the inode acceptable to MSDOS FIXME */
-Printk ((KERN_WARNING "   symname=%s ; dentry name=%.*s (ino=%lu)\n",
-symname, (int) dentry->d_name.len, dentry->d_name.name, dentry->d_inode->i_ino));
+	len = strlen (symname);
 	ret = umsdos_file_write_kmem_real (&filp, symname, len);
-	
 	if (ret >= 0) {
 		if (ret != len) {
 			ret = -EIO;
-			printk ("UMSDOS: "
-			     "Can't write symbolic link data\n");
+			printk (KERN_WARNING
+				"UMSDOS: Can't write symbolic link data\n");
 		} else {
 			ret = 0;
 		}
 	}
 	if (ret != 0) {
+printk("umsdos_symlink: write failed, unlinking\n");
 		UMSDOS_unlink (dir, dentry);
 	}
 
 out:
-	Printk (("\n"));
 	return ret;
 }
 
@@ -576,13 +638,20 @@
 		 struct dentry *dentry)
 {
 	struct inode *oldinode = olddentry->d_inode;
-	struct inode *olddir;
-	char *path;
+	struct inode *olddir = olddentry->d_parent->d_inode;
 	struct dentry *temp;
+	char *path;
 	unsigned long buffer;
 	int ret;
-	struct umsdos_dirent entry;
+	struct umsdos_info old_info;
+	struct umsdos_info hid_info;
 
+#ifdef UMSDOS_DEBUG_VERBOSE
+printk("umsdos_link: new %s%s -> %s/%s\n",
+dentry->d_parent->d_name.name, dentry->d_name.name, 
+olddentry->d_parent->d_name.name, olddentry->d_name.name);
+#endif
+ 
 	ret = -EPERM;
 	if (S_ISDIR (oldinode->i_mode))
 		goto out;
@@ -596,48 +665,141 @@
 	if (!buffer)
 		goto out;
 
-	olddir = olddentry->d_parent->d_inode;
 	umsdos_lockcreate2 (dir, olddir);
 
-	/* get the entry for the old name */
-	ret = umsdos_dentry_to_entry(olddentry, &entry);
+	/*
+	 * Parse the name and get the visible directory entry.
+	 */
+	ret = umsdos_parse (olddentry->d_name.name, olddentry->d_name.len,
+				&old_info);
 	if (ret)
 		goto out_unlock;
-Printk (("umsdos_link :%.*s: ino %lu flags %d ",
-entry.name_len, entry.name ,oldinode->i_ino, entry.flags));
+	ret = umsdos_findentry (olddentry->d_parent, &old_info, 1);
+	if (ret) {
+printk("UMSDOS_link: %s/%s not in EMD, ret=%d\n",
+olddentry->d_parent->d_name.name, olddentry->d_name.name, ret);
+		goto out_unlock;
+	}
 
-	if (!(entry.flags & UMSDOS_HIDDEN)) {
-		struct umsdos_info info;
+	/*
+	 * If the visible dentry is a pseudo-hardlink, the original
+	 * file must be already hidden.
+	 */
+	if (!(old_info.entry.flags & UMSDOS_HLINK)) {
+		int err;
 
-		ret = umsdos_newhidden (olddentry->d_parent, &info);
-		if (ret)
+		/* create a hidden link name */
+		ret = umsdos_newhidden (olddentry->d_parent, &hid_info);
+		if (ret) {
+printk("umsdos_link: can't make hidden %s/%s, ret=%d\n",
+olddentry->d_parent->d_name.name, hid_info.entry.name, ret);
 			goto out_unlock;
+		}
 
-		ret = umsdos_rename_f (olddentry->d_inode, olddentry, 
-					dir, dentry, UMSDOS_HIDDEN);
-		if (ret)
-			goto out_unlock;
-		path = d_path(olddentry, (char *) buffer, PAGE_SIZE);
-		if (!path)
-			goto out_unlock;
-		temp = umsdos_lookup_dentry(olddentry->d_parent, entry.name,
-						entry.name_len); 
-		if (IS_ERR(temp))
-			goto out_unlock;
-		ret = umsdos_symlink_x (olddir, temp, path, 
-					S_IFREG | 0777, UMSDOS_HLINK);
-		if (ret == 0) {
-			ret = umsdos_symlink_x (dir, dentry, path,
-					S_IFREG | 0777, UMSDOS_HLINK);
+		/*
+		 * Make a dentry and rename the original file ...
+		 */
+		temp = umsdos_lookup_dentry(olddentry->d_parent,
+						hid_info.entry.name,
+						hid_info.entry.name_len, 0); 
+		ret = PTR_ERR(temp);
+		if (IS_ERR(temp)) {
+printk("umsdos_link: lookup %s/%s failed, ret=%d\n",
+dentry->d_parent->d_name.name, hid_info.entry.name, ret);
+			goto cleanup;
 		}
+		/* rename the link to the hidden location ... */
+		ret = umsdos_rename_f (olddir, olddentry, olddir, temp,
+					UMSDOS_HIDDEN);
 		dput(temp);
+		if (ret) {
+printk("umsdos_link: rename to %s/%s failed, ret=%d\n",
+temp->d_parent->d_name.name, temp->d_name.name, ret);
+			goto cleanup;
+		}
+		/* mark the inode as a hardlink */
+		oldinode->u.umsdos_i.i_is_hlink = 1;
+
+		/*
+		 * Capture the path to the hidden link.
+		 */
+		path = umsdos_d_path(olddentry, (char *) buffer, PAGE_SIZE);
+Printk(("umsdos_link: hidden link path=%s\n", path));
+
+		/*
+		 * Recreate a dentry for the original name and symlink it,
+		 * then symlink the new dentry. Don't give up if one fails,
+		 * or we'll lose the file completely!
+		 *
+		 * Note: this counts as the "original" reference, so we 
+		 * don't increment i_nlink for this one.
+		 */ 
+		temp = umsdos_lookup_dentry(olddentry->d_parent,
+						old_info.entry.name,
+						old_info.entry.name_len, 0); 
+		ret = PTR_ERR(temp);
+		if (!IS_ERR(temp)) {
+			ret = umsdos_symlink_x (olddir, temp, path, 
+						S_IFREG | 0777, UMSDOS_HLINK);
+			dput(temp);
+		}
+
+		/* This symlink increments i_nlink (see below.) */
+		err = umsdos_symlink_x (dir, dentry, path,
+					S_IFREG | 0777, UMSDOS_HLINK);
+		/* fold the two errors */
+		if (!ret)
+			ret = err;
+		goto out_unlock;
+
+		/* creation failed ... remove the link entry */
+	cleanup:
+printk("umsdos_link: link failed, ret=%d, removing %s/%s\n",
+ret, olddentry->d_parent->d_name.name, hid_info.entry.name);
+		err = umsdos_delentry(olddentry->d_parent, &hid_info, 0);
 		goto out_unlock;
 	}
-	path = d_path(olddentry, (char *) buffer, PAGE_SIZE);
-	if (path) {
-		ret = umsdos_symlink_x (dir, dentry, path, 
-				S_IFREG | 0777, UMSDOS_HLINK);
-	}
+
+Printk(("UMSDOS_link: %s/%s already hidden\n",
+olddentry->d_parent->d_name.name, olddentry->d_name.name));
+	/*
+	 * The original file is already hidden, and we need to get 
+	 * the dentry for its real name, not the visible name.
+	 * N.B. make sure it's the hidden inode ...
+	 */
+	if (!oldinode->u.umsdos_i.i_is_hlink)
+		printk("UMSDOS_link: %s/%s hidden, ino=%ld not hlink??\n",
+			olddentry->d_parent->d_name.name,
+			olddentry->d_name.name, oldinode->i_ino);
+
+	/*
+	 * In order to get the correct (real) inode, we just drop
+	 * the original dentry.
+	 */ 
+	d_drop(olddentry);
+Printk(("UMSDOS_link: hard link %s/%s, fake=%s\n",
+olddentry->d_parent->d_name.name, olddentry->d_name.name, old_info.fake.fname));
+
+	/* Do a real lookup to get the short name dentry */
+	temp = umsdos_lookup_dentry(olddentry->d_parent,
+					old_info.fake.fname, 
+					old_info.fake.len, 1);
+	ret = PTR_ERR(temp);
+	if (IS_ERR(temp))
+		goto out_unlock;
+
+	/* now resolve the link ... */
+	temp = umsdos_solve_hlink(temp);
+	ret = PTR_ERR(temp);
+	if (IS_ERR(temp))
+		goto out_unlock;
+	path = umsdos_d_path(temp, (char *) buffer, PAGE_SIZE);
+	dput(temp);
+Printk(("umsdos_link: %s/%s already hidden, path=%s\n",
+olddentry->d_parent->d_name.name, olddentry->d_name.name, path));
+
+	/* finally we can symlink it ... */
+	ret = umsdos_symlink_x (dir, dentry, path, S_IFREG | 0777,UMSDOS_HLINK);
 
 out_unlock:
 	umsdos_unlockcreate (olddir);
@@ -647,7 +809,15 @@
 	if (ret == 0) {
 		struct iattr newattrs;
 
+#ifdef UMSDOS_PARANOIA
+if (!oldinode->u.umsdos_i.i_is_hlink)
+printk("UMSDOS_link: %s/%s, ino=%ld, not marked as hlink!\n",
+olddentry->d_parent->d_name.name, olddentry->d_name.name, oldinode->i_ino);
+#endif
 		oldinode->i_nlink++;
+Printk(("UMSDOS_link: linked %s/%s, ino=%ld, nlink=%d\n",
+olddentry->d_parent->d_name.name, olddentry->d_name.name,
+oldinode->i_ino, oldinode->i_nlink));
 		newattrs.ia_valid = 0;
 		ret = UMSDOS_notify_change (olddentry, &newattrs);
 	}
@@ -657,24 +827,6 @@
 
 
 /*
- * Add a new file into the alternate directory.
- * The file is added to the real MSDOS directory. If successful, it
- * is then added to the EMD file.
- * 
- * Return the status of the operation. 0 mean success.
- */
-int UMSDOS_create (struct inode *dir, struct dentry *dentry, int mode)
-{
-	int ret;
-	Printk ((KERN_ERR "UMSDOS_create: entering\n"));
-	check_dentry_path (dentry, "UMSDOS_create START");
-	ret = umsdos_create_any (dir, dentry, mode, 0, 0);
-	check_dentry_path (dentry, "UMSDOS_create END");
-	return ret;
-}
-
-
-/*
  * Add a sub-directory in a directory
  */
 /* #Specification: mkdir / Directory already exist in DOS
@@ -699,10 +851,8 @@
 		goto out;
 
 	ret = umsdos_parse (dentry->d_name.name, dentry->d_name.len, &info);
-	if (ret) {
-Printk (("umsdos_mkdir %d\n", ret));
+	if (ret)
 		goto out;
-	}
 
 	umsdos_lockcreate (dir);
 	info.entry.mode = mode | S_IFDIR;
@@ -713,14 +863,12 @@
 	info.entry.flags = 0;
 	info.entry.nlink = 1;
 	ret = umsdos_newentry (dentry->d_parent, &info);
-	if (ret) {
-Printk (("newentry %d ", ret));
+	if (ret)
 		goto out_unlock;
-	}
 
 	/* lookup the short name dentry */
 	temp = umsdos_lookup_dentry(dentry->d_parent, info.fake.fname, 
-					info.fake.len);
+					info.fake.len, 1);
 	ret = PTR_ERR(temp);
 	if (IS_ERR(temp))
 		goto out_unlock;
@@ -737,36 +885,27 @@
 	if (ret)
 		goto out_remove;
 
-	inode = temp->d_inode;
-	umsdos_lookup_patch_new(temp, &info.entry, info.f_pos);
-
 	/*
 	 * Note! The long and short name might be the same,
 	 * so check first before doing the instantiate ...
 	 */
 	if (dentry != temp) {
-		if (!dentry->d_inode) {
-			inode->i_count++;
-			d_instantiate(dentry, inode);
-		} else {
-			printk("umsdos_mkdir: not negative??\n");
-		}
-	} else {
-		printk("umsdos_mkdir: dentries match, skipping inst\n");
+if (dentry->d_inode)
+printk("umsdos_mkdir: dentry not negative!\n");
+		inode = temp->d_inode;
+		inode->i_count++;
+		d_instantiate(dentry, inode);
 	}
-
-	/* create the EMD file */
-	err = umsdos_make_emd(dentry);
+	umsdos_lookup_patch_new(dentry, &info.entry, info.f_pos);
 
 	/* 
-	 * set up the dir so it is promoted to EMD,
-	 * with the EMD file invisible inside it.
+	 * Create the EMD file, and set up the dir so it is
+	 * promoted to EMD with the EMD file invisible.
+	 *
+	 * N.B. error return if EMD fails?
 	 */
-	umsdos_setup_dir(temp);
-	goto out_dput;
-
-out_remove:
-	umsdos_delentry (dentry->d_parent, &info, 1);
+	err = umsdos_make_emd(dentry);
+	umsdos_setup_dir(dentry);
 
 out_dput:
 	/* kill off the short name dentry */ 
@@ -776,9 +915,14 @@
 
 out_unlock:
 	umsdos_unlockcreate (dir);
-	Printk (("umsdos_mkdir %d\n", ret));
 out:
+	Printk (("umsdos_mkdir %d\n", ret));
 	return ret;
+
+	/* an error occurred ... remove EMD entry. */
+out_remove:
+	umsdos_delentry (dentry->d_parent, &info, 1);
+	goto out_dput;
 }
 
 /*
@@ -801,11 +945,7 @@
 int UMSDOS_mknod (struct inode *dir, struct dentry *dentry,
 		 int mode, int rdev)
 {
-	int ret;
-	check_dentry_path (dentry, "UMSDOS_mknod START");
-	ret = umsdos_create_any (dir, dentry, mode, rdev, 0);
-	check_dentry_path (dentry, "UMSDOS_mknod END");
-	return ret;
+	return umsdos_create_any (dir, dentry, mode, rdev, 0);
 }
 
 /*
@@ -821,13 +961,6 @@
 	if (ret)
 		goto out;
 
-#if 0	/* no need for lookup ... we have a dentry ... */
-	ret = umsdos_lookup_x (dir, dentry, 0);
-	Printk (("rmdir lookup %d ", ret));
-	if (ret != 0)
-		goto out;
-#endif
-
 	umsdos_lockcreate (dir);
 	ret = -EBUSY;
 	if (dentry->d_count > 1) {
@@ -839,6 +972,14 @@
 		}
 	}
 
+	/* check the sticky bit */
+	ret = -EPERM;
+	if (is_sticky(dir, dentry->d_inode->i_uid)) {
+printk("umsdos_rmdir: %s/%s is sticky\n",
+dentry->d_parent->d_name.name, dentry->d_name.name);
+		goto out_unlock;
+	}
+
 	/* check whether the EMD is empty */
 	empty = umsdos_isempty (dentry);
 	ret = -ENOTEMPTY;
@@ -848,24 +989,21 @@
 	/* Have to remove the EMD file? */
 	if (empty == 1) {
 		struct dentry *demd;
-		/* check sticky bit */
-		ret = -EPERM;
-		if (is_sticky(dir, dentry->d_inode->i_uid)) {
-printk("umsdos_rmdir: %s/%s is sticky\n",
-dentry->d_parent->d_name.name, dentry->d_name.name);
-			goto out_unlock;
-		}
+
+Printk (("UMSDOS_rmdir: unlinking empty EMD err=%d", err));
 
 		ret = -ENOTEMPTY;
 		/* see if there's an EMD file ... */
 		demd = umsdos_get_emd_dentry(dentry);
 		if (IS_ERR(demd))
 			goto out_unlock;
-printk("umsdos_rmdir: got EMD dentry %s/%s, inode=%p\n",
-demd->d_parent->d_name.name, demd->d_name.name, demd->d_inode);
 
 		err = msdos_unlink (dentry->d_inode, demd);
-Printk (("UMSDOS_rmdir: unlinking empty EMD err=%d", err));
+#ifdef UMSDOS_PARANOIA
+if (err)
+printk("umsdos_rmdir: EMD %s/%s unlink failed, err=%d\n",
+demd->d_parent->d_name.name, demd->d_name.name, err);
+#endif
 		dput(demd);
 		if (err)
 			goto out_unlock;
@@ -875,7 +1013,7 @@
 	/* Call findentry to complete the mangling */
 	umsdos_findentry (dentry->d_parent, &info, 2);
 	temp = umsdos_lookup_dentry(dentry->d_parent, info.fake.fname, 
-					info.fake.len);
+					info.fake.len, 1);
 	ret = PTR_ERR(temp);
 	if (IS_ERR(temp))
 		goto out_unlock;
@@ -884,8 +1022,8 @@
 	 */
 	if (temp == dentry) {
 		dput(temp);
-printk("umsdos_rmdir: %s/%s, short matches long\n",
-dentry->d_parent->d_name.name, dentry->d_name.name);
+Printk(("umsdos_rmdir: %s/%s, short matches long\n",
+dentry->d_parent->d_name.name, dentry->d_name.name));
 	}
 
 	/*
@@ -897,16 +1035,18 @@
 
 	/* OK so far ... remove the name from the EMD */
 	ret = umsdos_delentry (dentry->d_parent, &info, 1);
+#ifdef UMSDOS_PARANOIA
+if (ret)
+printk("umsdos_rmdir: delentry %s failed, ret=%d\n", info.entry.name, ret);
+#endif
 
-out_dput:
 	/* dput() temp if we didn't do it above */
+out_dput:
 	if (temp != dentry) {
 		d_drop(temp);
 		dput(temp);
 		if (!ret)
 			d_delete (dentry);
-printk("umsdos_rmdir: %s/%s, short=%s dput\n",
-dentry->d_parent->d_name.name, dentry->d_name.name, info.fake.fname);
 	}
 
 out_unlock:
@@ -922,8 +1062,8 @@
  * Remove a file from the directory.
  *
  * #Specification: hard link / deleting a link
- * When we delete a file, and this file is a link
- * we must subtract 1 to the nlink field of the
+ * When we delete a file and this file is a link,
+ * we must subtract 1 from the nlink field of the
  * hidden link.
  * 
  * If the count goes to 0, we delete this hidden
@@ -931,12 +1071,12 @@
  */
 int UMSDOS_unlink (struct inode *dir, struct dentry *dentry)
 {
-	struct dentry *temp;
+	struct dentry *temp, *link = NULL;
 	struct inode *inode;
-	int ret;
+	int ret, rehash = 0;
 	struct umsdos_info info;
 
-Printk (("UMSDOS_unlink: entering %s/%s\n",
+Printk(("UMSDOS_unlink: entering %s/%s\n",
 dentry->d_parent->d_name.name, dentry->d_name.name));
 
 	ret = umsdos_nevercreat (dir, dentry, -EPERM);
@@ -950,11 +1090,13 @@
 	umsdos_lockcreate (dir);
 	ret = umsdos_findentry (dentry->d_parent, &info, 1);
 	if (ret) {
-printk("UMSDOS_unlink: findentry returned %d\n", ret);
+printk("UMSDOS_unlink: %s/%s not in EMD, ret=%d\n",
+dentry->d_parent->d_name.name, dentry->d_name.name, ret);
 		goto out_unlock;
 	}
 
 Printk (("UMSDOS_unlink %.*s ", info.fake.len, info.fake.fname));
+
 	ret = -EPERM;
 	/* check sticky bit */
 	if (is_sticky(dir, info.entry.uid)) {
@@ -963,65 +1105,54 @@
 		goto out_unlock;
 	}
 
-	ret = 0;
+	/*
+	 * Note! If this is a hardlink and the names are aliased,
+	 * the short-name lookup will return the hardlink dentry.
+	 * In order to get the correct (real) inode, we just drop
+	 * the original dentry.
+	 */ 
 	if (info.entry.flags & UMSDOS_HLINK) {
-printk("UMSDOS_unlink: hard link %s/%s, fake=%s\n",
-dentry->d_parent->d_name.name, dentry->d_name.name, info.fake.fname);
-		/*
-		 * First, get the inode of the hidden link
-		 * using the standard lookup function.
-		 */
-
-		ret = umsdos_lookup_x (dir, dentry, 0);
-		inode = dentry->d_inode;
-		if (ret)
-			goto out_unlock;
-
-		Printk (("unlink nlink = %d ", inode->i_nlink));
-		inode->i_nlink--;
-		if (inode->i_nlink == 0) {
-			struct umsdos_dirent entry;
-
-			ret = umsdos_dentry_to_entry (dentry, &entry);
-			if (ret == 0) {
-				ret = UMSDOS_unlink (dentry->d_parent->d_inode,
-							dentry);
-			}
-		} else {
-			struct iattr newattrs;
-			newattrs.ia_valid = 0;
-			ret = UMSDOS_notify_change (dentry, &newattrs);
-		}
+		rehash = !list_empty(&dentry->d_hash);
+		d_drop(dentry);
+Printk(("UMSDOS_unlink: hard link %s/%s, fake=%s, rehash=%d\n",
+dentry->d_parent->d_name.name, dentry->d_name.name, info.fake.fname, rehash));
 	}
-	if (ret)
-		goto out_unlock;
 
-	/* get the short name dentry */
+	/* Do a real lookup to get the short name dentry */
 	temp = umsdos_lookup_dentry(dentry->d_parent, info.fake.fname, 
-					info.fake.len);
+					info.fake.len, 1);
+	ret = PTR_ERR(temp);
 	if (IS_ERR(temp))
 		goto out_unlock;
 
 	/*
-	 * If the short name matches the long,
-	 * dput() it now so it's not busy.
+	 * Resolve hardlinks now, but defer processing until later.
 	 */
-	if (temp == dentry) {
-printk("UMSDOS_unlink: %s/%s, short matches long\n",
-dentry->d_parent->d_name.name, dentry->d_name.name);
-		dput(temp);
+	if (info.entry.flags & UMSDOS_HLINK) {
+		link = umsdos_solve_hlink(dget(temp));
 	}
 
+	/*
+	 * If the short and long names are aliased,
+	 * dput() it now so the dentry isn't busy.
+	 */
+	if (temp == dentry)
+		dput(temp);
+
+	/* Delete the EMD entry */
 	ret = umsdos_delentry (dentry->d_parent, &info, 0);
-	if (ret && ret != -ENOENT)
+	if (ret && ret != -ENOENT) {
+		printk(KERN_WARNING "UMSDOS_unlink: delentry %s, error=%d\n",
+			info.entry.name, ret);
 		goto out_dput;
+	}
 
-printk("UMSDOS: Before msdos_unlink %.*s ",
-info.fake.len, info.fake.fname);
 	ret = msdos_unlink_umsdos (dir, temp);
-
-Printk (("msdos_unlink %.*s %o ret %d ",
-info.fake.len, info.fake.fname ,info.entry.mode, ret));
+#ifdef UMSDOS_PARANOIA
+if (ret)
+printk("umsdos_unlink: %s/%s unlink failed, ret=%d\n",
+temp->d_parent->d_name.name, temp->d_name.name, ret);
+#endif
 
 	/* dput() temp if we didn't do it above */
 out_dput:
@@ -1030,12 +1161,60 @@
 		dput(temp);
 		if (!ret)
 			d_delete (dentry);
-printk("umsdos_unlink: %s/%s, short=%s dput\n",
-dentry->d_parent->d_name.name, dentry->d_name.name, info.fake.fname);
 	}
 
 out_unlock:
 	umsdos_unlockcreate (dir);
+
+	/*
+	 * Now check for deferred handling of a hardlink.
+	 */
+	if (!link)
+		goto out;
+
+	if (IS_ERR(link)) {
+printk("umsdos_unlink: failed to resolve %s/%s\n",
+dentry->d_parent->d_name.name, dentry->d_name.name);
+		if (!ret)
+			ret = PTR_ERR(link);
+		goto out;
+	}
+
+Printk(("umsdos_unlink: link %s/%s deferred, pending ret=%d\n",
+link->d_parent->d_name.name, link->d_name.name, ret));
+
+	/* already have an error? */
+	if (ret)
+		goto out_cleanup;
+
+	/* make sure the link exists ... */
+	inode = link->d_inode;
+	if (!inode) {
+		printk(KERN_WARNING "umsdos_unlink: hard link not found\n");
+		goto out_cleanup;
+	}
+
+	/*
+	 * If this was the last linked reference, delete it now.
+	 */
+	if (inode->i_nlink <= 1) {
+		ret = UMSDOS_unlink (link->d_parent->d_inode, link);
+		if (ret) {
+			printk(KERN_WARNING
+				"umsdos_unlink: link removal failed, ret=%d\n",
+				 ret);
+		}
+	} else {
+		struct iattr newattrs;
+		inode->i_nlink--;
+		newattrs.ia_valid = 0;
+		ret = UMSDOS_notify_change (link, &newattrs);
+	}
+
+out_cleanup:
+	d_drop(link);
+	dput(link);
+
 out:
 	Printk (("umsdos_unlink %d\n", ret));
 	return ret;
@@ -1058,20 +1237,28 @@
 	if (ret != -EEXIST)
 		goto out;
 
+	/*
+	 * Something seems to be giving negative lookups when
+	 * the file really exists ... track this down!
+	 */
+	ret = -EIO;
+	if (!new_dentry->d_inode) {
+printk("UMSDOS_rename: %s/%s negative, error EEXIST??\n",
+new_dentry->d_parent->d_name.name, new_dentry->d_name.name);
+		d_drop(new_dentry);
+		goto out;
+	}
+
 	/* This is not terribly efficient but should work. */
-	ret = UMSDOS_unlink (new_dir, new_dentry);
-	chkstk ();
-	Printk (("rename unlink ret %d -- ", ret));
-	if (ret == -EISDIR) {
+	if (S_ISDIR(new_dentry->d_inode->i_mode))
 		ret = UMSDOS_rmdir (new_dir, new_dentry);
-		chkstk ();
-		Printk (("rename rmdir ret %d -- ", ret));
-	}
+	else
+		ret = UMSDOS_unlink (new_dir, new_dentry);
 	if (ret)
 		goto out;
 
 	/* this time the rename should work ... */
-	ret = umsdos_rename_f (old_dir, old_dentry, new_dir, new_dentry, 0);
+	ret = umsdos_rename_f(old_dir, old_dentry, new_dir, new_dentry, 0);
 
 out:
 	return ret;

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