如果你是我的長期讀者,那么你應該知道我在尋找一個完美備份程序,最后我寫了一個基于bup的我自己的加密層。
在寫encbup的時候,我對僅僅恢復一個文件就必須要下載整個巨大的檔案文件的做法不甚滿意,但仍然希望能將EncFS和 rdiff-backup一起使用來實現可遠程掛載、加密、去重、版本化備份的功能。
再次試用obnam 后(啰嗦一句:它還是慢的出奇),我注意到了它有一個mount命令。深入研究后,我發現了fuse-python和fusepy,感覺用Python寫一個FUSE文件系統應該挺簡單的。
聰明的讀者可能已經意識到了我接下來要做的事情:我決定用Python寫一個加密文件系統層!它與EncFS會非常相似,但也有一些重要的區別:
- 它默認以反向模式運行,接收正常的文件并且暴露一個被加密的目錄。任何備份程序會發現(并且備份)這些加密的目錄,不需要任何其它的存儲。
- 它也能接受由一個目錄列表組成的配置文件,并且在掛載點將這些目錄暴露出來。這樣的話,所有的備份腳本就需要將掛載點備份,各種不同的目錄會立刻得以備份。
- 它會更偏重于備份,而不是加密存儲。寫起來應該會挺有意思的。
一個FUSE文件系統示例
寫這個腳本的第一步是寫出一個純粹的傳遞式的文件系統。它僅僅是接受一個目錄,并在掛載點將其暴露出來,確保任何在掛載點的修改都會鏡像到源數據中。
fusepy 要求你寫一個類,里面定義了各種操作系統級別的方法。你可以選擇定義那些你的文件系統想要支持的方法,其他的可以暫時不予定義,但是我需要定義全部的方法,因為我的文件系統是一個傳遞式的文件系統,它應該表現的與原有的文件系統盡可能一致。
寫這段代碼會非常簡單有趣,因為大部分的方法只是對os模塊的一些簡單封裝(確實,你可以直接給它們賦值,比如 open=os.open 等等,但是我的模塊需要一些路徑擴展)。不幸的是,fuse-python有一個bug(據我所知)是當打開和讀文件的時候,它無法將文件句柄回傳給文件系統。因而我的腳本不知道某個應用執行讀寫操作時對應的是哪個文件句柄,從而導致了失敗。只需要對fusepy做極少的改動,它就可以很好地運行。它只有一個文件,所以你可以把它直接放到你的工程里。
代碼
在這里,我很樂意給出這段代碼,當你打算自己實現文件系統的時候可以拿來參考。這段代碼提供了一個很好的起點,你可以直接把這個類復制到你的工程中并且根據需要重寫里面的一些方法。
接下來是真正的代碼了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
#!/usr/bin/env python from __future__ import with_statement import os import sys import errno from fuse import FUSE, FuseOSError, Operations class Passthrough(Operations): def __init__( self , root): self .root = root # Helpers # ======= def _full_path( self , partial): if partial.startswith( "/" ): partial = partial[ 1 :] path = os.path.join( self .root, partial) return path # Filesystem methods # ================== def access( self , path, mode): full_path = self ._full_path(path) if not os.access(full_path, mode): raise FuseOSError(errno.EACCES) def chmod( self , path, mode): full_path = self ._full_path(path) return os.chmod(full_path, mode) def chown( self , path, uid, gid): full_path = self ._full_path(path) return os.chown(full_path, uid, gid) def getattr ( self , path, fh = None ): full_path = self ._full_path(path) st = os.lstat(full_path) return dict ((key, getattr (st, key)) for key in ( 'st_atime' , 'st_ctime' , 'st_gid' , 'st_mode' , 'st_mtime' , 'st_nlink' , 'st_size' , 'st_uid' )) def readdir( self , path, fh): full_path = self ._full_path(path) dirents = [ '.' , '..' ] if os.path.isdir(full_path): dirents.extend(os.listdir(full_path)) for r in dirents: yield r def readlink( self , path): pathname = os.readlink( self ._full_path(path)) if pathname.startswith( "/" ): # Path name is absolute, sanitize it. return os.path.relpath(pathname, self .root) else : return pathname def mknod( self , path, mode, dev): return os.mknod( self ._full_path(path), mode, dev) def rmdir( self , path): full_path = self ._full_path(path) return os.rmdir(full_path) def mkdir( self , path, mode): return os.mkdir( self ._full_path(path), mode) def statfs( self , path): full_path = self ._full_path(path) stv = os.statvfs(full_path) return dict ((key, getattr (stv, key)) for key in ( 'f_bavail' , 'f_bfree' , 'f_blocks' , 'f_bsize' , 'f_favail' , 'f_ffree' , 'f_files' , 'f_flag' , 'f_frsize' , 'f_namemax' )) def unlink( self , path): return os.unlink( self ._full_path(path)) def symlink( self , target, name): return os.symlink( self ._full_path(target), self ._full_path(name)) def rename( self , old, new): return os.rename( self ._full_path(old), self ._full_path(new)) def link( self , target, name): return os.link( self ._full_path(target), self ._full_path(name)) def utimens( self , path, times = None ): return os.utime( self ._full_path(path), times) # File methods # ============ def open ( self , path, flags): full_path = self ._full_path(path) return os. open (full_path, flags) def create( self , path, mode, fi = None ): full_path = self ._full_path(path) return os. open (full_path, os.O_WRONLY | os.O_CREAT, mode) def read( self , path, length, offset, fh): os.lseek(fh, offset, os.SEEK_SET) return os.read(fh, length) def write( self , path, buf, offset, fh): os.lseek(fh, offset, os.SEEK_SET) return os.write(fh, buf) def truncate( self , path, length, fh = None ): full_path = self ._full_path(path) with open (full_path, 'r+' ) as f: f.truncate(length) def flush( self , path, fh): return os.fsync(fh) def release( self , path, fh): return os.close(fh) def fsync( self , path, fdatasync, fh): return self .flush(path, fh) def main(mountpoint, root): FUSE(Passthrough(root), mountpoint, foreground = True ) if __name__ = = '__main__' : main(sys.argv[ 2 ], sys.argv[ 1 ]) |
如果你想要運行它,只需要安裝fusepy,把這段代碼放進一個文件(比如myfuse.py)然后運行 python myfuse.py /你的目錄 /掛載點目錄 。你會發現 “/你的目錄” 路徑下的所有文件都跑到”/掛載點目錄”,還能像用原生文件系統一樣操作它們。
結語
總的來說,我并不認為寫一個文件系統就這么簡單。接下來要做的是在腳本里添加加密/解密的功能,以及一些幫助類的方法。我的目標是能讓它除了有更好的擴展性(因為是用Python寫的),以及包含一些針對備份文件的額外特性外,可以成為一個EncFS的完全替代品。
如果你想跟進這個腳本的開發過程,請在下面訂閱我的郵件列表,或者在Twitter上關注我。一如既往的歡迎反饋(在下面評論就很好)。