/* * *very* heavily based on ramfs * * Copyright (C) 2001 Sistina Software * * This file is released under the GPL. */ #include #include #include #include #include #include #include #include #include "dm.h" /* some magic number */ #define DM_MAGIC 0x444D4653 static struct super_operations dm_ops; static struct address_space_operations dm_aops; static struct file_operations dm_dir_operations; static struct file_operations dm_file_operations; static struct inode_operations dm_dir_inode_operations; struct vfsmount *_mnt; static int _unlink(struct inode *dir, struct dentry *dentry); #define NOT_A_TABLE ((struct dm_table *) 1) /* * context for the line splitter and error function. */ struct line_c { unsigned int line_num; loff_t next_read; char data[MAX_TARGET_LINE]; struct file *in; struct file *out; }; static int is_identifier(const char *str, int len) { if (len > DM_NAME_LEN - 1) return 0; while(len--) { if (!isalnum(*str) && *str != '_') return 0; str++; } return 1; } /* * Grabs lines one at a time from the table file. */ int extract_line(struct text_region *line, void *private) { struct line_c *lc = (struct line_c *) private; struct text_region text; ssize_t n; loff_t off = lc->next_read; const char *read_begin; mm_segment_t fs; fs = get_fs(); set_fs(get_ds()); n = lc->in->f_op->read(lc->in, lc->data, sizeof (lc->data), &off); set_fs(fs); if (n <= 0) return 0; read_begin = text.b = lc->data; text.e = lc->data + n; if (!dm_get_line(&text, line)) return 0; lc->line_num++; lc->next_read += line->e - read_begin; return 1; } static struct file *open_error_file(struct file *table) { struct file *f; char *name, *buffer; int bufsize = PATH_MAX + 1; if (bufsize < PAGE_SIZE) bufsize = PAGE_SIZE; /* Get space to append ".err" */ buffer = (char *) kmalloc(bufsize + 4, GFP_KERNEL); if (!buffer) return 0; /* Get path name */ name = d_path(table->f_dentry, table->f_vfsmnt, buffer, bufsize); if (!name) { kfree(buffer); return 0; } /* Create error file */ strcat(name, ".err"); f = filp_open(name, O_WRONLY | O_TRUNC | O_CREAT, S_IRUGO); kfree(buffer); if (f) f->f_dentry->d_inode->u.generic_ip = NOT_A_TABLE; return f; } static void close_error_file(struct file *out) { fput(out); } static void parse_error(const char *message, void *private) { struct line_c *lc = (struct line_c *) private; char buffer[32]; #define emit(b, l) lc->out->f_op->write(lc->out, (b), (l), &lc->out->f_pos) emit(lc->in->f_dentry->d_name.name, lc->in->f_dentry->d_name.len); sprintf(buffer, "(%d): ", lc->line_num); emit(buffer, strlen(buffer)); emit(message, strlen(message)); emit("\n", 1); #undef emit } static int _release(struct inode *inode, struct file *f) { /* FIXME: we should lock the inode to prevent someone else opening it while we are parsing */ struct line_c *lc; struct dm_table *table = (struct dm_table *) inode->u.generic_ip; /* noop for files without tables (.err files) */ if (table == NOT_A_TABLE) return 0; /* only bother parsing if it was open for a write */ if (!(f->f_mode & S_IWUGO)) return 0; /* free off the old table */ if (table) { dm_table_destroy(table); inode->u.generic_ip = 0; } if (!(lc = kmalloc(sizeof (*lc), GFP_KERNEL))) return -ENOMEM; memset(lc, 0, sizeof (*lc)); lc->in = f; if (!(lc->out = open_error_file(lc->in))) return -ENOMEM; table = dm_parse(extract_line, lc, parse_error, lc); close_error_file(lc->out); kfree(lc); inode->u.generic_ip = table; return 0; } void _put_inode(struct inode *inode) { struct mapped_device *md = (struct mapped_device *) inode->u.generic_ip; struct dm_table *table = (struct dm_table *) inode->u.generic_ip; if (inode->i_mode & S_IFDIR) { if (md) dm_remove(md); } else { if (table) dm_table_destroy(table); } inode->u.generic_ip = 0; force_delete(inode); } static int _statfs(struct super_block *sb, struct statfs *buf) { buf->f_type = DM_MAGIC; buf->f_bsize = PAGE_CACHE_SIZE; buf->f_namelen = 255; return 0; } /* * Lookup the data. This is trivial - if the dentry didn't already * exist, we know it is negative. */ static struct dentry *_lookup(struct inode *dir, struct dentry *dentry) { d_add(dentry, NULL); return NULL; } /* * Read a page. Again trivial. If it didn't already exist * in the page cache, it is zero-filled. */ static int _readpage(struct file *file, struct page *page) { if (!Page_Uptodate(page)) { memset(kmap(page), 0, PAGE_CACHE_SIZE); kunmap(page); flush_dcache_page(page); SetPageUptodate(page); } UnlockPage(page); return 0; } /* * Writing: just make sure the page gets marked dirty, so that * the page stealer won't grab it. */ static int _writepage(struct page *page) { SetPageDirty(page); UnlockPage(page); return 0; } static int _prepare_write(struct file *file, struct page *page, unsigned offset, unsigned to) { void *addr = kmap(page); if (!Page_Uptodate(page)) { memset(addr, 0, PAGE_CACHE_SIZE); flush_dcache_page(page); SetPageUptodate(page); } SetPageDirty(page); return 0; } static int _commit_write(struct file *file, struct page *page, unsigned offset, unsigned to) { struct inode *inode = page->mapping->host; loff_t pos = ((loff_t) page->index << PAGE_CACHE_SHIFT) + to; kunmap(page); if (pos > inode->i_size) inode->i_size = pos; return 0; } struct inode *_get_inode(struct super_block *sb, int mode, int dev) { struct inode *inode = new_inode(sb); if (inode) { inode->i_mode = mode; inode->i_uid = current->fsuid; inode->i_gid = current->fsgid; inode->i_blksize = PAGE_CACHE_SIZE; inode->i_blocks = 0; inode->i_rdev = NODEV; inode->i_mapping->a_ops = &dm_aops; inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; switch (mode & S_IFMT) { case S_IFBLK: case S_IFCHR: init_special_inode(inode, mode, dev); break; case S_IFREG: inode->i_fop = &dm_file_operations; break; case S_IFDIR: inode->i_op = &dm_dir_inode_operations; inode->i_fop = &dm_dir_operations; break; case S_IFLNK: inode->i_op = &page_symlink_inode_operations; break; default: make_bad_inode(inode); } } return inode; } /* * File creation. Allocate an inode, and we're done.. */ static int _mknod(struct inode *dir, struct dentry *dentry, int mode) { int error = -ENOSPC; struct inode *inode = _get_inode(dir->i_sb, mode, 0); if (inode) { d_instantiate(dentry, inode); dget(dentry); /* Extra count - pin the dentry in core */ error = 0; } return error; } static int _mkdir(struct inode *dir, struct dentry *dentry, int mode) { int r; const char *name = (const char *) dentry->d_name.name; struct mapped_device *md; if (!is_identifier(name, dentry->d_name.len)) return -EPERM; /* or EINVAL ? */ md = dm_create(name, -1); if (IS_ERR(md)) return PTR_ERR(md); r = _mknod(dir, dentry, mode | S_IFDIR); if (r) { dm_remove(md); return r; } dentry->d_inode->u.generic_ip = md; md->inode = dentry->d_inode; return 0; } static int _rmdir(struct inode *dir, struct dentry *dentry) { int r = _unlink(dir, dentry); return r; } static int _create(struct inode *dir, struct dentry *dentry, int mode) { int r; if ((r = _mknod(dir, dentry, mode | S_IFREG))) return r; dentry->d_inode->u.generic_ip = 0; return 0; } static inline int positive(struct dentry *dentry) { return dentry->d_inode && !d_unhashed(dentry); } /* * Check that a directory is empty (this works * for regular files too, they'll just always be * considered empty..). * * Note that an empty directory can still have * children, they just all have to be negative.. */ static int _empty(struct dentry *dentry) { struct list_head *list; spin_lock(&dcache_lock); list = dentry->d_subdirs.next; while (list != &dentry->d_subdirs) { struct dentry *de = list_entry(list, struct dentry, d_child); if (positive(de)) { spin_unlock(&dcache_lock); return 0; } list = list->next; } spin_unlock(&dcache_lock); return 1; } /* * This works for both directories and regular files. * (non-directories will always have empty subdirs) */ static int _unlink(struct inode *dir, struct dentry *dentry) { int retval = -ENOTEMPTY; if (_empty(dentry)) { struct inode *inode = dentry->d_inode; inode->i_nlink--; dput(dentry); /* Undo the count from "create" - this does all the work */ retval = 0; } return retval; } /* * The VFS layer already does all the dentry stuff for rename, * we just have to decrement the usage count for the target if * it exists so that the VFS layer correctly free's it when it * gets overwritten. */ static int _rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) { struct inode *inode = new_dentry->d_inode; struct mapped_device *md = old_dir->u.generic_ip; struct dm_table *table = old_dentry->d_inode->u.generic_ip; if (!md || !table) return -EINVAL; if (!_empty(new_dentry)) return -ENOTEMPTY; if (!strcmp(new_dentry->d_name.name, "ACTIVE")) { /* activate the table */ dm_activate(md, table); } else if (!strcmp(old_dentry->d_name.name, "ACTIVE")) { dm_suspend(md); } if (inode) { inode->i_nlink--; dput(new_dentry); } return 0; } static int _sync_file(struct file *file, struct dentry *dentry, int datasync) { return 0; } static struct address_space_operations dm_aops = { readpage: _readpage, writepage: _writepage, prepare_write: _prepare_write, commit_write: _commit_write }; static struct file_operations dm_file_operations = { read: generic_file_read, write: generic_file_write, fsync: _sync_file, release: _release, }; static struct file_operations dm_dir_operations = { read: generic_read_dir, readdir: dcache_readdir, fsync: _sync_file, }; static struct inode_operations root_dir_inode_operations = { lookup: _lookup, mkdir: _mkdir, rmdir: _rmdir, rename: _rename, }; static struct inode_operations dm_dir_inode_operations = { create: _create, lookup: _lookup, unlink: _unlink, rename: _rename, }; static struct super_operations dm_ops = { statfs: _statfs, put_inode: _put_inode, }; static struct super_block *_read_super(struct super_block *sb, void *data, int silent) { struct inode *inode; struct dentry *root; sb->s_blocksize = PAGE_CACHE_SIZE; sb->s_blocksize_bits = PAGE_CACHE_SHIFT; sb->s_magic = DM_MAGIC; sb->s_op = &dm_ops; inode = _get_inode(sb, S_IFDIR | 0755, 0); inode->i_op = &root_dir_inode_operations; if (!inode) return NULL; root = d_alloc_root(inode); if (!root) { iput(inode); return NULL; } sb->s_root = root; return sb; } static DECLARE_FSTYPE(_fs_type, "dmfs", _read_super, FS_SINGLE); int __init dm_fs_init(void) { int r; if ((r = register_filesystem(&_fs_type))) return r; _mnt = kern_mount(&_fs_type); if (IS_ERR(_mnt)) { unregister_filesystem(&_fs_type); return PTR_ERR(_mnt); } return 0; } void __exit dm_fs_exit(void) { unregister_filesystem(&_fs_type); }