reverse mode: implement -one-file-system
Fixes https://github.com/rfjakob/gocryptfs/issues/475
This commit is contained in:
parent
ad4b99170b
commit
b2724070d9
@ -331,6 +331,14 @@ See `-suid, -nosuid`.
|
||||
Send USR1 to the specified process after successful mount. This is
|
||||
used internally for daemonization.
|
||||
|
||||
#### -one-file-system
|
||||
Don't cross filesystem boundaries (like rsync's `--one-file-system`).
|
||||
Mountpoints will appear as empty directories.
|
||||
|
||||
Only applicable to reverse mode.
|
||||
|
||||
Limitation: Mounted single files (yes this is possible) are NOT hidden.
|
||||
|
||||
#### -rw, -ro
|
||||
Mount the filesystem read-write (`-rw`, default) or read-only (`-ro`).
|
||||
If both are specified, `-ro` takes precedence.
|
||||
|
@ -30,7 +30,7 @@ type argContainer struct {
|
||||
plaintextnames, quiet, nosyslog, wpanic,
|
||||
longnames, allow_other, reverse, aessiv, nonempty, raw64,
|
||||
noprealloc, speed, hkdf, serialize_reads, forcedecode, hh, info,
|
||||
sharedstorage, devrandom, fsck bool
|
||||
sharedstorage, devrandom, fsck, one_file_system bool
|
||||
// Mount options with opposites
|
||||
dev, nodev, suid, nosuid, exec, noexec, rw, ro, kernel_cache, acl bool
|
||||
masterkey, mountpoint, cipherdir, cpuprofile,
|
||||
@ -178,6 +178,7 @@ func parseCliOpts(osArgs []string) (args argContainer) {
|
||||
flagSet.BoolVar(&args.sharedstorage, "sharedstorage", false, "Make concurrent access to a shared CIPHERDIR safer")
|
||||
flagSet.BoolVar(&args.devrandom, "devrandom", false, "Use /dev/random for generating master key")
|
||||
flagSet.BoolVar(&args.fsck, "fsck", false, "Run a filesystem check on CIPHERDIR")
|
||||
flagSet.BoolVar(&args.one_file_system, "one-file-system", false, "Don't cross filesystem boundaries")
|
||||
|
||||
// Mount options with opposites
|
||||
flagSet.BoolVar(&args.dev, "dev", false, "Allow device files")
|
||||
|
@ -5,7 +5,8 @@
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
export GO111MODULE=on
|
||||
B="go build -tags without_openssl"
|
||||
# Discard resulting binary by writing to /dev/null
|
||||
B="go build -tags without_openssl -o /dev/null"
|
||||
|
||||
set -x
|
||||
|
||||
@ -26,6 +27,3 @@ GOOS=darwin GOARCH=amd64 $B
|
||||
if go tool dist list | grep ios/arm64 ; then
|
||||
GOOS=darwin GOARCH=arm64 $B
|
||||
fi
|
||||
|
||||
# The cross-built binary is not useful on the compile host.
|
||||
rm gocryptfs
|
||||
|
@ -49,4 +49,8 @@ type Args struct {
|
||||
// SharedStorage disables caching & hard link tracking,
|
||||
// enabled via cli flag "-sharedstorage"
|
||||
SharedStorage bool
|
||||
// OneFileSystem disables crossing filesystem boundaries,
|
||||
// like rsync's `--one-file-system` does.
|
||||
// Only applicable to reverse mode.
|
||||
OneFileSystem bool
|
||||
}
|
||||
|
@ -22,6 +22,10 @@ import (
|
||||
// in a `gocryptfs -reverse` mount.
|
||||
type Node struct {
|
||||
fs.Inode
|
||||
// isOtherFilesystem is used for --one-filesystem.
|
||||
// It is set when the device number of this file or directory
|
||||
// is different from n.rootNode().rootDev.
|
||||
isOtherFilesystem bool
|
||||
}
|
||||
|
||||
// Lookup - FUSE call for discovering a file.
|
||||
@ -31,7 +35,14 @@ func (n *Node) Lookup(ctx context.Context, cName string, out *fuse.EntryOut) (ch
|
||||
if t == typeDiriv {
|
||||
// gocryptfs.diriv
|
||||
return n.lookupDiriv(ctx, out)
|
||||
} else if t == typeName {
|
||||
}
|
||||
rn := n.rootNode()
|
||||
if rn.args.OneFileSystem && n.isOtherFilesystem {
|
||||
// With --one-file-system, we present mountpoints as empty. That is,
|
||||
// it contains only a gocryptfs.diriv file (allowed above).
|
||||
return nil, syscall.ENOENT
|
||||
}
|
||||
if t == typeName {
|
||||
// gocryptfs.longname.*.name
|
||||
return n.lookupLongnameName(ctx, cName, out)
|
||||
} else if t == typeConfig {
|
||||
|
@ -23,6 +23,22 @@ import (
|
||||
// This function is symlink-safe through use of openBackingDir() and
|
||||
// ReadDirIVAt().
|
||||
func (n *Node) Readdir(ctx context.Context) (stream fs.DirStream, errno syscall.Errno) {
|
||||
// Virtual files: at least one gocryptfs.diriv file
|
||||
virtualFiles := []fuse.DirEntry{
|
||||
{Mode: virtualFileMode, Name: nametransform.DirIVFilename},
|
||||
}
|
||||
rn := n.rootNode()
|
||||
|
||||
// This directory is a mountpoint. Present it as empty.
|
||||
if rn.args.OneFileSystem && n.isOtherFilesystem {
|
||||
if rn.args.PlaintextNames {
|
||||
return fs.NewListDirStream(nil), 0
|
||||
} else {
|
||||
// An "empty" directory still has a gocryptfs.diriv file!
|
||||
return fs.NewListDirStream(virtualFiles), 0
|
||||
}
|
||||
}
|
||||
|
||||
d, errno := n.prepareAtSyscall("")
|
||||
if errno != 0 {
|
||||
return
|
||||
@ -41,8 +57,6 @@ func (n *Node) Readdir(ctx context.Context) (stream fs.DirStream, errno syscall.
|
||||
return nil, fs.ToErrno(err)
|
||||
}
|
||||
|
||||
rn := n.rootNode()
|
||||
|
||||
// Filter out excluded entries
|
||||
entries = rn.excludeDirEntries(d, entries)
|
||||
|
||||
@ -50,11 +64,6 @@ func (n *Node) Readdir(ctx context.Context) (stream fs.DirStream, errno syscall.
|
||||
return n.readdirPlaintextnames(entries)
|
||||
}
|
||||
|
||||
// Virtual files: at least one gocryptfs.diriv file
|
||||
virtualFiles := []fuse.DirEntry{
|
||||
{Mode: virtualFileMode, Name: nametransform.DirIVFilename},
|
||||
}
|
||||
|
||||
dirIV := pathiv.Derive(d.cPath, pathiv.PurposeDirIV)
|
||||
// Encrypt names
|
||||
for i := range entries {
|
||||
|
@ -91,6 +91,7 @@ func (n *Node) prepareAtSyscall(child string) (d *dirfdPlus, errno syscall.Errno
|
||||
// newChild attaches a new child inode to n.
|
||||
// The passed-in `st` will be modified to get a unique inode number.
|
||||
func (n *Node) newChild(ctx context.Context, st *syscall.Stat_t, out *fuse.EntryOut) *fs.Inode {
|
||||
isOtherFilesystem := (uint64(st.Dev) != n.rootNode().rootDev)
|
||||
// Get unique inode number
|
||||
rn := n.rootNode()
|
||||
rn.inoMap.TranslateStat(st)
|
||||
@ -101,7 +102,9 @@ func (n *Node) newChild(ctx context.Context, st *syscall.Stat_t, out *fuse.Entry
|
||||
Gen: 1,
|
||||
Ino: st.Ino,
|
||||
}
|
||||
node := &Node{}
|
||||
node := &Node{
|
||||
isOtherFilesystem: isOtherFilesystem,
|
||||
}
|
||||
return n.NewInode(ctx, node, id)
|
||||
}
|
||||
|
||||
|
@ -36,17 +36,31 @@ type RootNode struct {
|
||||
// inoMap translates inode numbers from different devices to unique inode
|
||||
// numbers.
|
||||
inoMap *inomap.InoMap
|
||||
// rootDev stores the device number of the backing directory. Used for
|
||||
// --one-file-system.
|
||||
rootDev uint64
|
||||
}
|
||||
|
||||
// NewRootNode returns an encrypted FUSE overlay filesystem.
|
||||
// In this case (reverse mode) the backing directory is plain-text and
|
||||
// ReverseFS provides an encrypted view.
|
||||
func NewRootNode(args fusefrontend.Args, c *contentenc.ContentEnc, n *nametransform.NameTransform) *RootNode {
|
||||
var rootDev uint64
|
||||
if args.OneFileSystem {
|
||||
var st syscall.Stat_t
|
||||
err := syscall.Stat(args.Cipherdir, &st)
|
||||
if err != nil {
|
||||
log.Panicf("Could not stat backing directory %q: %v", args.Cipherdir, err)
|
||||
}
|
||||
rootDev = uint64(st.Dev)
|
||||
}
|
||||
|
||||
rn := &RootNode{
|
||||
args: args,
|
||||
nameTransform: n,
|
||||
contentEnc: c,
|
||||
inoMap: inomap.New(),
|
||||
rootDev: rootDev,
|
||||
}
|
||||
if len(args.Exclude) > 0 || len(args.ExcludeWildcard) > 0 || len(args.ExcludeFrom) > 0 {
|
||||
rn.excluder = prepareExcluder(args)
|
||||
|
1
mount.go
1
mount.go
@ -275,6 +275,7 @@ func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys f
|
||||
Suid: args.suid,
|
||||
KernelCache: args.kernel_cache,
|
||||
SharedStorage: args.sharedstorage,
|
||||
OneFileSystem: args.one_file_system,
|
||||
}
|
||||
// confFile is nil when "-zerokey" or "-masterkey" was used
|
||||
if confFile != nil {
|
||||
|
77
tests/reverse/one_file_system_test.go
Normal file
77
tests/reverse/one_file_system_test.go
Normal file
@ -0,0 +1,77 @@
|
||||
package reverse
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/tests/test_helpers"
|
||||
)
|
||||
|
||||
func doTestOneFileSystem(t *testing.T, plaintextnames bool) {
|
||||
// Let's not explode with "TempDir: pattern contains path separator"
|
||||
myEscapedName := url.PathEscape(t.Name())
|
||||
mnt, err := ioutil.TempDir(test_helpers.TmpDir, myEscapedName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cliArgs := []string{"-reverse", "-zerokey", "-one-file-system"}
|
||||
if plaintextnames {
|
||||
cliArgs = append(cliArgs, "-plaintextnames")
|
||||
}
|
||||
test_helpers.MountOrFatal(t, "/", mnt, cliArgs...)
|
||||
defer test_helpers.UnmountErr(mnt)
|
||||
|
||||
// Copied from inomap
|
||||
const maxPassthruIno = 1<<48 - 1
|
||||
|
||||
entries, err := os.ReadDir(mnt)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mountpoints := []string{}
|
||||
for _, e := range entries {
|
||||
i, err := e.Info()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if !e.IsDir() {
|
||||
// We are only interested in directories
|
||||
continue
|
||||
}
|
||||
st := i.Sys().(*syscall.Stat_t)
|
||||
// The inode numbers of files with a different device number are remapped
|
||||
// to something above maxPassthruIno
|
||||
if st.Ino > maxPassthruIno {
|
||||
mountpoints = append(mountpoints, e.Name())
|
||||
}
|
||||
}
|
||||
if len(mountpoints) == 0 {
|
||||
t.Skip("no mountpoints found, nothing to test")
|
||||
}
|
||||
for _, m := range mountpoints {
|
||||
e, err := os.ReadDir(mnt + "/" + m)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
expected := 1
|
||||
if plaintextnames {
|
||||
expected = 0
|
||||
}
|
||||
if len(e) != expected {
|
||||
t.Errorf("mountpoint %q does not look empty: %v", m, e)
|
||||
}
|
||||
}
|
||||
t.Logf("tested %d mountpoints: %v", len(mountpoints), mountpoints)
|
||||
}
|
||||
|
||||
func TestOneFileSystem(t *testing.T) {
|
||||
if runtime.GOOS != "linux" {
|
||||
t.Skip("only works on linux")
|
||||
}
|
||||
t.Run("normal", func(t *testing.T) { doTestOneFileSystem(t, false) })
|
||||
t.Run("plaintextnames", func(t *testing.T) { doTestOneFileSystem(t, true) })
|
||||
}
|
Loading…
Reference in New Issue
Block a user