Skip to content

Instantly share code, notes, and snippets.

@moriyoshi
Created April 27, 2009 06:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save moriyoshi/102366 to your computer and use it in GitHub Desktop.
Save moriyoshi/102366 to your computer and use it in GitHub Desktop.
ifneq ($(KERNELRELEASE),)
obj-m := my_vfs.o
my_vfs-objs := my_vfs_module.o
else
KVER = $(shell uname -r)
KDIR = /lib/modules/$(KVER)/build
modules::
$(MAKE) -C $(KDIR) M=$(shell pwd) modules
clean::
$(MAKE) -C $(KDIR) M=$(shell pwd) clean
endif
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/dcache.h>
#include <linux/string.h>
#include <linux/pagemap.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <asm/uaccess.h> /* for copy_to_user */
struct my_vfs_inode {
struct inode inode;
struct timer_list timer;
wait_queue_head_t wait;
atomic_t flagged;
};
static struct inode *my_vfs_get_inode(struct super_block *sb, unsigned int ino);
/**
* でっち上げたディレクトリエントリ
*/
static const char *dir_entries[] = {
"a",
"b",
"c",
"d",
"e",
};
/**
* タイマーの時間間隔 (数値は適当)
*/
static const unsigned long timer_interval = 400ul;
/**
* struct my_vfs_inode 用スラブアロケータ
*/
static struct kmem_cache *my_vfs_inode_cache __read_mostly;
/**
* このモジュールで内部的に使うだけの関数。
* dir_entries に、name で指定されたファイルがあるかどうかを調べて、
* 見つかった場合は inode 番号を返す。
*
* @param name 探すファイル名
* @param name_sz 探すファイル名の長さ
* @return inode 番号
*/
static unsigned long my_vfs_internal_lookup(const char *name, size_t name_sz)
{
int i;
for (i = 0; i < sizeof(dir_entries) / sizeof(*dir_entries); i++) {
if (0 == strncmp(dir_entries[i], name, name_sz)) {
/* とりあえず文字列へのポインタ == inode 番号
* ということにしてしまう */
return (unsigned long)dir_entries[i];
}
}
return 0;
}
/**
* ディレクトリノード用のコールバック関数。read(2) / pread(2) に対応する処理。
* @file: ディレクトリのディスクリプタ
* @buf: 読み込み先であるユーザ空間のバッファへのポインタ
* @buf_sz: バッファのサイズ
* @pos: ファイルポインタ
*/
static ssize_t my_vfs_dir_read(struct file *file, char __user *buf, size_t buf_sz, loff_t *pos)
{
/* ディレクトリに対する read 操作は無効 */
return -EISDIR;
}
/**
* ディレクトリノード用のコールバック関数。readdir(3) に対応する処理。
* @file: ディレクトリのディスクリプタ
* @dirent: ディレクトリエントリを格納するバッファへのポインタ
* @filldir: 指定されたバッファにデータを追加するために呼び出し側から
* 提供される実装のコールバック関数ポインタ
*/
static int my_vfs_dir_readdir(struct file *file, void *dirent, filldir_t filldir)
{
unsigned int i = file->f_pos;
while (i < 2 + sizeof(dir_entries) / sizeof(*dir_entries)) {
if (i == 0) {
/* "." の処理 */
if (filldir(dirent, ".", sizeof(".") - 1, i, 1, DT_DIR))
break;
} else if (i == 1) {
/* ".." の処理 */
if (filldir(dirent, "..", sizeof("..") - 1, i,
parent_ino(file->f_path.dentry), DT_DIR))
break;
} else {
/* それ以外 */
if (filldir(dirent, dir_entries[i - 2], strlen(dir_entries[i - 2]),
i, (unsigned long)dir_entries[i - 2], DT_REG))
break;
}
++i;
}
file->f_pos = i;
return 0;
}
/**
* ディレクトリノード用のコールバック関数。lseek(2) に対応する処理。
* @file: ディレクトリのディスクリプタ
* @off: オフセット
* @origin: SEEK_SET / SEEK_CUR / SEEK_END のいずれか
*/
static loff_t my_vfs_dir_lseek(struct file *file, loff_t off, int origin)
{
loff_t new_off = file->f_pos;
switch (origin) {
case SEEK_SET:
new_off = off;
break;
case SEEK_CUR:
new_off += off;
break;
default:
return -EINVAL;
}
if (new_off > sizeof(dir_entries) / sizeof(*dir_entries))
file->f_pos = sizeof(dir_entries) / sizeof(*dir_entries);
file->f_pos = new_off;
return new_off;
}
/**
* ディレクトリノード用のコールバック関数。open(2) か creat(2) に対応する処理。
* @inode: ディレクトリのノードを表す struct inode へのポインタ
* @dentry: 作成されるファイルを表す struct dentry へのポインタ
* @mode: ファイルモード
* @nd: [out] 解決されたパス名?
*/
static int my_vfs_dir_create(struct inode *inode, struct dentry *entry, int mode, struct nameidata *nd)
{
return -ENOSPC;
}
/**
* ディレクトリノード用のコールバック関数。stat(2) に対応する処理。
* @mnt: マウントポイントを表す struct vfsmount へのポインタ
* @dentry: 対象のファイルを表す struct dentry へのポインタ
* @mode: ファイルの属性を返す struct kstat へのポインタ
*/
static int my_vfs_dir_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat)
{
generic_fillattr(dentry->d_inode, stat);
return 0;
}
/**
* dentry 用のコールバック関数。
* @dentry: 対象の dentry を表すポインタ。
*/
static int my_vfs_dir_dentry_delete(struct dentry *dentry)
{
return 1;
}
/* XXX: why not const'ified? */
static struct dentry_operations my_vfs_dir_dentry_operations = {
.d_delete = my_vfs_dir_dentry_delete
};
/**
* ディレクトリノード用のコールバック関数。ディレクトリから指定された
* エントリを探す。
* @inode: 検索対象のディレクトリノードを表す struct inode へのポインタ
* @entry: 検索するエントリの名前
* @nd: 何の操作で呼ばれたかが intent に入ってたりとか
*/
static struct dentry *my_vfs_dir_lookup(struct inode *inode, struct dentry *entry, struct nameidata *nd)
{
(void)nd; /* unused */
if (entry->d_name.len > NAME_MAX)
return ERR_PTR(-ENAMETOOLONG);
entry->d_op = &my_vfs_dir_dentry_operations;
{
unsigned long file_inode_n;
struct inode *file_inode;
file_inode_n = my_vfs_internal_lookup(entry->d_name.name,
entry->d_name.len);
if (!file_inode_n) {
d_add(entry, NULL);
return NULL;
}
file_inode = my_vfs_get_inode(inode->i_sb, file_inode_n);
if (!file_inode)
return ERR_PTR(-ENOENT);
d_add(entry, file_inode);
}
return NULL;
}
/**
* ディレクトリノード用のコールバック関数。シンボリックリンクを作成する。
* @inode: 作成対象のディレクトリノードを表す struct inode へのポインタ
* @entry: 作成するシンボリックリンクの名前
* @name: リンク先
*/
static int my_vfs_dir_symlink(struct inode *inode, struct dentry *entry, const char *name)
{
if (!my_vfs_internal_lookup(entry->d_name.name, entry->d_name.len))
return -ENOENT;
return -ENOSPC;
}
/**
* ディレクトリノード用のコールバック関数。新しいノードを作成する
* @inode: 作成対象のディレクトリノードを表す struct inode へのポインタ
* @entry: 作成するノードの名前
* @mode: ファイルモード
* @dev: デバイス番号
*/
static int my_vfs_dir_mknod(struct inode *inode, struct dentry *entry, int mode, dev_t dev)
{
return -ENOSPC;
}
/**
* ディレクトリノード用のコールバック関数。新しいディレクトリノードを作成する。
* @inode: 作成対象のディレクトリノードを表す struct inode へのポインタ
* @entry: 作成するディレクトリの名前
* @mode: ファイルモード
*/
static int my_vfs_dir_mkdir(struct inode *inode, struct dentry *entry, int mode)
{
return -ENOSPC;
}
/**
* ディレクトリノード用のコールバック関数。ディレクトリノードを削除する。
* @inode: 削除するディレクトリが含まれるディレクトリノードを表す
* struct inode へのポインタ
* @entry: 削除するディレクトリの名前
*/
static int my_vfs_dir_rmdir(struct inode *inode, struct dentry *entry)
{
return -EACCES;
}
/**
* ディレクトリノード用のコールバック関数。ハードリンクを作成する
* @old_entry: リンク元のするファイルを表す struct dentry へのポインタ
* @inode: ハードリンクを作成するディレクトリノードを表す
* struct inode へのポインタ
* @entry: ハードリンク名
*/
static int my_vfs_dir_link(struct dentry *old_entry, struct inode *inode, struct dentry *entry)
{
return -ENOSPC;
}
/**
* ディレクトリノード用のコールバック関数。ノードをアンリンクする。
* @inode: アンリンクを作成するエントリを含むディレクトリノードを表す
* struct inode へのポインタ
* @entry: アンリンクするファイルを表す struct dentry へのポインタ
*/
static int my_vfs_dir_unlink(struct inode *inode, struct dentry *entry)
{
return my_vfs_internal_lookup(entry->d_name.name, entry->d_name.len) ?
-EACCES: -ENOENT;
}
/**
* ディレクトリノード用のコールバック関数。エントリを移動する。
* @old_inode: 移動元のエントリを含むディレクトリノードを表す
* struct inode へのポインタ
* @old_entry: 移動元のエントリを表す struct dentry へのポインタ
* @inode: 移動先のエントリを含むディレクトリノードを表す
* struct inode へのポインタ
* @entry: 移動先のエントリを表す struct dentry へのポインタ
*/
static int my_vfs_dir_rename(struct inode *old_inode, struct dentry *old_entry, struct inode *new_inode, struct dentry *new_entry)
{
if (!my_vfs_internal_lookup(old_entry->d_name.name, old_entry->d_name.len))
return -ENOENT;
if (my_vfs_internal_lookup(new_entry->d_name.name, new_entry->d_name.len))
return -EEXIST;
return -EACCES;
}
/**
* ディレクトリノード用のコールバック関数。close(2) の後、
* ディスクリプタの参照カウントが 0 になると呼ばれる。
* @inode: ディレクトリノードを表す struct inode へのポインタ
* @file: ディレクトリのディスクリプタ
*/
static int my_vfs_dir_release(struct inode *inode, struct file *file)
{
return 0;
}
/**
* ディレクトリノード用のコールバック関数。
* ディレクトリのディスクリプタを開く
* @inode: ディレクトリノードを表す struct inode へのポインタ
* @file: ディレクトリのディスクリプタ
*/
static int my_vfs_dir_open(struct inode *inode, struct file *file)
{
return 0;
}
static const struct file_operations my_vfs_dir_ops = {
.llseek = my_vfs_dir_lseek,
.read = my_vfs_dir_read,
.readdir = my_vfs_dir_readdir,
.release = my_vfs_dir_release,
.open = my_vfs_dir_open
};
static const struct inode_operations my_vfs_dir_inode_ops = {
.create = my_vfs_dir_create,
.getattr = my_vfs_dir_getattr,
.lookup = my_vfs_dir_lookup,
.mknod = my_vfs_dir_mknod,
.link = my_vfs_dir_link,
.unlink = my_vfs_dir_unlink,
.symlink = my_vfs_dir_symlink,
.rename = my_vfs_dir_rename,
.mkdir = my_vfs_dir_mkdir,
.rmdir = my_vfs_dir_rmdir
};
/** ファイルの内容を模した文字列 */
static const char str[] = "quick brown fox jumps over the lazy dog";
/**
* ファイルノード用のコールバック関数。read(2) / pread(2) に対応する処理。
* @file: ファイルのディスクリプタ
* @buf: 読み込み先であるユーザ空間のバッファへのポインタ
* @buf_sz: バッファのサイズ
* @pos: ファイルポインタ
*/
static ssize_t my_vfs_file_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
if (*ppos >= sizeof(str) - 1)
return 0;
{
const ssize_t nbytes = ((loff_t)(sizeof(str) - 1) < *ppos + count ?
sizeof(str) - 1: *ppos + count) - *ppos;
if (copy_to_user(buf, str + *ppos, nbytes))
return -EFAULT;
*ppos += nbytes;
return nbytes;
}
}
/**
* ファイルノード用のコールバック関数。lseek(2) に対応する処理。
* @file: ファイルのディスクリプタ
* @off: オフセット
* @origin: SEEK_SET / SEEK_CUR / SEEK_END のいずれか
*/
static loff_t my_vfs_file_lseek(struct file *file, loff_t off, int origin)
{
loff_t new_off = file->f_pos;
switch (origin) {
case SEEK_SET:
new_off = off;
break;
case SEEK_CUR:
new_off += off;
break;
default:
return -EINVAL;
}
if (new_off > sizeof(str) - 1) {
file->f_pos = sizeof(str) - 1;
}
file->f_pos = new_off;
return new_off;
}
/**
* ファイルノード用のコールバック関数。close(2) の後、
* ディスクリプタの参照カウントが 0 になると呼ばれる。
* @file: ファイルのディスクリプタ
* @off: オフセット
* @origin: SEEK_SET / SEEK_CUR / SEEK_END のいずれか
*/
static int my_vfs_file_release(struct inode *inode, struct file *file)
{
return 0;
}
/**
* ファイルノード用のコールバック関数。poll(2) / select(2) に対応する処理
* @file: ファイルのディスクリプタ
* @pts: もしイベントが発生していなかったときに通知する先の
* 待ち行列を指定する struct poll_table_struct
*/
static unsigned int my_vfs_file_poll(struct file *file, struct poll_table_struct *pts)
{
struct my_vfs_inode *inode = file->private_data;
BUG_ON(!inode);
/* まずは呼出側のwait_queueを登録 */
poll_wait(file, &inode->wait, pts);
/* もしフラグがすでに立っていたら、リセットして POLLIN を返す */
if (atomic_add_unless(&inode->flagged, -1, 0))
return POLLIN;
else
return 0;
}
/**
* ファイルノード用のコールバック関数。open(2) に対応する処理 (一部)。
* @inode: ディレクトリのノードを表す struct inode へのポインタ
* @file: 開かれるファイルのディスクリプタ
*/
static int my_vfs_file_open(struct inode *inode, struct file *file)
{
file->private_data = inode;
return 0;
}
static const struct file_operations my_vfs_file_ops = {
.read = my_vfs_file_read,
.llseek = my_vfs_file_lseek,
.release = my_vfs_file_release,
.poll = my_vfs_file_poll,
.open = my_vfs_file_open
};
static const struct inode_operations my_vfs_file_inode_ops = {};
/**
* inode を inode 番号から引く。
* @sb: マウントされている実体を表す struct super_block へのポインタ
* @ino: inode 番号
*/
static struct inode *my_vfs_get_inode(struct super_block *sb, unsigned int ino)
{
struct inode *retval;
retval = iget_locked(sb, ino);
if (!retval)
return 0;
retval->i_uid = 0;
retval->i_gid = 0;
retval->i_mode = 0644;
if (ino == 1) {
retval->i_fop = &my_vfs_dir_ops;
retval->i_op = &my_vfs_dir_inode_ops;
retval->i_mode |= 0111 | S_IFDIR;
inc_nlink(retval);
} else {
retval->i_fop = &my_vfs_file_ops;
retval->i_op = &my_vfs_file_inode_ops;
retval->i_mode |= S_IFREG;
retval->i_size = sizeof(str) - 1;
}
unlock_new_inode(retval);
return retval;
}
/**
* ルートディレクトリノードを返す。
* @sb: マウントされている実体を表す struct super_block へのポインタ
*/
static struct inode *my_vfs_get_root(struct super_block *sb)
{
return my_vfs_get_inode(sb, 1);
}
/**
* アンマウントされるときに呼ばれるコールバック関数
* @sb: マウントされている実体を表す struct super_block へのポインタ
*/
static void my_vfs_put_super(struct super_block *sb)
{
/* do nothing for now */
}
/**
* タイマー用のコールバック。
*/
static void my_vfs_inode_timer_cb(unsigned long inode_ptr)
{
struct my_vfs_inode *inode = (struct my_vfs_inode *)inode_ptr;
/* フラグを立てる */
if (atomic_add_unless(&inode->flagged, 1, 1)) {
/* pollしてる奴を全部起こす */
wake_up_interruptible_all(&inode->wait);
}
/* タイマーを再度セットする */
del_timer(&inode->timer);
inode->timer.expires = jiffies + timer_interval;
add_timer(&inode->timer);
}
/**
* inode 構造体用のメモリを確保するときに呼ばれる
* @sb: マウントされている実体を表す struct super_block へのポインタ
*/
static struct inode *my_vfs_inode_alloc(struct super_block *sb)
{
struct my_vfs_inode *inode;
/* 自分用のスラブアロケータから確保する */
inode = kmem_cache_alloc(my_vfs_inode_cache, GFP_KERNEL);
if (!inode)
return NULL;
/* 本来はここで初期化すべきなのだろうか... */
/* 待ち行列を初期化 */
init_waitqueue_head(&inode->wait);
/* タイマーを初期化 */
setup_timer(&inode->timer, my_vfs_inode_timer_cb, (unsigned long)inode);
inode->timer.expires = jiffies + timer_interval;
add_timer(&inode->timer);
atomic_set(&inode->flagged, 0);
return (struct inode *)inode;;
}
/**
* inode 構造体用のメモリを解放するときに呼ばれる
* @sb: マウントされている実体を表す struct super_block へのポインタ
*/
static void my_vfs_inode_destroy(struct inode *_inode)
{
struct my_vfs_inode *inode = (struct my_vfs_inode *)_inode;
del_timer_sync(&inode->timer);
kmem_cache_free(my_vfs_inode_cache, (inode));
}
static const struct super_operations my_vfs_ops = {
.alloc_inode = my_vfs_inode_alloc,
.destroy_inode = my_vfs_inode_destroy,
.statfs = simple_statfs,
.put_super = my_vfs_put_super
};
/**
* inode 構造体用のスラブアロケータが新たにメモリを確保したときに
* 呼ばれるコールバック
* @cache: アロケータを表すポインタ
* @data: 確保されたメモリ (sizeof(struct my_vfs_inode))
*/
static void my_vfs_inode_cache_init_once(struct kmem_cache *cache, void *data)
{
inode_init_once(&((struct my_vfs_inode *)data)->inode);
}
/**
* マウントされるときに呼ばれるコールバック関数。
* マウントされる実体 (スーパーブロック) を表す struct super_block を
* 初期化する。
* @sb: マウントされる実体を表す struct super_block へのポインタ
* @data: マウントパラメータ
* @silent: エラーメッセージを表示するか否か
*/
static int my_vfs_fill_super(struct super_block *sb, void *data, int silent)
{
sb->s_maxbytes = MAX_LFS_FILESIZE; sb->s_blocksize = PAGE_CACHE_SIZE;
sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
sb->s_magic = 0x484f4745; /* 'HOGE' */
sb->s_op = &my_vfs_ops;
/* sb->s_flags |= MS_RDONLY; */
{
struct inode *inode = my_vfs_get_root(sb); struct dentry *root = 0;
if (!inode)
return -ENOMEM;
root = d_alloc_root(inode);
if (!root) {
iput(inode);
return -ENOMEM;
}
sb->s_root = root;
}
return 0;
}
/**
* マウントされるときに呼ばれるコールバック関数。
* マウントポイントとマウントされる実体 (スーパーブロック) を関連付ける。
* @fs: ファイルシステム
* @flags: マウントフラグ
* @dev_name: マウントされるデバイスの名前
* @data: マウントオプション
* @mnt: マウントポイントを表す struct vfsmount へのポインタ
*/
static int my_vfs_get_sb(struct file_system_type *fs,
int flags, const char *dev_name, void *data,
struct vfsmount *mnt)
{
return get_sb_nodev(fs, flags, data, my_vfs_fill_super, mnt);
}
static struct file_system_type my_vfs_type = {
.name = "my_vfs",
.get_sb = my_vfs_get_sb,
.kill_sb = kill_litter_super,
.owner = THIS_MODULE
};
static int __init my_vfs_module_init(void)
{
/* スラブアロケータを確保する */
my_vfs_inode_cache = kmem_cache_create(
"my_vfs_inode_cache",
sizeof(struct my_vfs_inode),
0,
SLAB_HWCACHE_ALIGN | SLAB_RECLAIM_ACCOUNT
| SLAB_MEM_SPREAD,
my_vfs_inode_cache_init_once);
if (!my_vfs_inode_cache)
return -ENOMEM;
/* ファイルシステムを登録する */
register_filesystem(&my_vfs_type);
return 0;
}
static void __exit my_vfs_module_exit(void)
{
/* ファイルシステムの登録を解除する */
unregister_filesystem(&my_vfs_type);
/* スラブアロケータを削除する */
kmem_cache_destroy(my_vfs_inode_cache);
}
module_init(my_vfs_module_init)
module_exit(my_vfs_module_exit)
MODULE_LICENSE("GPL");
/*
* vim: sw=8 sts=8 ts=8 noet
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment