/* * linux/fs/umsdos/dir.c * * Written 1993 by Jacques Gelinas * Inspired from linux/fs/msdos/... : Werner Almesberger * * Extended MS-DOS directory handling functions */ #include #include #include #include #include #include #include #include #include #include #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, int flags) { 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 (inode->u.umsdos_i.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) && !inode->u.umsdos_i.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; 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; } 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 (inode->u.umsdos_i.i_patched) goto out; inode->u.umsdos_i.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) && !inode->u.umsdos_i.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 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(page); if (!Page_Uptodate(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) { inode->u.umsdos_i.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, };