/* * linux/fs/umsdos/namei.c * * Written 1993 by Jacques Gelinas * Inspired from linux/fs/msdos/... by Werner Almesberger * * Maintain and access the --linux alternate directory file. */ /* * You are in the maze of twisted functions - half of them shouldn't * be here... */ #include #include #include #include #include #include #include #include #include #include #define UMSDOS_DIR_LOCK #ifdef UMSDOS_DIR_LOCK static inline void u_sleep_on (struct inode *dir) { sleep_on (&dir->u.umsdos_i.dir_info.p); } static inline void u_wake_up (struct inode *dir) { wake_up (&dir->u.umsdos_i.dir_info.p); } /* * Wait for creation exclusivity. * Return 0 if the dir was already available. * Return 1 if a wait was necessary. * When 1 is return, it means a wait was done. It does not * mean the directory is available. */ static int umsdos_waitcreate (struct inode *dir) { int ret = 0; if (dir->u.umsdos_i.dir_info.creating && dir->u.umsdos_i.dir_info.pid != current->pid) { PRINTK (("creating && dir_info.pid=%lu, current->pid=%u\n", dir->u.umsdos_i.dir_info.pid, current->pid)); u_sleep_on (dir); ret = 1; } return ret; } /* * Wait for any lookup process to finish */ static void umsdos_waitlookup (struct inode *dir) { while (dir->u.umsdos_i.dir_info.looking) { u_sleep_on (dir); } } /* * Lock all other process out of this directory. */ /* #Specification: file creation / not atomic * File creation is a two step process. First we create (allocate) * an entry in the EMD file and then (using the entry offset) we * build a unique name for MSDOS. We create this name in the msdos * space. * * We have to use semaphore (sleep_on/wake_up) to prevent lookup * into a directory when we create a file or directory and to * prevent creation while a lookup is going on. Since many lookup * may happen at the same time, the semaphore is a counter. * * Only one creation is allowed at the same time. This protection * may not be necessary. The problem arise mainly when a lookup * or a readdir is done while a file is partially created. The * lookup process see that as a "normal" problem and silently * erase the file from the EMD file. Normal because a file * may be erased during a MSDOS session, but not removed from * the EMD file. * * The locking is done on a directory per directory basis. Each * directory inode has its wait_queue. * * For some operation like hard link, things even get worse. Many * creation must occur at once (atomic). To simplify the design * a process is allowed to recursively lock the directory for * creation. The pid of the locking process is kept along with * a counter so a second level of locking is granted or not. */ void umsdos_lockcreate (struct inode *dir) { /* * Wait for any creation process to finish except * if we (the process) own the lock */ while (umsdos_waitcreate (dir) != 0); dir->u.umsdos_i.dir_info.creating++; dir->u.umsdos_i.dir_info.pid = current->pid; umsdos_waitlookup (dir); } /* * Lock all other process out of those two directories. */ static void umsdos_lockcreate2 (struct inode *dir1, struct inode *dir2) { /* * We must check that both directory are available before * locking anyone of them. This is to avoid some deadlock. * Thanks to dglaude@is1.vub.ac.be (GLAUDE DAVID) for pointing * this to me. */ while (1) { if (umsdos_waitcreate (dir1) == 0 && umsdos_waitcreate (dir2) == 0) { /* We own both now */ dir1->u.umsdos_i.dir_info.creating++; dir1->u.umsdos_i.dir_info.pid = current->pid; dir2->u.umsdos_i.dir_info.creating++; dir2->u.umsdos_i.dir_info.pid = current->pid; break; } } umsdos_waitlookup (dir1); umsdos_waitlookup (dir2); } /* * Wait until creation is finish in this directory. */ void umsdos_startlookup (struct inode *dir) { while (umsdos_waitcreate (dir) != 0); dir->u.umsdos_i.dir_info.looking++; } /* * Unlock the directory. */ void umsdos_unlockcreate (struct inode *dir) { dir->u.umsdos_i.dir_info.creating--; if (dir->u.umsdos_i.dir_info.creating < 0) { printk ("UMSDOS: dir->u.umsdos_i.dir_info.creating < 0: %d" ,dir->u.umsdos_i.dir_info.creating); } u_wake_up (dir); } /* * Tell directory lookup is over. */ void umsdos_endlookup (struct inode *dir) { dir->u.umsdos_i.dir_info.looking--; if (dir->u.umsdos_i.dir_info.looking < 0) { printk ("UMSDOS: dir->u.umsdos_i.dir_info.looking < 0: %d" ,dir->u.umsdos_i.dir_info.looking); } u_wake_up (dir); } #else static void umsdos_lockcreate (struct inode *dir) { } static void umsdos_lockcreate2 (struct inode *dir1, struct inode *dir2) { } void umsdos_startlookup (struct inode *dir) { } static void umsdos_unlockcreate (struct inode *dir) { } void umsdos_endlookup (struct inode *dir) { } #endif static int umsdos_nevercreat (struct inode *dir, struct dentry *dentry, int errcod) { int ret = 0; if (umsdos_is_pseudodos (dir, dentry)) { /* #Specification: pseudo root / any file creation /DOS * The pseudo sub-directory /DOS can't be created! * EEXIST is returned. * * The pseudo sub-directory /DOS can't be removed! * EPERM is returned. */ ret = errcod; } return ret; } /* * Add a new file (ordinary or special) 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. * * #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. * * Here is what we are NOT doing: * * We could silently assume that everything is fine * and allows the creation to succeed. * * It is possible not all files in the partition * are meant to be visible from linux. By trying to create * those file in some directory, one user may get access * to those file without proper permissions. Looks like * a security hole to me. Off course sharing a file system * with DOS is some kind of security hole :-) * * So ? * * We return EEXIST in this case. * The same is true for directory creation. */ static int umsdos_create_any (struct inode *dir, struct dentry *dentry, int mode, int rdev, char flags) { struct dentry *fake; struct inode *inode; int ret; struct umsdos_info info; ret = umsdos_nevercreat (dir, dentry, -EEXIST); if (ret) goto out; ret = umsdos_parse (dentry->d_name.name, dentry->d_name.len, &info); if (ret) goto out; info.entry.mode = mode; info.entry.rdev = rdev; info.entry.flags = flags; info.entry.uid = current->fsuid; 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; ret = umsdos_newentry (dentry->d_parent, &info); if (ret) goto out; /* do a real lookup to get the short name dentry */ fake = umsdos_covered(dentry->d_parent, info.fake.fname, info.fake.len); ret = PTR_ERR(fake); if (IS_ERR(fake)) goto out_remove; /* should not exist yet ... */ ret = -EEXIST; if (fake->d_inode) goto out_remove_dput; ret = msdos_create (dir, fake, S_IFREG | 0777); if (ret) goto out_remove_dput; inode = fake->d_inode; atomic_inc(&inode->i_count); d_instantiate (dentry, inode); dput(fake); if (atomic_read(&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, atomic_read(&inode->i_count)); } umsdos_lookup_patch_new(dentry, &info); 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) { new_info->entry.mode = old_info->entry.mode; new_info->entry.rdev = old_info->entry.rdev; new_info->entry.uid = old_info->entry.uid; new_info->entry.gid = old_info->entry.gid; 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 = old_info->entry.flags; new_info->entry.nlink = old_info->entry.nlink; } /* * Rename a file (move) in the file system. */ static int umsdos_rename_f (struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, int flags) { struct inode *old_inode = old_dentry->d_inode; struct dentry *old, *new, *old_emd; int err, ret; struct umsdos_info old_info; struct umsdos_info new_info; ret = -EPERM; err = umsdos_parse (old_dentry->d_name.name, old_dentry->d_name.len, &old_info); if (err) goto out; err = umsdos_parse (new_dentry->d_name.name, new_dentry->d_name.len, &new_info); if (err) goto out; /* 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; umsdos_lockcreate2 (old_dir, new_dir); ret = umsdos_findentry(old_emd->d_parent, &old_info, 0); if (ret) goto out_unlock; err = umsdos_findentry(new_dentry->d_parent, &new_info, 0); if (err == 0) { /* 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)); } umsdos_ren_init (&new_info, &old_info); if (flags) new_info.entry.flags = flags; ret = umsdos_newentry (new_dentry->d_parent, &new_info); if (ret) goto out_unlock; /* If we're moving a hardlink, drop it first */ if (old_info.entry.flags & UMSDOS_HLINK) { d_drop(old_dentry); } old = umsdos_covered(old_dentry->d_parent, old_info.fake.fname, old_info.fake.len); ret = PTR_ERR(old); if (IS_ERR(old)) goto out_unlock; /* make sure it's the same inode! */ ret = -ENOENT; /* * note: for hardlinks they will be different! * old_inode will contain inode of .LINKxxx file containing data, and * old->d_inode will contain inode of file containing path to .LINKxxx file */ if (!(old_info.entry.flags & UMSDOS_HLINK)) { if (old->d_inode != old_inode) goto out_dput; } new = umsdos_covered(new_dentry->d_parent, new_info.fake.fname, new_info.fake.len); ret = PTR_ERR(new); if (IS_ERR(new)) goto out_dput; /* Do the msdos-level rename */ ret = msdos_rename (old_dir, old, new_dir, new); dput(new); /* If the rename failed, remove the new EMD entry */ if (ret != 0) { umsdos_delentry (new_dentry->d_parent, &new_info, S_ISDIR (new_info.entry.mode)); goto out_dput; } /* * 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); } /* * 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); /* dput() the dentry if we haven't already */ out_dput: dput(old); out_unlock: dput(old_emd); umsdos_unlockcreate (old_dir); umsdos_unlockcreate (new_dir); out: Printk ((" _ret=%d\n", ret)); return ret; } /* * Setup a Symbolic link or a (pseudo) hard link * Return a negative error code or 0 if OK. */ /* #Specification: symbolic links / strategy * 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 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... */ /* * AV. Should be called with dir->i_sem down. */ static int umsdos_symlink_x (struct inode *dir, struct dentry *dentry, const char *symname, int mode, char flags) { int ret, len; ret = umsdos_create_any (dir, dentry, mode, 0, flags); if (ret) { printk(KERN_WARNING "umsdos_symlink: create failed, ret=%d\n", ret); goto out; } len = strlen (symname) + 1; ret = block_symlink(dentry->d_inode, symname, len); if (ret < 0) goto out_unlink; out: return ret; out_unlink: printk(KERN_WARNING "umsdos_symlink: write failed, unlinking\n"); UMSDOS_unlink (dir, dentry); d_drop(dentry); goto out; } /* * Setup a Symbolic link. * Return a negative error code or 0 if OK. */ int UMSDOS_symlink ( struct inode *dir, struct dentry *dentry, const char *symname) { return umsdos_symlink_x (dir, dentry, symname, S_IFLNK | 0777, 0); } /* * Add a link to an inode in a directory */ int UMSDOS_link (struct dentry *olddentry, struct inode *dir, struct dentry *dentry) { struct inode *oldinode = olddentry->d_inode; struct inode *olddir = olddentry->d_parent->d_inode; struct dentry *temp; char *path; unsigned long buffer; int ret; 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; ret = umsdos_nevercreat (dir, dentry, -EPERM); if (ret) goto out; ret = -ENOMEM; buffer = get_free_page(GFP_KERNEL); if (!buffer) goto out; /* * 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); } /* * 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; 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 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; } /* * 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); d_move(olddentry, temp); 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; } 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_covered(olddentry->d_parent, old_info.fake.fname, old_info.fake.len); 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: /* remain locked for the call to notify_change ... */ if (ret == 0) { struct iattr newattrs; /* Do a real lookup to get the short name dentry */ temp = umsdos_covered(olddentry->d_parent, old_info.fake.fname, old_info.fake.len); ret = PTR_ERR(temp); if (IS_ERR(temp)) goto out_unlock2; /* now resolve the link ... */ temp = umsdos_solve_hlink(temp); ret = PTR_ERR(temp); if (IS_ERR(temp)) goto out_unlock2; #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 temp->d_inode->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_locked(temp, &newattrs); if (ret == 0) mark_inode_dirty(temp->d_inode); dput(temp); out_unlock2: if (ret == 0) mark_inode_dirty(olddentry->d_inode); } if (olddir != dir) up(&olddir->i_sem); out_free: free_page(buffer); out: Printk (("umsdos_link %d\n", ret)); return ret; } /* * Add a sub-directory in a directory */ /* #Specification: mkdir / Directory already exist in DOS * We do the same thing as for file creation. * For all user it is an error. */ /* #Specification: mkdir / umsdos directory / create EMD * When we created a new sub-directory in a UMSDOS * directory (one with full UMSDOS semantics), we * create immediately an EMD file in the new * sub-directory so it inherits UMSDOS semantics. */ int UMSDOS_mkdir (struct inode *dir, struct dentry *dentry, int mode) { struct dentry *temp; struct inode *inode; int ret, err; struct umsdos_info info; ret = umsdos_nevercreat (dir, dentry, -EEXIST); if (ret) goto out; ret = umsdos_parse (dentry->d_name.name, dentry->d_name.len, &info); if (ret) goto out; info.entry.mode = mode | S_IFDIR; info.entry.rdev = 0; info.entry.uid = current->fsuid; 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.flags = 0; info.entry.nlink = 1; ret = umsdos_newentry (dentry->d_parent, &info); if (ret) goto out; /* lookup the short name dentry */ temp = umsdos_covered(dentry->d_parent, info.fake.fname, info.fake.len); ret = PTR_ERR(temp); if (IS_ERR(temp)) goto out_remove; /* 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_dput; } ret = msdos_mkdir (dir, temp, mode); if (ret) goto out_remove_dput; /* * Lock the inode to protect the EMD creation ... */ inode = temp->d_inode; down(&inode->i_sem); atomic_inc(&inode->i_count); d_instantiate(dentry, inode); /* N.B. this should have an option to create the EMD ... */ umsdos_lookup_patch_new(dentry, &info); /* * 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? */ err = umsdos_make_emd(dentry); umsdos_setup_dir(dentry); up(&inode->i_sem); dput(temp); 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; } /* * Add a new device special file into a directory. * * #Specification: Special files / strategy * Device special file, pipes, etc ... are created like normal * file in the msdos file system. Of course they remain empty. * * One strategy was to create those files only in the EMD file * since they were not important for MSDOS. The problem with * that, is that there were not getting inode number allocated. * The MSDOS filesystems is playing a nice game to fake inode * number, so why not use it. * * The absence of inode number compatible with those allocated * for ordinary files was causing major trouble with hard link * in particular and other parts of the kernel I guess. */ int UMSDOS_mknod (struct inode *dir, struct dentry *dentry, int mode, int rdev) { return umsdos_create_any (dir, dentry, mode, rdev, 0); } /* * Remove a sub-directory. */ int UMSDOS_rmdir (struct inode *dir, struct dentry *dentry) { struct dentry *temp; int ret, err, empty; struct umsdos_info info; ret = umsdos_nevercreat (dir, dentry, -EPERM); if (ret) goto out; ret = -EBUSY; if (!d_unhashed(dentry)) goto out; /* check whether the EMD is empty */ ret = -ENOTEMPTY; empty = umsdos_isempty (dentry); /* Have to remove the EMD file? */ if (empty == 1) { struct dentry *demd; demd = umsdos_get_emd_dentry(dentry); 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)); #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 if (!err) { d_delete(demd); ret = 0; } dput(demd); } } 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_covered(dentry->d_parent, info.fake.fname, info.fake.len); ret = PTR_ERR(temp); if (IS_ERR(temp)) goto out; /* * Attempt to remove the msdos name. */ ret = msdos_rmdir (dir, temp); if (ret && ret != -ENOENT) goto out_dput; d_delete(temp); /* 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 /* dput() temp if we didn't do it above */ out_dput: dput(temp); out: Printk (("umsdos_rmdir %d\n", ret)); return ret; } /* * 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 from the nlink field of the * hidden link. * * If the count goes to 0, we delete this hidden * link too. */ int UMSDOS_unlink (struct inode *dir, struct dentry *dentry) { struct dentry *temp, *link = NULL; struct inode *inode; int ret; struct umsdos_info info; Printk(("UMSDOS_unlink: entering %s/%s\n", dentry->d_parent->d_name.name, dentry->d_name.name)); ret = umsdos_nevercreat (dir, dentry, -EPERM); if (ret) goto out; ret = umsdos_parse (dentry->d_name.name, dentry->d_name.len, &info); if (ret) goto out; umsdos_lockcreate (dir); ret = umsdos_findentry (dentry->d_parent, &info, 1); if (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)); /* * 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) { d_drop(dentry); } /* Do a real lookup to get the short name dentry */ temp = umsdos_covered(dentry->d_parent, info.fake.fname, info.fake.len); ret = PTR_ERR(temp); if (IS_ERR(temp)) goto out_unlock; /* * Resolve hardlinks now, but defer processing until later. */ if (info.entry.flags & UMSDOS_HLINK) { link = umsdos_solve_hlink(dget(temp)); } /* Delete the EMD entry */ ret = umsdos_delentry (dentry->d_parent, &info, 0); if (ret && ret != -ENOENT) { printk(KERN_WARNING "UMSDOS_unlink: delentry %s, error=%d\n", info.entry.name, ret); goto out_dput; } ret = msdos_unlink(dir, temp); if (!ret) d_delete(temp); #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: dput(temp); 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 d_delete(link); } else { struct iattr newattrs; inode->i_nlink--; newattrs.ia_valid = 0; ret = umsdos_notify_change_locked(link, &newattrs); if (!ret) mark_inode_dirty(link->d_inode); } out_cleanup: d_drop(link); dput(link); out: Printk (("umsdos_unlink %d\n", ret)); return ret; } /* * Rename (move) a file. */ int UMSDOS_rename (struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) { int ret; ret = umsdos_nevercreat (new_dir, new_dentry, -EEXIST); if (ret) return ret; /* * If the target already exists, delete it first. */ if (new_dentry->d_inode) { dget(new_dentry); if (S_ISDIR(old_dentry->d_inode->i_mode)) ret = UMSDOS_rmdir (new_dir, new_dentry); else ret = UMSDOS_unlink (new_dir, new_dentry); if (!ret) d_drop(new_dentry); dput(new_dentry); if (ret) return ret; } ret = umsdos_rename_f(old_dir, old_dentry, new_dir, new_dentry, 0); return ret; }