blob: ad0104d26056b159494338b47e49bc4ca472227b [file] [log] [blame]
/*
* linux/fs/umsdos/inode.c
*
* Written 1993 by Jacques Gelinas
* Inspired from linux/fs/msdos/... by Werner Almesberger
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/msdos_fs.h>
#include <linux/kernel.h>
#include <linux/time.h>
#include <linux/errno.h>
#include <asm/uaccess.h>
#include <linux/string.h>
#include <linux/stat.h>
#include <linux/umsdos_fs.h>
#include <linux/list.h>
#include <linux/pagemap.h>
extern struct dentry_operations umsdos_dentry_operations;
struct dentry *saved_root; /* Original root if changed */
struct inode *pseudo_root; /* Useful to simulate the pseudo DOS */
/* directory. See UMSDOS_readdir_x() */
static struct dentry *check_pseudo_root(struct super_block *);
void UMSDOS_put_inode (struct inode *inode)
{
PRINTK ((KERN_DEBUG
"put inode %p (%lu) pos %lu count=%d\n"
,inode, inode->i_ino
,UMSDOS_I(inode)->pos
,atomic_read(&inode->i_count)));
if (inode == pseudo_root) {
Printk ((KERN_ERR "Umsdos: debug: releasing pseudo_root - ino=%lu count=%d\n", inode->i_ino, atomic_read(&inode->i_count)));
}
if (atomic_read(&inode->i_count) == 1)
UMSDOS_I(inode)->i_patched = 0;
}
void UMSDOS_put_super (struct super_block *sb)
{
Printk ((KERN_DEBUG "UMSDOS_put_super: entering\n"));
if (saved_root && pseudo_root && kdev_same(sb->s_dev, ROOT_DEV)) {
shrink_dcache_parent(saved_root);
dput(saved_root);
saved_root = NULL;
pseudo_root = NULL;
}
msdos_put_super (sb);
}
/*
* Complete the setup of a directory dentry based on its
* EMD/non-EMD status. If it has an EMD, then plug the
* umsdos function table. If not, use the msdos one.
*/
void umsdos_setup_dir(struct dentry *dir)
{
struct inode *inode = dir->d_inode;
struct umsdos_inode_info *ui = UMSDOS_I(inode);
if (!S_ISDIR(inode->i_mode))
printk(KERN_ERR "umsdos_setup_dir: %s/%s not a dir!\n",
dir->d_parent->d_name.name, dir->d_name.name);
init_waitqueue_head (&ui->dir_info.p);
ui->dir_info.looking = 0;
ui->dir_info.creating = 0;
ui->dir_info.pid = 0;
inode->i_op = &umsdos_rdir_inode_operations;
inode->i_fop = &umsdos_rdir_operations;
if (umsdos_have_emd(dir)) {
Printk((KERN_DEBUG "umsdos_setup_dir: %s/%s using EMD\n",
dir->d_parent->d_name.name, dir->d_name.name));
inode->i_op = &umsdos_dir_inode_operations;
inode->i_fop = &umsdos_dir_operations;
}
}
/*
* Add some info into an inode so it can find its owner quickly
*/
void umsdos_set_dirinfo_new (struct dentry *dentry, off_t f_pos)
{
struct inode *inode = dentry->d_inode;
struct dentry *demd;
UMSDOS_I(inode)->pos = f_pos;
/* now check the EMD file */
demd = umsdos_get_emd_dentry(dentry->d_parent);
if (!IS_ERR(demd)) {
dput(demd);
}
return;
}
static struct inode_operations umsdos_file_inode_operations = {
truncate: fat_truncate,
setattr: UMSDOS_notify_change,
};
static struct inode_operations umsdos_symlink_inode_operations = {
readlink: page_readlink,
follow_link: page_follow_link,
setattr: UMSDOS_notify_change,
};
/*
* Connect the proper tables in the inode and add some info.
*/
/* #Specification: inode / umsdos info
* The first time an inode is seen (inode->i_count == 1),
* the inode number of the EMD file which controls this inode
* is tagged to this inode. It allows operations such as
* notify_change to be handled.
*/
void umsdos_patch_dentry_inode(struct dentry *dentry, off_t f_pos)
{
struct inode *inode = dentry->d_inode;
PRINTK (("umsdos_patch_dentry_inode: inode=%lu\n", inode->i_ino));
/*
* Classify the inode based on EMD/non-EMD status.
*/
PRINTK (("umsdos_patch_inode: call umsdos_set_dirinfo_new(%p,%lu)\n",
dentry, f_pos));
umsdos_set_dirinfo_new(dentry, f_pos);
inode->i_op = &umsdos_file_inode_operations;
if (S_ISREG (inode->i_mode)) {
/* address_space operations already set */
} else if (S_ISDIR (inode->i_mode)) {
umsdos_setup_dir(dentry);
} else if (S_ISLNK (inode->i_mode)) {
/* address_space operations already set */
inode->i_op = &umsdos_symlink_inode_operations;
} else
init_special_inode(inode, inode->i_mode,
kdev_t_to_nr(inode->i_rdev));
}
/*
* lock the parent dir before starting ...
* also handles hardlink converting
*/
int UMSDOS_notify_change (struct dentry *dentry, struct iattr *attr)
{
struct inode *dir, *inode;
struct umsdos_info info;
struct dentry *temp, *old_dentry = NULL;
int ret;
ret = umsdos_parse (dentry->d_name.name, dentry->d_name.len,
&info);
if (ret)
goto out;
ret = umsdos_findentry (dentry->d_parent, &info, 0);
if (ret) {
printk("UMSDOS_notify_change: %s/%s not in EMD, ret=%d\n",
dentry->d_parent->d_name.name, dentry->d_name.name, ret);
goto out;
}
if (info.entry.flags & UMSDOS_HLINK) {
/*
* In order to get the correct (real) inode, we just drop
* the original dentry.
*/
d_drop(dentry);
Printk(("UMSDOS_notify_change: hard link %s/%s, fake=%s\n",
dentry->d_parent->d_name.name, dentry->d_name.name, info.fake.fname));
/* 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;
/* now resolve the link ... */
temp = umsdos_solve_hlink(temp);
ret = PTR_ERR(temp);
if (IS_ERR(temp))
goto out;
old_dentry = dentry;
dentry = temp; /* so umsdos_notify_change_locked will operate on that */
}
dir = dentry->d_parent->d_inode;
inode = dentry->d_inode;
ret = inode_change_ok (inode, attr);
if (ret)
goto out;
down(&dir->i_sem);
ret = umsdos_notify_change_locked(dentry, attr);
up(&dir->i_sem);
if (ret == 0)
ret = inode_setattr (inode, attr);
out:
if (old_dentry)
dput (dentry); /* if we had to use fake dentry for hardlinks, dput() it now */
return ret;
}
/*
* Must be called with the parent lock held.
*/
int umsdos_notify_change_locked(struct dentry *dentry, struct iattr *attr)
{
struct inode *inode = dentry->d_inode;
struct dentry *demd;
struct address_space *mapping;
struct page *page;
int ret = 0;
struct umsdos_dirent *entry;
int offs;
Printk(("UMSDOS_notify_change: entering for %s/%s (%d)\n",
dentry->d_parent->d_name.name, dentry->d_name.name, UMSDOS_I(inode)->i_patched));
if (inode->i_nlink == 0)
goto out;
if (inode->i_ino == UMSDOS_ROOT_INO)
goto out;
/* get the EMD file dentry */
demd = umsdos_get_emd_dentry(dentry->d_parent);
ret = PTR_ERR(demd);
if (IS_ERR(demd))
goto out;
ret = 0;
/* don't do anything if directory is not promoted to umsdos yet */
if (!demd->d_inode) {
Printk((KERN_DEBUG
"UMSDOS_notify_change: no EMD file %s/%s\n",
demd->d_parent->d_name.name, demd->d_name.name));
goto out_dput;
}
/* don't do anything if this is the EMD itself */
if (inode == demd->d_inode)
goto out_dput;
/* This inode is not a EMD file nor an inode used internally
* by MSDOS, so we can update its status.
* See emd.c
*/
/* Read only the start of the entry since we don't touch the name */
mapping = demd->d_inode->i_mapping;
offs = UMSDOS_I(inode)->pos & ~PAGE_CACHE_MASK;
ret = -ENOMEM;
page=grab_cache_page(mapping,UMSDOS_I(inode)->pos>>PAGE_CACHE_SHIFT);
if (!page)
goto out_dput;
ret=mapping->a_ops->prepare_write(NULL,page,offs,offs+UMSDOS_REC_SIZE);
if (ret)
goto out_unlock;
entry = (struct umsdos_dirent *) (page_address(page) + offs);
if (attr->ia_valid & ATTR_UID)
entry->uid = cpu_to_le16(attr->ia_uid);
if (attr->ia_valid & ATTR_GID)
entry->gid = cpu_to_le16(attr->ia_gid);
if (attr->ia_valid & ATTR_MODE)
entry->mode = cpu_to_le16(attr->ia_mode);
if (attr->ia_valid & ATTR_ATIME)
entry->atime = cpu_to_le32(attr->ia_atime);
if (attr->ia_valid & ATTR_MTIME)
entry->mtime = cpu_to_le32(attr->ia_mtime);
if (attr->ia_valid & ATTR_CTIME)
entry->ctime = cpu_to_le32(attr->ia_ctime);
entry->nlink = cpu_to_le16(inode->i_nlink);
ret=mapping->a_ops->commit_write(NULL,page,offs,offs+UMSDOS_REC_SIZE);
if (ret)
printk(KERN_WARNING
"umsdos_notify_change: %s/%s EMD write error, ret=%d\n",
dentry->d_parent->d_name.name, dentry->d_name.name,ret);
/* #Specification: notify_change / msdos fs
* notify_change operation are done only on the
* EMD file. The msdos fs is not even called.
*/
out_unlock:
UnlockPage(page);
page_cache_release(page);
out_dput:
dput(demd);
out:
return ret;
}
/*
* Update the disk with the inode content
*/
void UMSDOS_write_inode (struct inode *inode, int wait)
{
struct iattr newattrs;
fat_write_inode (inode, wait);
newattrs.ia_mtime = inode->i_mtime;
newattrs.ia_atime = inode->i_atime;
newattrs.ia_ctime = inode->i_ctime;
newattrs.ia_valid = ATTR_MTIME | ATTR_ATIME | ATTR_CTIME;
/*
* UMSDOS_notify_change is convenient to call here
* to update the EMD entry associated with this inode.
* But it has the side effect to re"dirt" the inode.
*/
/*
* UMSDOS_notify_change (inode, &newattrs);
* inode->i_state &= ~I_DIRTY; / * FIXME: this doesn't work. We need to remove ourselves from list on dirty inodes. /mn/ */
}
static struct super_operations umsdos_sops =
{
write_inode: UMSDOS_write_inode,
put_inode: UMSDOS_put_inode,
delete_inode: fat_delete_inode,
put_super: UMSDOS_put_super,
statfs: UMSDOS_statfs,
clear_inode: fat_clear_inode,
};
int UMSDOS_statfs(struct super_block *sb,struct statfs *buf)
{
int ret;
ret = fat_statfs (sb, buf);
if (!ret)
buf->f_namelen = UMSDOS_MAXNAME;
return ret;
}
/*
* Read the super block of an Extended MS-DOS FS.
*/
struct super_block *UMSDOS_read_super (struct super_block *sb, void *data,
int silent)
{
struct super_block *res;
struct dentry *new_root;
/*
* Call msdos-fs to mount the disk.
* Note: this returns res == sb or NULL
*/
MSDOS_SB(sb)->options.isvfat = 0;
res = fat_read_super(sb, data, silent, &umsdos_rdir_inode_operations);
if (IS_ERR(res))
return NULL;
if (res == NULL) {
if (!silent)
printk(KERN_INFO "VFS: Can't find a valid "
"UMSDOS filesystem on dev %s.\n", sb->s_id);
return NULL;
}
printk (KERN_INFO "UMSDOS 0.86k "
"(compatibility level %d.%d, fast msdos)\n",
UMSDOS_VERSION, UMSDOS_RELEASE);
sb->s_op = &umsdos_sops;
MSDOS_SB(sb)->options.dotsOK = 0; /* disable hidden==dotfile */
/* install our dentry operations ... */
sb->s_root->d_op = &umsdos_dentry_operations;
umsdos_patch_dentry_inode(sb->s_root, 0);
/* Check whether to change to the /linux root */
new_root = check_pseudo_root(sb);
if (new_root) {
/* sanity check */
if (new_root->d_op != &umsdos_dentry_operations)
printk("umsdos_read_super: pseudo-root wrong ops!\n");
pseudo_root = new_root->d_inode;
saved_root = sb->s_root;
printk(KERN_INFO "UMSDOS: changed to alternate root\n");
dget (sb->s_root); sb->s_root = dget(new_root);
}
return sb;
}
/*
* Check for an alternate root if we're the root device.
*/
extern kdev_t ROOT_DEV;
static struct dentry *check_pseudo_root(struct super_block *sb)
{
struct dentry *root, *sbin, *init;
/*
* Check whether we're mounted as the root device.
* must check like this, because we can be used with initrd
*/
if (!kdev_same(sb->s_dev, ROOT_DEV))
goto out_noroot;
/*
* lookup_dentry needs a (so far non-existent) root.
*/
printk(KERN_INFO "check_pseudo_root: mounted as root\n");
root = lookup_one_len(UMSDOS_PSDROOT_NAME, sb->s_root,UMSDOS_PSDROOT_LEN);
if (IS_ERR(root))
goto out_noroot;
if (!root->d_inode || !S_ISDIR(root->d_inode->i_mode))
goto out_dput;
printk(KERN_INFO "check_pseudo_root: found %s/%s\n",
root->d_parent->d_name.name, root->d_name.name);
/* look for /sbin/init */
sbin = lookup_one_len("sbin", root, 4);
if (IS_ERR(sbin))
goto out_dput;
if (!sbin->d_inode || !S_ISDIR(sbin->d_inode->i_mode))
goto out_dput_sbin;
init = lookup_one_len("init", sbin, 4);
if (IS_ERR(init))
goto out_dput_sbin;
if (!init->d_inode)
goto out_dput_init;
printk(KERN_INFO "check_pseudo_root: found %s/%s, enabling pseudo-root\n", init->d_parent->d_name.name, init->d_name.name);
dput(sbin);
dput(init);
return root;
/* Alternate root not found ... */
out_dput_init:
dput(init);
out_dput_sbin:
dput(sbin);
out_dput:
dput(root);
out_noroot:
return NULL;
}
static DECLARE_FSTYPE_DEV(umsdos_fs_type, "umsdos", UMSDOS_read_super);
static int __init init_umsdos_fs (void)
{
return register_filesystem (&umsdos_fs_type);
}
static void __exit exit_umsdos_fs (void)
{
unregister_filesystem (&umsdos_fs_type);
}
EXPORT_NO_SYMBOLS;
module_init(init_umsdos_fs)
module_exit(exit_umsdos_fs)
MODULE_LICENSE("GPL");