811 lines
20 KiB
C
811 lines
20 KiB
C
/*
|
|
* linux/fs/umsdos/dir.c
|
|
*
|
|
* Written 1993 by Jacques Gelinas
|
|
* Inspired from linux/fs/msdos/... : Werner Almesberger
|
|
*
|
|
* Extended MS-DOS directory handling functions
|
|
*/
|
|
|
|
#include <linux/time.h>
|
|
#include <linux/string.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/msdos_fs.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/stat.h>
|
|
#include <linux/limits.h>
|
|
#include <linux/umsdos_fs.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/pagemap.h>
|
|
#include <linux/smp_lock.h>
|
|
|
|
#define UMSDOS_SPECIAL_DIRFPOS 3
|
|
extern struct dentry *saved_root;
|
|
extern struct inode *pseudo_root;
|
|
|
|
/* #define UMSDOS_DEBUG_VERBOSE 1 */
|
|
|
|
/*
|
|
* Dentry operations routines
|
|
*/
|
|
|
|
/* nothing for now ... */
|
|
static int umsdos_dentry_validate(struct dentry *dentry, struct nameidata *nd)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
/* for now, drop everything to force lookups ... */
|
|
/* ITYM s/everything/& positive/... */
|
|
static int umsdos_dentry_dput(struct dentry *dentry)
|
|
{
|
|
struct inode *inode = dentry->d_inode;
|
|
if (inode) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
struct dentry_operations umsdos_dentry_operations =
|
|
{
|
|
.d_revalidate = umsdos_dentry_validate,
|
|
.d_delete = umsdos_dentry_dput,
|
|
};
|
|
|
|
struct UMSDOS_DIR_ONCE {
|
|
void *dirbuf;
|
|
filldir_t filldir;
|
|
int count;
|
|
int stop;
|
|
};
|
|
|
|
/*
|
|
* Record a single entry the first call.
|
|
* Return -EINVAL the next one.
|
|
* NOTE: filldir DOES NOT use a dentry
|
|
*/
|
|
|
|
static int umsdos_dir_once ( void *buf,
|
|
const char *name,
|
|
int len,
|
|
loff_t offset,
|
|
ino_t ino,
|
|
unsigned type)
|
|
{
|
|
int ret = -EINVAL;
|
|
struct UMSDOS_DIR_ONCE *d = (struct UMSDOS_DIR_ONCE *) buf;
|
|
|
|
if (d->count == 0) {
|
|
PRINTK ((KERN_DEBUG "dir_once :%.*s: offset %Ld\n",
|
|
len, name, offset));
|
|
ret = d->filldir (d->dirbuf, name, len, offset, ino, DT_UNKNOWN);
|
|
d->stop = ret < 0;
|
|
d->count = 1;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* Read count directory entries from directory filp
|
|
* Return a negative value from linux/errno.h.
|
|
* Return > 0 if success (the number of bytes written by filldir).
|
|
*
|
|
* This function is used by the normal readdir VFS entry point,
|
|
* and in order to get the directory entry from a file's dentry.
|
|
* See umsdos_dentry_to_entry() below.
|
|
*/
|
|
|
|
static int umsdos_readdir_x (struct inode *dir, struct file *filp,
|
|
void *dirbuf, struct umsdos_dirent *u_entry,
|
|
filldir_t filldir)
|
|
{
|
|
struct dentry *demd;
|
|
off_t start_fpos;
|
|
int ret = 0;
|
|
loff_t pos;
|
|
|
|
umsdos_startlookup (dir);
|
|
|
|
if (filp->f_pos == UMSDOS_SPECIAL_DIRFPOS && dir == pseudo_root) {
|
|
|
|
/*
|
|
* We don't need to simulate this pseudo directory
|
|
* when umsdos_readdir_x is called for internal operation
|
|
* of umsdos. This is why dirent_in_fs is tested
|
|
*/
|
|
/* #Specification: pseudo root / directory /DOS
|
|
* When umsdos operates in pseudo root mode (C:\linux is the
|
|
* linux root), it simulate a directory /DOS which points to
|
|
* the real root of the file system.
|
|
*/
|
|
|
|
Printk ((KERN_WARNING "umsdos_readdir_x: pseudo_root thing UMSDOS_SPECIAL_DIRFPOS\n"));
|
|
if (filldir (dirbuf, "DOS", 3,
|
|
UMSDOS_SPECIAL_DIRFPOS, UMSDOS_ROOT_INO, DT_DIR) == 0) {
|
|
filp->f_pos++;
|
|
}
|
|
goto out_end;
|
|
}
|
|
|
|
if (filp->f_pos < 2 ||
|
|
(dir->i_ino != UMSDOS_ROOT_INO && filp->f_pos == 32)) {
|
|
|
|
int last_f_pos = filp->f_pos;
|
|
struct UMSDOS_DIR_ONCE bufk;
|
|
|
|
Printk (("umsdos_readdir_x: . or .. /mn/?\n"));
|
|
|
|
bufk.dirbuf = dirbuf;
|
|
bufk.filldir = filldir;
|
|
bufk.count = 0;
|
|
|
|
ret = fat_readdir (filp, &bufk, umsdos_dir_once);
|
|
if (last_f_pos > 0 && filp->f_pos > last_f_pos)
|
|
filp->f_pos = UMSDOS_SPECIAL_DIRFPOS;
|
|
if (u_entry != NULL)
|
|
u_entry->flags = 0;
|
|
goto out_end;
|
|
}
|
|
|
|
Printk (("umsdos_readdir_x: normal file /mn/?\n"));
|
|
|
|
/* get the EMD dentry */
|
|
demd = umsdos_get_emd_dentry(filp->f_dentry);
|
|
ret = PTR_ERR(demd);
|
|
if (IS_ERR(demd))
|
|
goto out_end;
|
|
ret = -EIO;
|
|
if (!demd->d_inode) {
|
|
printk(KERN_WARNING
|
|
"umsdos_readir_x: EMD file %s/%s not found\n",
|
|
demd->d_parent->d_name.name, demd->d_name.name);
|
|
goto out_dput;
|
|
}
|
|
|
|
pos = filp->f_pos;
|
|
start_fpos = filp->f_pos;
|
|
|
|
if (pos <= UMSDOS_SPECIAL_DIRFPOS + 1)
|
|
pos = 0;
|
|
ret = 0;
|
|
while (pos < demd->d_inode->i_size) {
|
|
off_t cur_f_pos = pos;
|
|
struct dentry *dret;
|
|
struct inode *inode;
|
|
struct umsdos_dirent entry;
|
|
struct umsdos_info info;
|
|
|
|
ret = -EIO;
|
|
if (umsdos_emd_dir_readentry (demd, &pos, &entry) != 0)
|
|
break;
|
|
if (entry.name_len == 0)
|
|
continue;
|
|
#ifdef UMSDOS_DEBUG_VERBOSE
|
|
if (entry.flags & UMSDOS_HLINK)
|
|
printk("umsdos_readdir_x: %s/%s is hardlink\n",
|
|
filp->f_dentry->d_name.name, entry.name);
|
|
#endif
|
|
|
|
umsdos_parse (entry.name, entry.name_len, &info);
|
|
info.f_pos = cur_f_pos;
|
|
umsdos_manglename (&info);
|
|
/*
|
|
* Do a real lookup on the short name.
|
|
*/
|
|
dret = umsdos_covered(filp->f_dentry, info.fake.fname,
|
|
info.fake.len);
|
|
ret = PTR_ERR(dret);
|
|
if (IS_ERR(dret))
|
|
break;
|
|
/*
|
|
* If the file wasn't found, remove it from the EMD.
|
|
*/
|
|
inode = dret->d_inode;
|
|
if (!inode)
|
|
goto remove_name;
|
|
#ifdef UMSDOS_DEBUG_VERBOSE
|
|
if (UMSDOS_I(inode)->i_is_hlink)
|
|
printk("umsdos_readdir_x: %s/%s already resolved, ino=%ld\n",
|
|
dret->d_parent->d_name.name, dret->d_name.name, inode->i_ino);
|
|
#endif
|
|
|
|
Printk (("Found %s/%s, ino=%ld, flags=%x\n",
|
|
dret->d_parent->d_name.name, info.fake.fname, dret->d_inode->i_ino,
|
|
entry.flags));
|
|
/* check whether to resolve a hard-link */
|
|
if ((entry.flags & UMSDOS_HLINK) &&
|
|
!UMSDOS_I(inode)->i_is_hlink) {
|
|
dret = umsdos_solve_hlink (dret);
|
|
ret = PTR_ERR(dret);
|
|
if (IS_ERR(dret))
|
|
break;
|
|
inode = dret->d_inode;
|
|
if (!inode) {
|
|
printk("umsdos_readdir_x: %s/%s negative after link\n",
|
|
dret->d_parent->d_name.name, dret->d_name.name);
|
|
goto clean_up;
|
|
}
|
|
}
|
|
|
|
/* #Specification: pseudo root / reading real root
|
|
* The pseudo root (/linux) is logically
|
|
* erased from the real root. This means that
|
|
* ls /DOS, won't show "linux". This avoids
|
|
* infinite recursion (/DOS/linux/DOS/linux/...) while
|
|
* walking the file system.
|
|
*/
|
|
if (inode != pseudo_root && !(entry.flags & UMSDOS_HIDDEN)) {
|
|
if (filldir (dirbuf, entry.name, entry.name_len,
|
|
cur_f_pos, inode->i_ino, DT_UNKNOWN) < 0) {
|
|
pos = cur_f_pos;
|
|
}
|
|
Printk(("umsdos_readdir_x: got %s/%s, ino=%ld\n",
|
|
dret->d_parent->d_name.name, dret->d_name.name, inode->i_ino));
|
|
if (u_entry != NULL)
|
|
*u_entry = entry;
|
|
dput(dret);
|
|
ret = 0;
|
|
break;
|
|
}
|
|
clean_up:
|
|
dput(dret);
|
|
continue;
|
|
|
|
remove_name:
|
|
/* #Specification: umsdos / readdir / not in MSDOS
|
|
* During a readdir operation, if the file is not
|
|
* in the MS-DOS directory any more, the entry is
|
|
* removed from the EMD file silently.
|
|
*/
|
|
#ifdef UMSDOS_PARANOIA
|
|
printk("umsdos_readdir_x: %s/%s out of sync, erasing\n",
|
|
filp->f_dentry->d_name.name, info.entry.name);
|
|
#endif
|
|
ret = umsdos_delentry(filp->f_dentry, &info,
|
|
S_ISDIR(info.entry.mode));
|
|
if (ret)
|
|
printk(KERN_WARNING
|
|
"umsdos_readdir_x: delentry %s, err=%d\n",
|
|
info.entry.name, ret);
|
|
goto clean_up;
|
|
}
|
|
/*
|
|
* If the fillbuf has failed, f_pos is back to 0.
|
|
* To avoid getting back into the . and .. state
|
|
* (see comments at the beginning), we put back
|
|
* the special offset.
|
|
*/
|
|
filp->f_pos = pos;
|
|
if (filp->f_pos == 0)
|
|
filp->f_pos = start_fpos;
|
|
out_dput:
|
|
dput(demd);
|
|
|
|
out_end:
|
|
umsdos_endlookup (dir);
|
|
|
|
Printk ((KERN_DEBUG "read dir %p pos %Ld ret %d\n",
|
|
dir, filp->f_pos, ret));
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* Read count directory entries from directory filp.
|
|
* Return a negative value from linux/errno.h.
|
|
* Return 0 or positive if successful.
|
|
*/
|
|
|
|
static int UMSDOS_readdir (struct file *filp, void *dirbuf, filldir_t filldir)
|
|
{
|
|
struct inode *dir = filp->f_dentry->d_inode;
|
|
int ret = 0, count = 0;
|
|
struct UMSDOS_DIR_ONCE bufk;
|
|
|
|
lock_kernel();
|
|
|
|
bufk.dirbuf = dirbuf;
|
|
bufk.filldir = filldir;
|
|
bufk.stop = 0;
|
|
|
|
Printk (("UMSDOS_readdir in\n"));
|
|
while (ret == 0 && bufk.stop == 0) {
|
|
struct umsdos_dirent entry;
|
|
|
|
bufk.count = 0;
|
|
ret = umsdos_readdir_x (dir, filp, &bufk, &entry,
|
|
umsdos_dir_once);
|
|
if (bufk.count == 0)
|
|
break;
|
|
count += bufk.count;
|
|
}
|
|
unlock_kernel();
|
|
Printk (("UMSDOS_readdir out %d count %d pos %Ld\n",
|
|
ret, count, filp->f_pos));
|
|
return count ? : ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* Complete the inode content with info from the EMD file.
|
|
*
|
|
* This function modifies the state of a dir inode. It decides
|
|
* whether the dir is a UMSDOS or DOS directory. This is done
|
|
* deeper in umsdos_patch_inode() called at the end of this function.
|
|
*
|
|
* Because it is does disk access, umsdos_patch_inode() may block.
|
|
* At the same time, another process may get here to initialise
|
|
* the same directory inode. There are three cases.
|
|
*
|
|
* 1) The inode is already initialised. We do nothing.
|
|
* 2) The inode is not initialised. We lock access and do it.
|
|
* 3) Like 2 but another process has locked the inode, so we try
|
|
* to lock it and check right afterward check whether
|
|
* initialisation is still needed.
|
|
*
|
|
*
|
|
* Thanks to the "mem" option of the kernel command line, it was
|
|
* possible to consistently reproduce this problem by limiting
|
|
* my memory to 4 MB and running X.
|
|
*
|
|
* Do this only if the inode is freshly read, because we will lose
|
|
* the current (updated) content.
|
|
*
|
|
* A lookup of a mount point directory yield the inode into
|
|
* the other fs, so we don't care about initialising it. iget()
|
|
* does this automatically.
|
|
*/
|
|
|
|
void umsdos_lookup_patch_new(struct dentry *dentry, struct umsdos_info *info)
|
|
{
|
|
struct inode *inode = dentry->d_inode;
|
|
struct umsdos_dirent *entry = &info->entry;
|
|
|
|
/*
|
|
* This part of the initialization depends only on i_patched.
|
|
*/
|
|
if (UMSDOS_I(inode)->i_patched)
|
|
goto out;
|
|
UMSDOS_I(inode)->i_patched = 1;
|
|
if (S_ISREG (entry->mode))
|
|
entry->mtime = inode->i_mtime;
|
|
inode->i_mode = entry->mode;
|
|
inode->i_rdev = to_kdev_t (entry->rdev);
|
|
inode->i_atime = entry->atime;
|
|
inode->i_ctime = entry->ctime;
|
|
inode->i_mtime = entry->mtime;
|
|
inode->i_uid = entry->uid;
|
|
inode->i_gid = entry->gid;
|
|
|
|
/* #Specification: umsdos / i_nlink
|
|
* The nlink field of an inode is maintained by the MSDOS file system
|
|
* for directory and by UMSDOS for other files. The logic is that
|
|
* MSDOS is already figuring out what to do for directories and
|
|
* does nothing for other files. For MSDOS, there are no hard links
|
|
* so all file carry nlink==1. UMSDOS use some info in the
|
|
* EMD file to plug the correct value.
|
|
*/
|
|
if (!S_ISDIR (entry->mode)) {
|
|
if (entry->nlink > 0) {
|
|
inode->i_nlink = entry->nlink;
|
|
} else {
|
|
printk (KERN_ERR
|
|
"UMSDOS: lookup_patch entry->nlink < 1 ???\n");
|
|
}
|
|
}
|
|
/*
|
|
* The mode may have changed, so patch the inode again.
|
|
*/
|
|
umsdos_patch_dentry_inode(dentry, info->f_pos);
|
|
umsdos_set_dirinfo_new(dentry, info->f_pos);
|
|
|
|
out:
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* Return != 0 if an entry is the pseudo DOS entry in the pseudo root.
|
|
*/
|
|
|
|
int umsdos_is_pseudodos (struct inode *dir, struct dentry *dentry)
|
|
{
|
|
/* #Specification: pseudo root / DOS hard coded
|
|
* The pseudo sub-directory DOS in the pseudo root is hard coded.
|
|
* The name is DOS. This is done this way to help standardised
|
|
* the umsdos layout. The idea is that from now on /DOS is
|
|
* a reserved path and nobody will think of using such a path
|
|
* for a package.
|
|
*/
|
|
return dir == pseudo_root
|
|
&& dentry->d_name.len == 3
|
|
&& dentry->d_name.name[0] == 'D'
|
|
&& dentry->d_name.name[1] == 'O'
|
|
&& dentry->d_name.name[2] == 'S';
|
|
}
|
|
|
|
|
|
/*
|
|
* Check whether a file exists in the current directory.
|
|
* Return 0 if OK, negative error code if not (ex: -ENOENT).
|
|
*
|
|
* fills dentry->d_inode with found inode, and increments its count.
|
|
* if not found, return -ENOENT.
|
|
*/
|
|
/* #Specification: umsdos / lookup
|
|
* A lookup for a file is done in two steps. First, we
|
|
* locate the file in the EMD file. If not present, we
|
|
* return an error code (-ENOENT). If it is there, we
|
|
* repeat the operation on the msdos file system. If
|
|
* this fails, it means that the file system is not in
|
|
* sync with the EMD file. We silently remove this
|
|
* entry from the EMD file, and return ENOENT.
|
|
*/
|
|
|
|
struct dentry *umsdos_lookup_x (struct inode *dir, struct dentry *dentry, int nopseudo)
|
|
{
|
|
struct dentry *dret = NULL;
|
|
struct inode *inode;
|
|
int ret = -ENOENT;
|
|
struct umsdos_info info;
|
|
|
|
#ifdef UMSDOS_DEBUG_VERBOSE
|
|
printk("umsdos_lookup_x: looking for %s/%s\n",
|
|
dentry->d_parent->d_name.name, dentry->d_name.name);
|
|
#endif
|
|
|
|
umsdos_startlookup (dir);
|
|
if (umsdos_is_pseudodos (dir, dentry)) {
|
|
/* #Specification: pseudo root / lookup(DOS)
|
|
* A lookup of DOS in the pseudo root will always succeed
|
|
* and return the inode of the real root.
|
|
*/
|
|
Printk ((KERN_DEBUG "umsdos_lookup_x: following /DOS\n"));
|
|
inode = saved_root->d_inode;
|
|
goto out_add;
|
|
}
|
|
|
|
ret = umsdos_parse (dentry->d_name.name, dentry->d_name.len, &info);
|
|
if (ret) {
|
|
printk("umsdos_lookup_x: %s/%s parse failed, ret=%d\n",
|
|
dentry->d_parent->d_name.name, dentry->d_name.name, ret);
|
|
goto out;
|
|
}
|
|
|
|
ret = umsdos_findentry (dentry->d_parent, &info, 0);
|
|
if (ret) {
|
|
if (ret != -ENOENT)
|
|
printk("umsdos_lookup_x: %s/%s findentry failed, ret=%d\n",
|
|
dentry->d_parent->d_name.name, dentry->d_name.name, ret);
|
|
goto out;
|
|
}
|
|
Printk (("lookup %.*s pos %lu ret %d len %d ",
|
|
info.fake.len, info.fake.fname, info.f_pos, ret, info.fake.len));
|
|
|
|
/* do a real lookup to get the short name ... */
|
|
dret = umsdos_covered(dentry->d_parent, info.fake.fname, info.fake.len);
|
|
ret = PTR_ERR(dret);
|
|
if (IS_ERR(dret)) {
|
|
printk("umsdos_lookup_x: %s/%s real lookup failed, ret=%d\n",
|
|
dentry->d_parent->d_name.name, dentry->d_name.name, ret);
|
|
goto out;
|
|
}
|
|
inode = dret->d_inode;
|
|
if (!inode)
|
|
goto out_remove;
|
|
umsdos_lookup_patch_new(dret, &info);
|
|
#ifdef UMSDOS_DEBUG_VERBOSE
|
|
printk("umsdos_lookup_x: found %s/%s, ino=%ld\n",
|
|
dret->d_parent->d_name.name, dret->d_name.name, dret->d_inode->i_ino);
|
|
#endif
|
|
|
|
/* Check for a hard link */
|
|
if ((info.entry.flags & UMSDOS_HLINK) &&
|
|
!UMSDOS_I(inode)->i_is_hlink) {
|
|
dret = umsdos_solve_hlink (dret);
|
|
ret = PTR_ERR(dret);
|
|
if (IS_ERR(dret))
|
|
goto out;
|
|
ret = -ENOENT;
|
|
inode = dret->d_inode;
|
|
if (!inode) {
|
|
printk("umsdos_lookup_x: %s/%s negative after link\n",
|
|
dret->d_parent->d_name.name, dret->d_name.name);
|
|
goto out_dput;
|
|
}
|
|
}
|
|
|
|
if (inode == pseudo_root && !nopseudo) {
|
|
/* #Specification: pseudo root / dir lookup
|
|
* For the same reason as readdir, a lookup in /DOS for
|
|
* the pseudo root directory (linux) will fail.
|
|
*/
|
|
/*
|
|
* This has to be allowed for resolving hard links
|
|
* which are recorded independently of the pseudo-root
|
|
* mode.
|
|
*/
|
|
printk("umsdos_lookup_x: skipping DOS/linux\n");
|
|
ret = -ENOENT;
|
|
goto out_dput;
|
|
}
|
|
|
|
/*
|
|
* We've found it OK. Now hash the dentry with the inode.
|
|
*/
|
|
out_add:
|
|
atomic_inc(&inode->i_count);
|
|
d_add (dentry, inode);
|
|
dentry->d_op = &umsdos_dentry_operations;
|
|
ret = 0;
|
|
|
|
out_dput:
|
|
if (dret && dret != dentry)
|
|
d_drop(dret);
|
|
dput(dret);
|
|
out:
|
|
umsdos_endlookup (dir);
|
|
return ERR_PTR(ret);
|
|
|
|
out_remove:
|
|
printk(KERN_WARNING "UMSDOS: entry %s/%s out of sync, erased\n",
|
|
dentry->d_parent->d_name.name, dentry->d_name.name);
|
|
umsdos_delentry (dentry->d_parent, &info, S_ISDIR (info.entry.mode));
|
|
ret = -ENOENT;
|
|
goto out_dput;
|
|
}
|
|
|
|
|
|
/*
|
|
* Check whether a file exists in the current directory.
|
|
* Return 0 if OK, negative error code if not (ex: -ENOENT).
|
|
*
|
|
* Called by VFS; should fill dentry->d_inode via d_add.
|
|
*/
|
|
|
|
struct dentry *UMSDOS_lookup (struct inode *dir, struct dentry *dentry, struct nameidata *nd)
|
|
{
|
|
struct dentry *ret;
|
|
|
|
ret = umsdos_lookup_x (dir, dentry, 0);
|
|
|
|
/* Create negative dentry if not found. */
|
|
if (ret == ERR_PTR(-ENOENT)) {
|
|
Printk ((KERN_DEBUG
|
|
"UMSDOS_lookup: converting -ENOENT to negative\n"));
|
|
d_add (dentry, NULL);
|
|
dentry->d_op = &umsdos_dentry_operations;
|
|
ret = NULL;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
struct dentry *umsdos_covered(struct dentry *parent, char *name, int len)
|
|
{
|
|
struct dentry *result, *dentry;
|
|
struct qstr qstr;
|
|
|
|
qstr.name = name;
|
|
qstr.len = len;
|
|
qstr.hash = full_name_hash(name, len);
|
|
result = ERR_PTR(-ENOMEM);
|
|
dentry = d_alloc(parent, &qstr);
|
|
if (dentry) {
|
|
/* XXXXXXXXXXXXXXXXXXX Race alert! */
|
|
result = UMSDOS_rlookup(parent->d_inode, dentry);
|
|
d_drop(dentry);
|
|
if (result)
|
|
goto out_fail;
|
|
return dentry;
|
|
}
|
|
out:
|
|
return result;
|
|
|
|
out_fail:
|
|
dput(dentry);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Lookup or create a dentry from within the filesystem.
|
|
*
|
|
* We need to use this instead of lookup_dentry, as the
|
|
* directory semaphore lock is already held.
|
|
*/
|
|
struct dentry *umsdos_lookup_dentry(struct dentry *parent, char *name, int len,
|
|
int real)
|
|
{
|
|
struct dentry *result, *dentry;
|
|
struct qstr qstr;
|
|
|
|
qstr.name = name;
|
|
qstr.len = len;
|
|
qstr.hash = full_name_hash(name, len);
|
|
result = d_lookup(parent, &qstr);
|
|
if (!result) {
|
|
result = ERR_PTR(-ENOMEM);
|
|
dentry = d_alloc(parent, &qstr);
|
|
if (dentry) {
|
|
result = real ?
|
|
UMSDOS_rlookup(parent->d_inode, dentry) :
|
|
UMSDOS_lookup(parent->d_inode, dentry);
|
|
if (result)
|
|
goto out_fail;
|
|
return dentry;
|
|
}
|
|
}
|
|
out:
|
|
return result;
|
|
|
|
out_fail:
|
|
dput(dentry);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Return a path relative to our root.
|
|
*/
|
|
char * umsdos_d_path(struct dentry *dentry, char * buffer, int len)
|
|
{
|
|
struct dentry * old_root;
|
|
char * path;
|
|
|
|
read_lock(¤t->fs->lock);
|
|
old_root = dget(current->fs->root);
|
|
read_unlock(¤t->fs->lock);
|
|
spin_lock(&dcache_lock);
|
|
path = __d_path(dentry, current->fs->rootmnt, dentry->d_sb->s_root, current->fs->rootmnt, buffer, len); /* FIXME: current->fs->rootmnt */
|
|
spin_unlock(&dcache_lock);
|
|
|
|
if (*path == '/')
|
|
path++; /* skip leading '/' */
|
|
|
|
if (current->fs->root->d_inode == pseudo_root)
|
|
{
|
|
*(path-1) = '/';
|
|
path -= (UMSDOS_PSDROOT_LEN+1);
|
|
memcpy(path, UMSDOS_PSDROOT_NAME, UMSDOS_PSDROOT_LEN);
|
|
}
|
|
dput(old_root);
|
|
|
|
return path;
|
|
}
|
|
|
|
/*
|
|
* Return the dentry which points to a pseudo-hardlink.
|
|
*
|
|
* it should try to find file it points to
|
|
* if file is found, return new dentry/inode
|
|
* The resolved inode will have i_is_hlink set.
|
|
*
|
|
* Note: the original dentry is always dput(), even if an error occurs.
|
|
*/
|
|
|
|
struct dentry *umsdos_solve_hlink (struct dentry *hlink)
|
|
{
|
|
/* root is our root for resolving pseudo-hardlink */
|
|
struct dentry *base = hlink->d_sb->s_root;
|
|
struct dentry *dentry_dst;
|
|
char *path, *pt;
|
|
int len;
|
|
struct address_space *mapping = hlink->d_inode->i_mapping;
|
|
struct page *page;
|
|
|
|
page=read_cache_page(mapping,0,(filler_t *)mapping->a_ops->readpage,NULL);
|
|
dentry_dst=(struct dentry *)page;
|
|
if (IS_ERR(page))
|
|
goto out;
|
|
wait_on_page_locked(page);
|
|
if (!PageUptodate(page))
|
|
goto async_fail;
|
|
|
|
dentry_dst = ERR_PTR(-ENOMEM);
|
|
path = (char *) kmalloc (PATH_MAX, GFP_KERNEL);
|
|
if (path == NULL)
|
|
goto out_release;
|
|
memcpy(path, kmap(page), hlink->d_inode->i_size);
|
|
kunmap(page);
|
|
page_cache_release(page);
|
|
|
|
len = hlink->d_inode->i_size;
|
|
|
|
/* start at root dentry */
|
|
dentry_dst = dget(base);
|
|
path[len] = '\0';
|
|
|
|
pt = path;
|
|
if (*path == '/')
|
|
pt++; /* skip leading '/' */
|
|
|
|
if (base->d_inode == pseudo_root)
|
|
pt += (UMSDOS_PSDROOT_LEN + 1);
|
|
|
|
while (1) {
|
|
struct dentry *dir = dentry_dst, *demd;
|
|
char *start = pt;
|
|
int real;
|
|
|
|
while (*pt != '\0' && *pt != '/') pt++;
|
|
len = (int) (pt - start);
|
|
if (*pt == '/') *pt++ = '\0';
|
|
|
|
real = 1;
|
|
demd = umsdos_get_emd_dentry(dir);
|
|
if (!IS_ERR(demd)) {
|
|
if (demd->d_inode)
|
|
real = 0;
|
|
dput(demd);
|
|
}
|
|
|
|
#ifdef UMSDOS_DEBUG_VERBOSE
|
|
printk ("umsdos_solve_hlink: dir %s/%s, name=%s, real=%d\n",
|
|
dir->d_parent->d_name.name, dir->d_name.name, start, real);
|
|
#endif
|
|
dentry_dst = umsdos_lookup_dentry(dir, start, len, real);
|
|
/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */
|
|
if (real)
|
|
d_drop(dir);
|
|
dput (dir);
|
|
if (IS_ERR(dentry_dst))
|
|
break;
|
|
/* not found? stop search ... */
|
|
if (!dentry_dst->d_inode) {
|
|
break;
|
|
}
|
|
if (*pt == '\0') /* we're finished! */
|
|
break;
|
|
} /* end while */
|
|
|
|
if (!IS_ERR(dentry_dst)) {
|
|
struct inode *inode = dentry_dst->d_inode;
|
|
if (inode) {
|
|
UMSDOS_I(inode)->i_is_hlink = 1;
|
|
#ifdef UMSDOS_DEBUG_VERBOSE
|
|
printk ("umsdos_solve_hlink: resolved link %s/%s, ino=%ld\n",
|
|
dentry_dst->d_parent->d_name.name, dentry_dst->d_name.name, inode->i_ino);
|
|
#endif
|
|
} else {
|
|
#ifdef UMSDOS_DEBUG_VERBOSE
|
|
printk ("umsdos_solve_hlink: resolved link %s/%s negative!\n",
|
|
dentry_dst->d_parent->d_name.name, dentry_dst->d_name.name);
|
|
#endif
|
|
}
|
|
} else
|
|
printk(KERN_WARNING
|
|
"umsdos_solve_hlink: err=%ld\n", PTR_ERR(dentry_dst));
|
|
kfree (path);
|
|
|
|
out:
|
|
dput(hlink); /* original hlink no longer needed */
|
|
return dentry_dst;
|
|
|
|
async_fail:
|
|
dentry_dst = ERR_PTR(-EIO);
|
|
out_release:
|
|
page_cache_release(page);
|
|
goto out;
|
|
}
|
|
|
|
|
|
struct file_operations umsdos_dir_operations =
|
|
{
|
|
.read = generic_read_dir,
|
|
.readdir = UMSDOS_readdir,
|
|
.ioctl = UMSDOS_ioctl_dir,
|
|
};
|
|
|
|
struct inode_operations umsdos_dir_inode_operations =
|
|
{
|
|
.create = UMSDOS_create,
|
|
.lookup = UMSDOS_lookup,
|
|
.link = UMSDOS_link,
|
|
.unlink = UMSDOS_unlink,
|
|
.symlink = UMSDOS_symlink,
|
|
.mkdir = UMSDOS_mkdir,
|
|
.rmdir = UMSDOS_rmdir,
|
|
.mknod = UMSDOS_mknod,
|
|
.rename = UMSDOS_rename,
|
|
.setattr = UMSDOS_notify_change,
|
|
};
|