博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
用 Python 写一个 FUSE(用户态文件系统)文件系统
阅读量:5937 次
发布时间:2019-06-19

本文共 4440 字,大约阅读时间需要 14 分钟。

如果你是我的长期读者,那么你应该知道我在,最后我写了一个基于的我自己的加密层。

在写的时候,我对仅仅恢复一个文件就必须要下载整个巨大的档案文件的做法不甚满意,但仍然希望能将和 一起使用来实现可远程挂载、加密、去重、版本化备份的功能。

再次试用 后(啰嗦一句:它还是慢的出奇),我注意到了它有一个mount命令。深入研究后,我发现了和,感觉用Python写一个FUSE文件系统应该挺简单的。

聪明的读者可能已经意识到了我接下来要做的事情:我决定用Python写一个加密文件系统层!它与EncFS会非常相似,但也有一些重要的区别:

  • 它默认以反向模式运行,接收正常的文件并且暴露一个被加密的目录。任何备份程序会发现(并且备份)这些加密的目录,不需要任何其它的存储。
  • 它也能接受由一个目录列表组成的配置文件,并且在挂载点将这些目录暴露出来。这样的话,所有的备份脚本就需要将挂载点备份,各种不同的目录会立刻得以备份。
  • 它会更偏重于备份,而不是加密存储。写起来应该会挺有意思的。

一个FUSE文件系统示例

写这个脚本的第一步是写出一个纯粹的传递式的文件系统。它仅仅是接受一个目录,并在挂载点将其暴露出来,确保任何在挂载点的修改都会镜像到源数据中。

fusepy 要求你写一个类,里面定义了各种操作系统级别的方法。你可以选择定义那些你的文件系统想要支持的方法,其他的可以暂时不予定义,但是我需要定义全部的方法,因为我的文件系统是一个传递式的文件系统,它应该表现的与原有的文件系统尽可能一致。

写这段代码会非常简单有趣,因为大部分的方法只是对os模块的一些简单封装(确实,你可以直接给它们赋值,比如 open=os.open 等等,但是我的模块需要一些路径扩展)。不幸的是,fuse-python有一个bug(据我所知)是当打开和读文件的时候,它无法将文件句柄回传给文件系统。因而我的脚本不知道某个应用执行读写操作时对应的是哪个文件句柄,从而导致了失败。只需要对fusepy做极少的改动,它就可以很好地运行。它只有一个文件,所以你可以把它直接放到你的工程里。

代码

在这里,我很乐意给出这段代码,当你打算自己实现文件系统的时候可以拿来参考。这段代码提供了一个很好的起点,你可以直接把这个类复制到你的工程中并且根据需要重写里面的一些方法。

接下来是真正的代码了:

 
  1. #!/usr/bin/env python
  2. from __future__ import with_statement
  3. import os
  4. import sys
  5. import errno
  6. from fuse import FUSE, FuseOSError, Operations
  7. class Passthrough(Operations):
  8. def __init__(self, root):
  9. self.root = root
  10. # Helpers
  11. # =======
  12. def _full_path(self, partial):
  13. if partial.startswith("/"):
  14. partial = partial[1:]
  15. path = os.path.join(self.root, partial)
  16. return path
  17. # Filesystem methods
  18. # ==================
  19. def access(self, path, mode):
  20. full_path = self._full_path(path)
  21. if not os.access(full_path, mode):
  22. raise FuseOSError(errno.EACCES)
  23. def chmod(self, path, mode):
  24. full_path = self._full_path(path)
  25. return os.chmod(full_path, mode)
  26. def chown(self, path, uid, gid):
  27. full_path = self._full_path(path)
  28. return os.chown(full_path, uid, gid)
  29. def getattr(self, path, fh=None):
  30. full_path = self._full_path(path)
  31. st = os.lstat(full_path)
  32. return dict((key, getattr(st, key)) for key in ('st_atime', 'st_ctime',
  33. 'st_gid', 'st_mode', 'st_mtime', 'st_nlink', 'st_size', 'st_uid'))
  34. def readdir(self, path, fh):
  35. full_path = self._full_path(path)
  36. dirents = ['.', '..']
  37. if os.path.isdir(full_path):
  38. dirents.extend(os.listdir(full_path))
  39. for r in dirents:
  40. yield r
  41. def readlink(self, path):
  42. pathname = os.readlink(self._full_path(path))
  43. if pathname.startswith("/"):
  44. # Path name is absolute, sanitize it.
  45. return os.path.relpath(pathname, self.root)
  46. else:
  47. return pathname
  48. def mknod(self, path, mode, dev):
  49. return os.mknod(self._full_path(path), mode, dev)
  50. def rmdir(self, path):
  51. full_path = self._full_path(path)
  52. return os.rmdir(full_path)
  53. def mkdir(self, path, mode):
  54. return os.mkdir(self._full_path(path), mode)
  55. def statfs(self, path):
  56. full_path = self._full_path(path)
  57. stv = os.statvfs(full_path)
  58. return dict((key, getattr(stv, key)) for key in ('f_bavail', 'f_bfree',
  59. 'f_blocks', 'f_bsize', 'f_favail', 'f_ffree', 'f_files', 'f_flag',
  60. 'f_frsize', 'f_namemax'))
  61. def unlink(self, path):
  62. return os.unlink(self._full_path(path))
  63. def symlink(self, target, name):
  64. return os.symlink(self._full_path(target), self._full_path(name))
  65. def rename(self, old, new):
  66. return os.rename(self._full_path(old), self._full_path(new))
  67. def link(self, target, name):
  68. return os.link(self._full_path(target), self._full_path(name))
  69. def utimens(self, path, times=None):
  70. return os.utime(self._full_path(path), times)
  71. # File methods
  72. # ============
  73. def open(self, path, flags):
  74. full_path = self._full_path(path)
  75. return os.open(full_path, flags)
  76. def create(self, path, mode, fi=None):
  77. full_path = self._full_path(path)
  78. return os.open(full_path, os.O_WRONLY | os.O_CREAT, mode)
  79. def read(self, path, length, offset, fh):
  80. os.lseek(fh, offset, os.SEEK_SET)
  81. return os.read(fh, length)
  82. def write(self, path, buf, offset, fh):
  83. os.lseek(fh, offset, os.SEEK_SET)
  84. return os.write(fh, buf)
  85. def truncate(self, path, length, fh=None):
  86. full_path = self._full_path(path)
  87. with open(full_path, 'r+') as f:
  88. f.truncate(length)
  89. def flush(self, path, fh):
  90. return os.fsync(fh)
  91. def release(self, path, fh):
  92. return os.close(fh)
  93. def fsync(self, path, fdatasync, fh):
  94. return self.flush(path, fh)
  95. def main(mountpoint, root):
  96. FUSE(Passthrough(root), mountpoint, foreground=True)
  97. if __name__ == '__main__':
  98. main(sys.argv[2], sys.argv[1])

如果你想要运行它,只需要安装fusepy,把这段代码放进一个文件(比如myfuse.py)然后运行 python myfuse.py /你的目录 /挂载点目录 。你会发现 “/你的目录” 路径下的所有文件都跑到”/挂载点目录”,还能像用原生文件系统一样操作它们。

结语

总的来说,我并不认为写一个文件系统就这么简单。接下来要做的是在脚本里添加加密/解密的功能,以及一些帮助类的方法。我的目标是能让它除了有更好的扩展性(因为是用Python写的),以及包含一些针对备份文件的额外特性外,可以成为一个EncFS的完全替代品。

原文发布时间为:2013-12-03

本文来自云栖社区合作伙伴“Linux中国”

转载地址:http://cyxtx.baihongyu.com/

你可能感兴趣的文章
关于ListView的一些不常用到的属性
查看>>
php 对象数组互转
查看>>
文本超过长度后隐藏,显示省略号
查看>>
netstat常见参数
查看>>
wpf Loading动画 AkeemLoading
查看>>
Ubuntu 里面 apt-get 三个有关更新的命令的区别
查看>>
POJ 1019, Number Sequence
查看>>
activiti插件安装-离线安装
查看>>
[译]准备 2017 前端面试
查看>>
RecyclerView的刷新分页
查看>>
MySQL——循环(双重循环)
查看>>
Html5学习笔记---1
查看>>
无聊的时候就看看
查看>>
UItableview section和cell的局部刷新
查看>>
字符串连接的效率问题
查看>>
紫书 例题 11-12 UVa 1515 (最大流最小割)
查看>>
紫书 习题 11-17 UVa 1670 (图论构造)
查看>>
洛谷P1108 低价购买 (最长下降子序列方案数)(int,long long等 范围)
查看>>
大道至简-第五章-心得体会
查看>>
Python编程从入门到实践,个人笔记
查看>>