go-fuse unionfs
网络上的反向代理我们已经非常清楚了。在应用服务器之前架设一个反向代理的服务器。通过访问反向代理,我们可以添加了一些http header,把http转换成https,诸如此类的。
在文件系统上也可以做这样的事情吗?我们能不能提供一个文件目录,是另外一个目录的反向代理。在新目录上实现文件的增减,以及文件内容的变换?
答案是可行的。这个实现基于 hanwen/go-fuse 。它提供一个 golang 实现的用户态文件系统(FUSE)。利用其中的 unionfs,我们可以得到以下的行为。
$unionfs
Usage:
unionfs MOUNTPOINT RW-DIRECTORY RO-DIRECTORY ...
其中 MOUNTPOINT 是我们最终访问的目录,RO目录是提供了原始文件的目录,而RW目录以COW(Copy-on-Write)的方式记录了我们在RO目录上的改动。
假设我们把ro目录和rw目录合并到了fuse目录
$unionfs fuse rw ro
- RW/RO 内容合并:ro 下的文件为 [a]。rw 下的文件为 [b]。那么 fuse 下的文件为 [a,b]
- RW/RO 文件同名:ro 下的文件为 [a]。rw 下的文件也为 [a]。那么 fuse 下的文件为 [a],其中 a 的内容是 rw 的。
- 在 fuse 中新增:ro 下的文件为 []。rw 下的文件为 []。如果给 fuse 新增文件a,从 [] 变为 [a]。那么结果是 ro 下的文件为 [],而 rw 变为了 [a]。
- 在 fuse 中更新:如果 a 同时存在于 ro, rw 中,更新 fuse 的 a,则等同于更新 rw 的。如果 a 只存在于 rw 中,更新 fuse 的 a,则等于更新 rw 的。如果 a 只存在于 ro 中,更新 fuse 的 a 不会更新 ro 的,而是会在 rw 中新增一个文件 a。
- 在 fuse 中删除:如果 a 同时存在与 ro,rw 中,删除 fuse 中的 a,不仅仅是从 rw 中把 a 删除了,同时会在 rw/GOUNIONFS_DELETIONS/ 目录下记录 a 已经被删除了。如果 a 只存在于 rw 中,则仅仅是从 rw 里把 a 删除。如果 a 只存在于 ro 中,则只会在 rw/GOUNIONFS_DELETIONS/ 目录下记录 a 已经被删除了。
- 在 ro 中新增:新增的文件立即可以在 fuse 中可见
- 在 ro 中更新:如果没有被 rw 覆盖的话,更新也可以在 fuse 中可见
- 在 ro 中删除:如果没有被 rw 覆盖的话,删除之后在 fuse 中也看不到了
这个和 overlayfs 的不同在于 fuse 合并了 rw 和 ro 之后,ro 的改动对于 fuse 仍然是可见的。
Changes to underlying filesystems --------------------------------- Offline changes, when the overlay is not mounted, are allowed to either the upper or the lower trees. Changes to the underlying filesystems while part of a mounted overlay filesystem are not allowed. If the underlying filesystem is changed, the behavior of the overlay is undefined, though it will not result in a crash or deadlock.
似乎 unionfs 要比 overlayfs 要宽松很多。
lambdafs
基于 go-fuse 的 unionfs,我们可以实现一个 lambdafs。其原理就是对 ro 里的部分文件进行 f() 的映射,把结果写入到 rw 目录里。从而使得最终的 fuse 目录里,对 ro 的部分文件进行了一个 lambda 的转换。而且这个转换是 lazy 的,而且是可更新的。只有当我们 cat 了 fuse 里的文件,这个时候才会触发 lambda。对于那些没有读到的文件,则可以先不处理。
我们要应用的 lambda 是这样的
UpdateFile: func(filePath string) ([]byte, error) {
if !strings.HasSuffix(filePath, ".php"){
return nil, nil
}
content, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, err
}
content = append(content, []byte("\nhello\n")...)
return content, nil
}
对于 .php 结尾的文件,在文件的尾部添加 hello。
在 ro 中添加一个文件 test.php
$cat ro/test.php
abc
在 fuse 中 cat 同一个我呢见
$cat fuse/test.php
abc
hello
如果更新了 test.php
$cat ro/test.php
new file content
在 fuse 中再去 cat 则会发现更新后的文件内容
$cat fuse/test.php
new file content
hello
就这样的效果。这样的东西可以广泛应用于源代码加工的场景(es6转javascript,css转换,源代码注入调试信息等)。可以无缝地实现自动更新。同时可以做到延迟转换,提高性能。这个代码在 taowen/lambdafs