どうすればfuseを使えるのかを知りたかったので、サンプルコードを参考にあれこれしていましたが、ここらで一区切り。
- FUSEWiki - FusePython
- FUSEWiki - FUSE Python tutorial
- IBM dW : Linux : FUSEによる独自ファイルシステムの開発 - Japan
- FUSEWiki - FileSystems
- macfuse - Google Code
- SSH Filesystem
- rubyfs
- pyfs
"ローカルファイル"以外の何かを使って読み書きが出来ればFUSEのさわり位は体験出来るだろうという事で、お題を『memcachedへの入出力をFUSEを使って書いてみる』としてみました。
書いたコードでは、読み・書き・削除くらいしか出来ませんが、カーネルもファイルシステムもろくに知らない自分でも、ls,cat,vi,rmなどのコマンドが使える似非ファイルシステムが書けました。
FUSEを利用したファイルシステムを実装する作業は、システムコールAPIを実装する作業と言えると思います。今回利用したPythonバインディングでは、fuse.Fuseクラスを継承してopen,read,writeなどのシステムコールをメソッドとして実装しました。この時、当然ながらシステムコール関数が利用する構造体(statやfstat)の遷移(?)などを扱う必要があるので、各システムコールがどんな役割を持っていてどんな動作をするのかは知っている必要があります。この辺りは適当な知識しか無いので困りました。
今回は目標を"感覚を掴む事が出来ればよし"としていたので、この辺りでやめにしておきますが、やはりI/Oの基礎(になるのかな?)は勉強し直さないと駄目ですね。ある程度"なにを知りたいのか"がはっきりしているので、やり易い気がしています。
そんなこんななFUSE体験記でした。
memcachefs.py
以下、習作として書いたコードです。記録がてら残しますが、だいぶ駄目なコードなのでご注意ください。
./memcachefs.py <memcache_server>:<memcache_port> </to/mount/point>
#!/usr/bin/env python import sys import os import stat import time import errno import memcache import fuse class MemcacheStat(fuse.Stat): def __init__(self): self.st_mode = 0 self.st_ino = 0 self.st_dev = 0 self.st_nlink = 0 self.st_uid = 0 self.st_gid = 0 self.st_size = 0 self.st_atime = 0 self.st_mtime = 0 self.st_ctime = 0 class MemcacheFS(fuse.Fuse): def __init__(self, memcache, *args, **kw): fuse.Fuse.__init__(self, *args, **kw) self.__memcache = memcache self.__init_root() self.__keys = {} def __init_root(self): root_stat = os.stat(sys.argv[-1]) self.__root_stat = MemcacheStat() self.__root_stat.st_mode = root_stat[0] self.__root_stat.st_ino = root_stat[1] self.__root_stat.st_dev = root_stat[2] self.__root_stat.st_nlink = root_stat[3] self.__root_stat.st_uid = root_stat[4] self.__root_stat.st_gid = root_stat[5] self.__root_stat.st_size = root_stat[6] self.__root_stat.st_atime = root_stat[7] self.__root_stat.st_utime = root_stat[8] self.__root_stat.st_ctime = root_stat[9] def __get_key(self, path): return '/' + os.path.basename(path) def getattr(self, path): if path == '/': return self.__root_stat else: key = self.__get_key(path) if self.__keys.get(key, None): return self.__keys[key] else: return -errno.ENOENT def readdir(self, path, offset): for r in ['.', '..'] + [os.path.basename(k) for k in self.__keys]: yield fuse.Direntry(r) def open(self, path, flags): key = self.__get_key(path) if self.__memcache.get(key) == None: return -errno.ENOENT accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR if not (flags & accmode) in [os.O_RDONLY, os.O_WRONLY, os.O_RDWR]: return -errno.EACCES def read(self, path, size, offset): key = self.__get_key(path) d = self.__memcache.get(key) if d == None: return -errno.ENOENT else: self.__keys[key].st_atime = time.time() return d def write(self, path, buf, offset): key = self.__get_key(path) size = len(buf) if self.__memcache.set(key, buf): self.__keys[key].st_utime = time.time() self.__keys[key].st_size = size #self.__keys[key].st_ctime = time.time() return size else: return -error.EIO def getdir(self, path): return map(lambda x: (x, 0), path_list) def mythread ( self ): print '*** mythread' -errno.ENOSYS def chmod ( self, path, mode ): key = self.__get_key(path) if self.__keys.get(key, None): self.__keys[key].st_mode = stat.S_IFREG | mode #self.__keys[key].st_ctime = time.time() else: return -errno.ENOENT def chown ( self, path, uid, gid ): key = self.__get_key(path) if self.__keys.get(key, None): self.__keys[key].st_uid = uid self.__keys[key].st_gid = gid #self.__keys[key].st_ctime = time.time() else: return -errno.ENOENT def fsync ( self, path, isFsyncFile ): print '*** fsync', path, isFsyncFile -errno.ENOSYS def link ( self, targetPath, linkPath ): print '*** link', targetPath, linkPath -errno.ENOSYS def mkdir ( self, path, mode ): print '*** mkdir', path, oct(mode) -errno.ENOSYS def mknod ( self, path, mode, dev ): key = self.__get_key(path) self.__memcache.set(key, '') now = time.time() st = MemcacheStat() st.st_mode = stat.S_IFREG | mode st.st_dev = dev st.st_nlink = 1 st.st_size = len(self.__memcache.get(key)) st.st_uid = os.getuid() st.st_gid = os.getgid() st.st_utime = now #st.st_ctime = now self.__keys[key] = st def readlink ( self, path ): print '*** readlink', path -errno.ENOSYS def release ( self, path, flags ): print '*** release', path, flags -errno.ENOSYS def rename ( self, oldPath, newPath ): old_key = self.__get_key(oldPath) new_key = self.__get_key(newPath) d = self.__memcache.get(old_key) self.__memcache.set(new_key, d) self.__memcache.delete(old_key) self.__keys[new_key] = self.__keys[old_key] del(self.__keys[old_key]) def rmdir ( self, path ): print '*** rmdir', path -errno.ENOSYS def statfs ( self ): print '*** statfs' -errno.ENOSYS def symlink ( self, targetPath, linkPath ): print '*** symlink', targetPath, linkPath -errno.ENOSYS def truncate ( self, path, size ): print '*** truncate', path, size -errno.ENOSYS def unlink ( self, path ): key = self.__get_key(path) self.__memcache.delete(key) del(self.__keys[key]) def utime ( self, path, times ): key = self.__get_key(path) if self.__keys.get(key, None): self.__keys[key].st_atime = times[0] self.__keys[key].st_utime = times[1] def main(): usage=""" memcached client use Filesystem <prog> <host:port> <mount point> """ + fuse.Fuse.fusage if len(sys.argv) < 3: raise usage mc = memcache.Client([sys.argv[1]], debug=0) if not mc.get_stats(): raise 'memcached error: could not connect to ' + sys.argv[1] server = MemcacheFS(version="%prog " + fuse.__version__, usage=usage, dash_s_do='setsingle', memcache=mc) server.parse(errex=1) server.main() if __name__ == '__main__': main()

