| /* |
| * 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"); |