2020-08-01 20:48:32 +02:00
|
|
|
package fusefrontend_reverse
|
|
|
|
|
|
|
|
import (
|
2020-08-02 19:33:12 +02:00
|
|
|
"log"
|
2021-09-10 17:17:16 +02:00
|
|
|
"os"
|
2020-08-15 17:31:25 +02:00
|
|
|
"path/filepath"
|
2020-08-02 19:33:12 +02:00
|
|
|
"strings"
|
|
|
|
"syscall"
|
|
|
|
|
2021-09-10 17:17:16 +02:00
|
|
|
"github.com/rfjakob/gocryptfs/v2/internal/exitcodes"
|
|
|
|
|
2021-08-23 15:05:15 +02:00
|
|
|
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
|
2020-08-15 17:31:25 +02:00
|
|
|
|
2020-08-02 19:33:12 +02:00
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
|
|
|
|
"github.com/hanwen/go-fuse/v2/fs"
|
2020-08-15 17:31:25 +02:00
|
|
|
"github.com/hanwen/go-fuse/v2/fuse"
|
2020-08-02 19:33:12 +02:00
|
|
|
|
2021-08-23 15:05:15 +02:00
|
|
|
"github.com/rfjakob/gocryptfs/v2/internal/contentenc"
|
|
|
|
"github.com/rfjakob/gocryptfs/v2/internal/fusefrontend"
|
|
|
|
"github.com/rfjakob/gocryptfs/v2/internal/inomap"
|
|
|
|
"github.com/rfjakob/gocryptfs/v2/internal/nametransform"
|
|
|
|
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
|
2020-08-01 20:48:32 +02:00
|
|
|
|
|
|
|
"github.com/sabhiram/go-gitignore"
|
|
|
|
)
|
|
|
|
|
|
|
|
// RootNode is the root directory in a `gocryptfs -reverse` mount
|
|
|
|
type RootNode struct {
|
|
|
|
Node
|
|
|
|
// Stores configuration arguments
|
|
|
|
args fusefrontend.Args
|
|
|
|
// Filename encryption helper
|
2021-06-21 11:53:33 +02:00
|
|
|
nameTransform *nametransform.NameTransform
|
2020-08-01 20:48:32 +02:00
|
|
|
// Content encryption helper
|
|
|
|
contentEnc *contentenc.ContentEnc
|
2020-08-15 16:08:16 +02:00
|
|
|
// Tests whether a path is excluded (hidden) from the user. Used by -exclude.
|
2020-08-15 17:31:25 +02:00
|
|
|
excluder ignore.IgnoreParser
|
2020-08-01 20:48:32 +02:00
|
|
|
// inoMap translates inode numbers from different devices to unique inode
|
|
|
|
// numbers.
|
|
|
|
inoMap *inomap.InoMap
|
2021-08-16 18:40:48 +02:00
|
|
|
// rootDev stores the device number of the backing directory. Used for
|
|
|
|
// --one-file-system.
|
|
|
|
rootDev uint64
|
2022-04-24 19:35:30 +02:00
|
|
|
// If a file name length is shorter than shortNameMax, there is no need to
|
|
|
|
// hash it.
|
|
|
|
shortNameMax int
|
2020-08-01 20:48:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewRootNode returns an encrypted FUSE overlay filesystem.
|
|
|
|
// In this case (reverse mode) the backing directory is plain-text and
|
|
|
|
// ReverseFS provides an encrypted view.
|
2021-06-21 11:53:33 +02:00
|
|
|
func NewRootNode(args fusefrontend.Args, c *contentenc.ContentEnc, n *nametransform.NameTransform) *RootNode {
|
2021-08-16 18:40:48 +02:00
|
|
|
var rootDev uint64
|
2021-09-10 17:17:16 +02:00
|
|
|
var st syscall.Stat_t
|
2022-04-24 19:35:30 +02:00
|
|
|
var shortNameMax int
|
2021-09-10 17:17:16 +02:00
|
|
|
if err := syscall.Stat(args.Cipherdir, &st); err != nil {
|
|
|
|
tlog.Warn.Printf("Could not stat backing directory %q: %v", args.Cipherdir, err)
|
|
|
|
if args.OneFileSystem {
|
|
|
|
tlog.Fatal.Printf("This is a fatal error in combination with -one-file-system")
|
|
|
|
os.Exit(exitcodes.CipherDir)
|
2021-08-16 18:40:48 +02:00
|
|
|
}
|
2021-09-10 17:17:16 +02:00
|
|
|
} else {
|
2021-08-16 18:40:48 +02:00
|
|
|
rootDev = uint64(st.Dev)
|
|
|
|
}
|
|
|
|
|
2022-04-24 19:35:30 +02:00
|
|
|
shortNameMax = n.GetLongNameMax() * 3 / 4
|
2022-08-28 11:11:36 +02:00
|
|
|
shortNameMax = shortNameMax - shortNameMax%16 - 1
|
2022-04-24 19:35:30 +02:00
|
|
|
|
2020-08-15 17:31:25 +02:00
|
|
|
rn := &RootNode{
|
2020-08-01 20:48:32 +02:00
|
|
|
args: args,
|
|
|
|
nameTransform: n,
|
|
|
|
contentEnc: c,
|
2021-09-10 17:17:16 +02:00
|
|
|
inoMap: inomap.New(rootDev),
|
2021-08-16 18:40:48 +02:00
|
|
|
rootDev: rootDev,
|
2022-04-24 19:35:30 +02:00
|
|
|
shortNameMax: shortNameMax,
|
2020-08-01 20:48:32 +02:00
|
|
|
}
|
2020-08-15 17:31:25 +02:00
|
|
|
if len(args.Exclude) > 0 || len(args.ExcludeWildcard) > 0 || len(args.ExcludeFrom) > 0 {
|
|
|
|
rn.excluder = prepareExcluder(args)
|
|
|
|
}
|
|
|
|
return rn
|
2020-08-01 20:48:32 +02:00
|
|
|
}
|
2020-08-02 19:33:12 +02:00
|
|
|
|
|
|
|
// You can pass either gocryptfs.longname.XYZ.name or gocryptfs.longname.XYZ.
|
|
|
|
func (rn *RootNode) findLongnameParent(fd int, diriv []byte, longname string) (pName string, cFullName string, errno syscall.Errno) {
|
2020-08-15 17:31:25 +02:00
|
|
|
defer func() {
|
|
|
|
tlog.Debug.Printf("findLongnameParent: %d %x %q -> %q %q %d\n", fd, diriv, longname, pName, cFullName, errno)
|
|
|
|
}()
|
2020-08-02 19:33:12 +02:00
|
|
|
if strings.HasSuffix(longname, nametransform.LongNameSuffix) {
|
|
|
|
longname = nametransform.RemoveLongNameSuffix(longname)
|
|
|
|
}
|
|
|
|
entries, err := syscallcompat.Getdents(fd)
|
|
|
|
if err != nil {
|
|
|
|
errno = fs.ToErrno(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for _, entry := range entries {
|
2022-04-24 19:35:30 +02:00
|
|
|
if len(entry.Name) <= rn.shortNameMax {
|
2020-08-02 19:33:12 +02:00
|
|
|
continue
|
|
|
|
}
|
2021-06-02 14:21:30 +02:00
|
|
|
cFullName, err = rn.nameTransform.EncryptName(entry.Name, diriv)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
2022-04-24 19:35:30 +02:00
|
|
|
if len(cFullName) <= unix.NAME_MAX && len(cFullName) <= rn.nameTransform.GetLongNameMax() {
|
2021-06-02 14:21:30 +02:00
|
|
|
// Entry should have been skipped by the shortNameMax check above
|
2022-04-24 19:35:30 +02:00
|
|
|
log.Panic("logic error or wrong shortNameMax?")
|
2020-08-02 19:33:12 +02:00
|
|
|
}
|
|
|
|
hName := rn.nameTransform.HashLongName(cFullName)
|
|
|
|
if longname == hName {
|
|
|
|
pName = entry.Name
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if pName == "" {
|
|
|
|
errno = syscall.ENOENT
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2020-08-15 16:08:16 +02:00
|
|
|
|
|
|
|
// isExcludedPlain finds out if the plaintext path "pPath" is
|
|
|
|
// excluded (used when -exclude is passed by the user).
|
|
|
|
func (rn *RootNode) isExcludedPlain(pPath string) bool {
|
2021-08-18 11:33:12 +02:00
|
|
|
// root dir can't be excluded
|
|
|
|
if pPath == "" {
|
|
|
|
return false
|
|
|
|
}
|
2020-08-15 16:08:16 +02:00
|
|
|
return rn.excluder != nil && rn.excluder.MatchesPath(pPath)
|
|
|
|
}
|
2020-08-15 17:31:25 +02:00
|
|
|
|
|
|
|
// excludeDirEntries filters out directory entries that are "-exclude"d.
|
|
|
|
// pDir is the relative plaintext path to the directory these entries are
|
|
|
|
// from. The entries should be plaintext files.
|
|
|
|
func (rn *RootNode) excludeDirEntries(d *dirfdPlus, entries []fuse.DirEntry) (filtered []fuse.DirEntry) {
|
|
|
|
if rn.excluder == nil {
|
|
|
|
return entries
|
|
|
|
}
|
|
|
|
filtered = make([]fuse.DirEntry, 0, len(entries))
|
|
|
|
for _, entry := range entries {
|
|
|
|
// filepath.Join handles the case of pDir="" correctly:
|
|
|
|
// Join("", "foo") -> "foo". This does not: pDir + "/" + name"
|
|
|
|
p := filepath.Join(d.pPath, entry.Name)
|
|
|
|
if rn.isExcludedPlain(p) {
|
|
|
|
// Skip file
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
filtered = append(filtered, entry)
|
|
|
|
}
|
|
|
|
return filtered
|
|
|
|
}
|