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
|
Send USR1 to the specified process after successful mount. This is
|
||||||
used internally for daemonization.
|
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
|
#### -rw, -ro
|
||||||
Mount the filesystem read-write (`-rw`, default) or read-only (`-ro`).
|
Mount the filesystem read-write (`-rw`, default) or read-only (`-ro`).
|
||||||
If both are specified, `-ro` takes precedence.
|
If both are specified, `-ro` takes precedence.
|
||||||
|
@ -30,7 +30,7 @@ type argContainer struct {
|
|||||||
plaintextnames, quiet, nosyslog, wpanic,
|
plaintextnames, quiet, nosyslog, wpanic,
|
||||||
longnames, allow_other, reverse, aessiv, nonempty, raw64,
|
longnames, allow_other, reverse, aessiv, nonempty, raw64,
|
||||||
noprealloc, speed, hkdf, serialize_reads, forcedecode, hh, info,
|
noprealloc, speed, hkdf, serialize_reads, forcedecode, hh, info,
|
||||||
sharedstorage, devrandom, fsck bool
|
sharedstorage, devrandom, fsck, one_file_system bool
|
||||||
// Mount options with opposites
|
// Mount options with opposites
|
||||||
dev, nodev, suid, nosuid, exec, noexec, rw, ro, kernel_cache, acl bool
|
dev, nodev, suid, nosuid, exec, noexec, rw, ro, kernel_cache, acl bool
|
||||||
masterkey, mountpoint, cipherdir, cpuprofile,
|
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.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.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.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
|
// Mount options with opposites
|
||||||
flagSet.BoolVar(&args.dev, "dev", false, "Allow device files")
|
flagSet.BoolVar(&args.dev, "dev", false, "Allow device files")
|
||||||
|
@ -5,7 +5,8 @@
|
|||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
export GO111MODULE=on
|
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
|
set -x
|
||||||
|
|
||||||
@ -26,6 +27,3 @@ GOOS=darwin GOARCH=amd64 $B
|
|||||||
if go tool dist list | grep ios/arm64 ; then
|
if go tool dist list | grep ios/arm64 ; then
|
||||||
GOOS=darwin GOARCH=arm64 $B
|
GOOS=darwin GOARCH=arm64 $B
|
||||||
fi
|
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,
|
// SharedStorage disables caching & hard link tracking,
|
||||||
// enabled via cli flag "-sharedstorage"
|
// enabled via cli flag "-sharedstorage"
|
||||||
SharedStorage bool
|
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.
|
// in a `gocryptfs -reverse` mount.
|
||||||
type Node struct {
|
type Node struct {
|
||||||
fs.Inode
|
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.
|
// 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 {
|
if t == typeDiriv {
|
||||||
// gocryptfs.diriv
|
// gocryptfs.diriv
|
||||||
return n.lookupDiriv(ctx, out)
|
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
|
// gocryptfs.longname.*.name
|
||||||
return n.lookupLongnameName(ctx, cName, out)
|
return n.lookupLongnameName(ctx, cName, out)
|
||||||
} else if t == typeConfig {
|
} else if t == typeConfig {
|
||||||
|
@ -23,6 +23,22 @@ import (
|
|||||||
// This function is symlink-safe through use of openBackingDir() and
|
// This function is symlink-safe through use of openBackingDir() and
|
||||||
// ReadDirIVAt().
|
// ReadDirIVAt().
|
||||||
func (n *Node) Readdir(ctx context.Context) (stream fs.DirStream, errno syscall.Errno) {
|
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("")
|
d, errno := n.prepareAtSyscall("")
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
return
|
return
|
||||||
@ -41,8 +57,6 @@ func (n *Node) Readdir(ctx context.Context) (stream fs.DirStream, errno syscall.
|
|||||||
return nil, fs.ToErrno(err)
|
return nil, fs.ToErrno(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rn := n.rootNode()
|
|
||||||
|
|
||||||
// Filter out excluded entries
|
// Filter out excluded entries
|
||||||
entries = rn.excludeDirEntries(d, 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)
|
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)
|
dirIV := pathiv.Derive(d.cPath, pathiv.PurposeDirIV)
|
||||||
// Encrypt names
|
// Encrypt names
|
||||||
for i := range entries {
|
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.
|
// newChild attaches a new child inode to n.
|
||||||
// The passed-in `st` will be modified to get a unique inode number.
|
// 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 {
|
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
|
// Get unique inode number
|
||||||
rn := n.rootNode()
|
rn := n.rootNode()
|
||||||
rn.inoMap.TranslateStat(st)
|
rn.inoMap.TranslateStat(st)
|
||||||
@ -101,7 +102,9 @@ func (n *Node) newChild(ctx context.Context, st *syscall.Stat_t, out *fuse.Entry
|
|||||||
Gen: 1,
|
Gen: 1,
|
||||||
Ino: st.Ino,
|
Ino: st.Ino,
|
||||||
}
|
}
|
||||||
node := &Node{}
|
node := &Node{
|
||||||
|
isOtherFilesystem: isOtherFilesystem,
|
||||||
|
}
|
||||||
return n.NewInode(ctx, node, id)
|
return n.NewInode(ctx, node, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,17 +36,31 @@ type RootNode struct {
|
|||||||
// inoMap translates inode numbers from different devices to unique inode
|
// inoMap translates inode numbers from different devices to unique inode
|
||||||
// numbers.
|
// numbers.
|
||||||
inoMap *inomap.InoMap
|
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.
|
// NewRootNode returns an encrypted FUSE overlay filesystem.
|
||||||
// In this case (reverse mode) the backing directory is plain-text and
|
// In this case (reverse mode) the backing directory is plain-text and
|
||||||
// ReverseFS provides an encrypted view.
|
// ReverseFS provides an encrypted view.
|
||||||
func NewRootNode(args fusefrontend.Args, c *contentenc.ContentEnc, n *nametransform.NameTransform) *RootNode {
|
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{
|
rn := &RootNode{
|
||||||
args: args,
|
args: args,
|
||||||
nameTransform: n,
|
nameTransform: n,
|
||||||
contentEnc: c,
|
contentEnc: c,
|
||||||
inoMap: inomap.New(),
|
inoMap: inomap.New(),
|
||||||
|
rootDev: rootDev,
|
||||||
}
|
}
|
||||||
if len(args.Exclude) > 0 || len(args.ExcludeWildcard) > 0 || len(args.ExcludeFrom) > 0 {
|
if len(args.Exclude) > 0 || len(args.ExcludeWildcard) > 0 || len(args.ExcludeFrom) > 0 {
|
||||||
rn.excluder = prepareExcluder(args)
|
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,
|
Suid: args.suid,
|
||||||
KernelCache: args.kernel_cache,
|
KernelCache: args.kernel_cache,
|
||||||
SharedStorage: args.sharedstorage,
|
SharedStorage: args.sharedstorage,
|
||||||
|
OneFileSystem: args.one_file_system,
|
||||||
}
|
}
|
||||||
// confFile is nil when "-zerokey" or "-masterkey" was used
|
// confFile is nil when "-zerokey" or "-masterkey" was used
|
||||||
if confFile != nil {
|
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