diff options
Diffstat (limited to 'fs/umsdos/namei.c')
-rw-r--r-- | fs/umsdos/namei.c | 1001 |
1 files changed, 617 insertions, 384 deletions
diff --git a/fs/umsdos/namei.c b/fs/umsdos/namei.c index b365fbf04..831802dcf 100644 --- a/fs/umsdos/namei.c +++ b/fs/umsdos/namei.c @@ -177,17 +177,15 @@ void umsdos_endlookup (struct inode *dir) static int is_sticky(struct inode *dir, int uid) { return !((dir->i_mode & S_ISVTX) == 0 || - capable (CAP_FOWNER) || current->fsuid == uid || - current->fsuid == dir->i_uid); + current->fsuid == dir->i_uid || + capable (CAP_FOWNER)); } static int umsdos_nevercreat (struct inode *dir, struct dentry *dentry, int errcod) { - const char *name = dentry->d_name.name; - int len = dentry->d_name.len; int ret = 0; if (umsdos_is_pseudodos (dir, dentry)) { @@ -198,20 +196,6 @@ static int umsdos_nevercreat (struct inode *dir, struct dentry *dentry, * The pseudo sub-directory /DOS can't be removed! * EPERM is returned. */ - ret = -EPERM; - ret = errcod; - } else if (name[0] == '.' - && (len == 1 || (len == 2 && name[1] == '.'))) { - /* #Specification: create / . and .. - * If one try to creates . or .., it always fail and return - * EEXIST. - * - * If one try to delete . or .., it always fail and return - * EPERM. - * - * This should be test at the VFS layer level to avoid - * duplicating this in all file systems. Any comments ? - */ ret = errcod; } return ret; @@ -224,8 +208,8 @@ static int umsdos_nevercreat (struct inode *dir, struct dentry *dentry, * * 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 +238,9 @@ static int umsdos_create_any (struct inode *dir, struct dentry *dentry, 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) @@ -279,79 +253,82 @@ Printk (("%d/\n", ret)); info.entry.gid = (dir->i_mode & S_ISGID) ? dir->i_gid : current->fsgid; info.entry.ctime = info.entry.atime = info.entry.mtime = CURRENT_TIME; info.entry.nlink = 1; - umsdos_lockcreate (dir); ret = umsdos_newentry (dentry->d_parent, &info); if (ret) - goto out_unlock; + goto out; - /* 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; + + /* keep the short name anonymous ... */ + if (dentry != fake) + d_drop(fake); /* should not exist yet ... */ ret = -EEXIST; if (fake->d_inode) - goto out_remove; + goto out_remove_dput; ret = msdos_create (dir, fake, S_IFREG | 0777); if (ret) - goto out_remove; + goto out_remove_dput; 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->i_count++; d_instantiate (dentry, inode); } - - check_dentry_path (dentry, "umsdos_create_any: END dentry"); - check_dentry_path (fake, "umsdos_create_any: END fake"); - 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); - -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); dput(fake); + if (inode->i_count > 1) { + printk(KERN_WARNING + "umsdos_create_any: %s/%s, ino=%ld, icount=%d??\n", + dentry->d_parent->d_name.name, dentry->d_name.name, + inode->i_ino, inode->i_count); + } + umsdos_lookup_patch_new(dentry, &info); -out_unlock: - umsdos_unlockcreate (dir); out: return ret; + + /* Creation failed ... remove the EMD entry */ +out_remove_dput: + dput(fake); +out_remove: + 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)); + goto out; +} + +/* + * 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; new_info->entry.rdev = old_info->entry.rdev; new_info->entry.uid = old_info->entry.uid; @@ -359,10 +336,11 @@ static void umsdos_ren_init (struct umsdos_info *new_info, 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 +351,7 @@ if (STACK_MAGIC != *(unsigned long *)current->kernel_stack_page){\ #undef chkstk #define chkstk() do { } while (0); +#endif /* * Rename a file (move) in the file system. @@ -382,124 +361,238 @@ static int umsdos_rename_f (struct inode *old_dir, struct dentry *old_dentry, 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 inode *old_inode = old_dentry->d_inode; + struct dentry *old, *new, *old_emd; + 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 ")); +printk("umsdos_rename_f: %s/%s old sticky bit, fsuid=%d, uid=%d, dir=%d\n", +old_dentry->d_parent->d_name.name, old_info.entry.name, +current->fsuid, old_info.entry.uid, old_dir->i_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); + else { + /* make sure it's the same inode! */ + ret = -ENOENT; + if (old->d_inode != old_inode) + goto out_dput; + /* + * A cross-directory move with different short and long + * names has nasty complications: msdos-fs will need to + * change inodes, so we must check whether the original + * dentry is busy, and if the rename succeeds the short + * dentry will come back with a different inode. + * + * To handle this, we drop the dentry and free the inode, + * and then pick up the new inode after the rename. + */ + if (old_dir != new_dir) { + ret = -EBUSY; + if (old_dentry->d_count > 1) { +printk("umsdos_rename_f: old dentry %s/%s busy, d_count=%d\n", +old_dentry->d_parent->d_name.name, old_dentry->d_name.name,old_dentry->d_count); + goto out_dput; + } + d_drop(old_dentry); + d_delete(old_dentry); +printk("umsdos_rename_f: cross-dir move, %s/%s dropped\n", +old_dentry->d_parent->d_name.name, old_dentry->d_name.name); + } + } + 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; - - Printk (("msdos_rename ")); - check_dentry_path (old, "umsdos_rename_f OLD2"); - check_dentry_path (new, "umsdos_rename_f NEW2"); + } +#ifdef UMSDOS_PARANOIA +if (new->d_inode != new_dentry->d_inode) +printk("umsdos_rename_f: new %s/%s, inode %p!=%p??\n", +new->d_parent->d_name.name, new->d_name.name, new->d_inode,new_dentry->d_inode); +#endif + /* short and long name dentries match? */ + if (new == new_dentry) + dput(new); + +#ifdef UMSDOS_DEBUG_VERBOSE +printk("umsdos_rename_f: msdos_rename %s/%s(%ld) to %s/%s(%ld)\n", +old->d_parent->d_name.name, old->d_name.name, old->d_inode->i_ino, +new->d_parent->d_name.name, new->d_name.name, +new->d_inode ? new->d_inode->i_ino : 0); +#endif + /* 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 + /* + * 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 ... + */ + 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); + } + + /* + * Check whether to update the dcache ... if both + * old and new dentries match, it's already correct. + * If the targets were aliases, the old short-name + * dentry has the original target name. + */ + if (old_dentry != old) { + if (!old_dentry->d_inode) { + struct inode *inode = old->d_inode; + inode->i_count++; + d_instantiate(old_dentry, inode); +printk("umsdos_rename_f: %s/%s gets new ino=%ld\n", +old_dentry->d_parent->d_name.name, old_dentry->d_name.name, inode->i_ino); + } + if (new_dentry == new) + new_dentry = old; + goto move_it; + } else if (new_dentry != new) { + move_it: + /* this will rehash the dentry ... */ + d_move(old_dentry, new_dentry); + } + /* Check whether the old inode changed ... */ + if (old_dentry->d_inode != old_inode) { + umsdos_lookup_patch_new(old_dentry, &new_info); + } + /* * Update f_pos so notify_change will succeed * if the file was already in use. */ - 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; - } - d_move (old_dentry, new_dentry); + umsdos_set_dirinfo_new(old_dentry, new_info.f_pos); + /* dput() the dentry if we haven't already */ out_dput: - dput(dret); + if (old_dentry != old) + dput(old); out_unlock: - Printk ((KERN_DEBUG "umsdos_rename_f: unlocking dirs...\n")); + 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,54 +602,51 @@ out: * 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... */ +extern struct inode_operations umsdos_symlink_inode_operations; + static int umsdos_symlink_x (struct inode *dir, struct dentry *dentry, const char *symname, int mode, char flags) { 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(KERN_WARNING + "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"); - } else { - ret = 0; - } - } - if (ret != 0) { - UMSDOS_unlink (dir, dentry); - } - + if (ret < 0) + goto out_unlink; + if (ret != len) + goto out_error; + ret = 0; out: - Printk (("\n")); return ret; + +out_error: + ret = -EIO; +out_unlink: + printk(KERN_WARNING "umsdos_symlink: write failed, unlinking\n"); + UMSDOS_unlink (dir, dentry); + goto out; } /* @@ -576,13 +666,20 @@ int UMSDOS_link (struct dentry *olddentry, struct inode *dir, 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,80 +693,174 @@ int UMSDOS_link (struct dentry *olddentry, struct inode *dir, if (!buffer) goto out; - olddir = olddentry->d_parent->d_inode; - umsdos_lockcreate2 (dir, olddir); + /* + * Lock the link parent if it's not the same directory. + */ + ret = -EDEADLOCK; + if (olddir != dir) { + if (atomic_read(&olddir->i_sem.count) < 1) + goto out_free; + down(&olddir->i_sem); + } - /* 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)); - - if (!(entry.flags & UMSDOS_HIDDEN)) { - struct umsdos_info info; + 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; + } - ret = umsdos_newhidden (olddentry->d_parent, &info); - if (ret) + /* + * If the visible dentry is a pseudo-hardlink, the original + * file must be already hidden. + */ + if (!(old_info.entry.flags & UMSDOS_HLINK)) { + int err; + + /* 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); - umsdos_unlockcreate (dir); - free_page(buffer); -out: + /* remain locked for the call to notify_change ... */ 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); + ret = umsdos_notify_change_locked(olddentry, &newattrs); } - Printk (("umsdos_link %d\n", ret)); - return ret; -} - + if (olddir != dir) + up(&olddir->i_sem); -/* - * 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"); +out_free: + free_page(buffer); +out: + Printk (("umsdos_link %d\n", ret)); return ret; } @@ -699,12 +890,9 @@ int UMSDOS_mkdir (struct inode *dir, struct dentry *dentry, int mode) 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; info.entry.rdev = 0; info.entry.uid = current->fsuid; @@ -713,72 +901,74 @@ Printk (("umsdos_mkdir %d\n", ret)); info.entry.flags = 0; info.entry.nlink = 1; ret = umsdos_newentry (dentry->d_parent, &info); - if (ret) { -Printk (("newentry %d ", ret)); - goto out_unlock; - } + if (ret) + goto out; /* 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; + goto out_remove; + + /* Keep the short name dentry anonymous */ + if (temp != dentry) + d_drop(temp); /* Make sure the short name doesn't exist */ ret = -EEXIST; if (temp->d_inode) { printk("umsdos_mkdir: short name %s/%s exists\n", dentry->d_parent->d_name.name, info.fake.fname); - goto out_remove; + goto out_remove_dput; } ret = msdos_mkdir (dir, temp, mode); if (ret) - goto out_remove; + goto out_remove_dput; + /* + * Lock the inode to protect the EMD creation ... + */ inode = temp->d_inode; - umsdos_lookup_patch_new(temp, &info.entry, info.f_pos); + down(&inode->i_sem); /* * 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->i_count++; + d_instantiate(dentry, inode); } - - /* create the EMD file */ - err = umsdos_make_emd(dentry); + /* N.B. this should have an option to create the EMD ... */ + umsdos_lookup_patch_new(dentry, &info); /* - * 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 */ - if (temp != dentry) - d_drop(temp); + up(&inode->i_sem); dput(temp); -out_unlock: - umsdos_unlockcreate (dir); - Printk (("umsdos_mkdir %d\n", ret)); out: + Printk(("umsdos_mkdir: %s/%s, ret=%d\n", + dentry->d_parent->d_name.name, dentry->d_name.name, ret)); return ret; + + /* an error occurred ... remove EMD entry. */ +out_remove_dput: + dput(temp); +out_remove: + umsdos_delentry (dentry->d_parent, &info, 1); + goto out; } /* @@ -801,11 +991,7 @@ out: 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,72 +1007,62 @@ int UMSDOS_rmdir (struct inode *dir, struct dentry *dentry) 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) + ret = -EBUSY; + if (!list_empty(&dentry->d_hash)) goto out; -#endif - umsdos_lockcreate (dir); - ret = -EBUSY; - if (dentry->d_count > 1) { - shrink_dcache_parent(dentry); - if (dentry->d_count > 1) { -printk("umsdos_rmdir: %s/%s busy\n", + /* 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; - } + goto out; } /* check whether the EMD is empty */ - empty = umsdos_isempty (dentry); ret = -ENOTEMPTY; - if (empty == 0) - goto out_unlock; + empty = umsdos_isempty (dentry); /* 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; - } - 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); + if (!IS_ERR(demd)) { + err = -ENOENT; + if (demd->d_inode) + err = msdos_unlink (dentry->d_inode, demd); Printk (("UMSDOS_rmdir: unlinking empty EMD err=%d", err)); - dput(demd); - if (err) - goto out_unlock; - } +#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) + ret = 0; + } + } else if (empty == 2) + ret = 0; + if (ret) + goto out; umsdos_parse (dentry->d_name.name, dentry->d_name.len, &info); /* 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; + goto out; /* - * If the short name matches the dentry, dput() it now. + * If the short name is an alias, dput() it now; + * otherwise d_drop() it to keep it anonymous. */ - if (temp == dentry) { + if (temp == dentry) dput(temp); -printk("umsdos_rmdir: %s/%s, short matches long\n", -dentry->d_parent->d_name.name, dentry->d_name.name); - } + else + d_drop(temp); /* * Attempt to remove the msdos name. @@ -897,20 +1073,15 @@ dentry->d_parent->d_name.name, dentry->d_name.name); /* 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 */ - if (temp != dentry) { - d_drop(temp); +out_dput: + if (temp != dentry) 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: - umsdos_unlockcreate (dir); out: Printk (("umsdos_rmdir %d\n", ret)); @@ -922,8 +1093,8 @@ out: * 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 +1102,12 @@ out: */ 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 +1121,13 @@ dentry->d_parent->d_name.name, dentry->d_name.name)); 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 +1136,54 @@ dentry->d_parent->d_name.name, dentry->d_name.name); 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 +1192,64 @@ out_dput: 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. + * + * N.B. Deadlock problem? We should be holding the lock + * for the hardlink's parent, but another process might + * be holding that lock waiting for us to finish ... + */ + 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_locked(link, &newattrs); + } + +out_cleanup: + d_drop(link); + dput(link); + out: Printk (("umsdos_unlink %d\n", ret)); return ret; @@ -1048,30 +1262,49 @@ out: int UMSDOS_rename (struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) { + struct dentry *new_target; int ret; +#ifdef UMSDOS_DEBUG_VERBOSE +printk("umsdos_rename: enter, %s/%s(%ld) to %s/%s(%ld)\n", +old_dentry->d_parent->d_name.name, old_dentry->d_name.name, +old_dentry->d_inode->i_ino, +new_dentry->d_parent->d_name.name, new_dentry->d_name.name, +new_dentry->d_inode ? new_dentry->d_inode->i_ino : 0); +#endif ret = umsdos_nevercreat (new_dir, new_dentry, -EEXIST); if (ret) goto out; - ret = umsdos_rename_f(old_dir, old_dentry, new_dir, new_dentry, 0); - if (ret != -EEXIST) - 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) { - ret = UMSDOS_rmdir (new_dir, new_dentry); - chkstk (); - Printk (("rename rmdir ret %d -- ", ret)); + /* + * If the target already exists, delete it first. + */ + if (new_dentry->d_inode) { + if (S_ISDIR(new_dentry->d_inode->i_mode)) + ret = UMSDOS_rmdir (new_dir, new_dentry); + else + ret = UMSDOS_unlink (new_dir, new_dentry); + if (ret) + goto out; } - if (ret) - goto out; - /* this time the rename should work ... */ - ret = umsdos_rename_f (old_dir, old_dentry, new_dir, new_dentry, 0); + /* + * If we didn't get a negative dentry, make a copy and hash it. + */ + new_target = new_dentry; + if (new_dentry->d_inode) { +printk("umsdos_rename: %s/%s not negative, hash=%d\n", +new_dentry->d_parent->d_name.name, new_dentry->d_name.name, +!list_empty(&new_dentry->d_hash)); + ret = -ENOMEM; + new_target = d_alloc(new_dentry->d_parent, &new_dentry->d_name); + if (!new_target) + goto out; + d_add(new_target, NULL); + } + ret = umsdos_rename_f(old_dir, old_dentry, new_dir, new_target, 0); + if (new_target != new_dentry) + dput(new_target); out: return ret; |