@@ -2,7 +2,9 @@ package fusefrontend_reverse
22
33import (
44 "fmt"
5+ "os"
56 "path/filepath"
7+ "strings"
68 "syscall"
79
810 "golang.org/x/sys/unix"
@@ -14,6 +16,8 @@ import (
1416 "github.com/rfjakob/gocryptfs/internal/configfile"
1517 "github.com/rfjakob/gocryptfs/internal/contentenc"
1618 "github.com/rfjakob/gocryptfs/internal/cryptocore"
19+ "github.com/rfjakob/gocryptfs/internal/ctlsock"
20+ "github.com/rfjakob/gocryptfs/internal/exitcodes"
1721 "github.com/rfjakob/gocryptfs/internal/fusefrontend"
1822 "github.com/rfjakob/gocryptfs/internal/nametransform"
1923 "github.com/rfjakob/gocryptfs/internal/pathiv"
@@ -34,6 +38,8 @@ type ReverseFS struct {
3438 nameTransform * nametransform.NameTransform
3539 // Content encryption helper
3640 contentEnc * contentenc.ContentEnc
41+ // Relative ciphertext paths to exclude (hide) from the user. Used by -exclude.
42+ cExclude []string
3743}
3844
3945var _ pathfs.FileSystem = & ReverseFS {}
@@ -43,14 +49,30 @@ var _ pathfs.FileSystem = &ReverseFS{}
4349// ReverseFS provides an encrypted view.
4450func NewFS (args fusefrontend.Args , c * contentenc.ContentEnc , n * nametransform.NameTransform ) * ReverseFS {
4551 initLongnameCache ()
46- return & ReverseFS {
52+ fs := & ReverseFS {
4753 // pathfs.defaultFileSystem returns ENOSYS for all operations
4854 FileSystem : pathfs .NewDefaultFileSystem (),
4955 loopbackfs : pathfs .NewLoopbackFileSystem (args .Cipherdir ),
5056 args : args ,
5157 nameTransform : n ,
5258 contentEnc : c ,
5359 }
60+ if len (args .Exclude ) > 0 {
61+ for _ , dirty := range args .Exclude {
62+ clean := ctlsock .SanitizePath (dirty )
63+ if clean != dirty {
64+ tlog .Warn .Printf ("-exclude: non-canonical path %q has been interpreted as %q" , dirty , clean )
65+ }
66+ cPath , err := fs .EncryptPath (clean )
67+ if err != nil {
68+ tlog .Fatal .Printf ("-exclude: EncryptPath %q failed: %v" , clean , err )
69+ os .Exit (exitcodes .ExcludeError )
70+ }
71+ fs .cExclude = append (fs .cExclude , cPath )
72+ }
73+ tlog .Debug .Printf ("-exclude: %v -> %v" , fs .args .Exclude , fs .cExclude )
74+ }
75+ return fs
5476}
5577
5678// relDir is identical to filepath.Dir excepts that it returns "" when
@@ -64,6 +86,21 @@ func relDir(path string) string {
6486 return dir
6587}
6688
89+ // isExcluded finds out if relative ciphertext path "relPath" is excluded
90+ // (used when -exclude is passed by the user)
91+ func (rfs * ReverseFS ) isExcluded (relPath string ) bool {
92+ for _ , e := range rfs .cExclude {
93+ if e == relPath {
94+ return true
95+ }
96+ // Files inside an excluded directory are also excluded
97+ if strings .HasPrefix (relPath , e + "/" ) {
98+ return true
99+ }
100+ }
101+ return false
102+ }
103+
67104// isDirIV determines if the path points to a gocryptfs.diriv file
68105func (rfs * ReverseFS ) isDirIV (relPath string ) bool {
69106 if rfs .args .PlaintextNames {
@@ -99,6 +136,9 @@ func (rfs *ReverseFS) isTranslatedConfig(relPath string) bool {
99136// GetAttr - FUSE call
100137// "relPath" is the relative ciphertext path
101138func (rfs * ReverseFS ) GetAttr (relPath string , context * fuse.Context ) (* fuse.Attr , fuse.Status ) {
139+ if rfs .isExcluded (relPath ) {
140+ return nil , fuse .ENOENT
141+ }
102142 // Handle "gocryptfs.conf"
103143 if rfs .isTranslatedConfig (relPath ) {
104144 absConfPath , _ := rfs .abs (configfile .ConfReverseName , nil )
@@ -180,6 +220,9 @@ func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr
180220
181221// Access - FUSE call
182222func (rfs * ReverseFS ) Access (relPath string , mode uint32 , context * fuse.Context ) fuse.Status {
223+ if rfs .isExcluded (relPath ) {
224+ return fuse .ENOENT
225+ }
183226 if rfs .isTranslatedConfig (relPath ) || rfs .isDirIV (relPath ) || rfs .isNameFile (relPath ) {
184227 // access(2) R_OK flag for checking if the file is readable, always 4 as defined in POSIX.
185228 ROK := uint32 (0x4 )
@@ -203,6 +246,9 @@ func (rfs *ReverseFS) Access(relPath string, mode uint32, context *fuse.Context)
203246
204247// Open - FUSE call
205248func (rfs * ReverseFS ) Open (relPath string , flags uint32 , context * fuse.Context ) (fuseFile nodefs.File , status fuse.Status ) {
249+ if rfs .isExcluded (relPath ) {
250+ return nil , fuse .ENOENT
251+ }
206252 if rfs .isTranslatedConfig (relPath ) {
207253 return rfs .loopbackfs .Open (configfile .ConfReverseName , flags , context )
208254 }
@@ -242,6 +288,9 @@ func (rfs *ReverseFS) openDirPlaintextnames(relPath string, entries []fuse.DirEn
242288
243289// OpenDir - FUSE readdir call
244290func (rfs * ReverseFS ) OpenDir (cipherPath string , context * fuse.Context ) ([]fuse.DirEntry , fuse.Status ) {
291+ if rfs .isExcluded (cipherPath ) {
292+ return nil , fuse .ENOENT
293+ }
245294 relPath , err := rfs .decryptPath (cipherPath )
246295 if err != nil {
247296 return nil , fuse .ToStatus (err )
@@ -292,6 +341,21 @@ func (rfs *ReverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse.
292341 }
293342 entries [i ].Name = cName
294343 }
344+ // Filter out excluded entries
345+ if rfs .cExclude != nil {
346+ filtered := make ([]fuse.DirEntry , 0 , len (entries ))
347+ for _ , entry := range entries {
348+ // filepath.Join handles the case of cipherPath="" correctly:
349+ // Join("", "foo") -> "foo". This does not: cipherPath + "/" + name"
350+ p := filepath .Join (cipherPath , entry .Name )
351+ if rfs .isExcluded (p ) {
352+ // Skip file
353+ continue
354+ }
355+ filtered = append (filtered , entry )
356+ }
357+ entries = filtered
358+ }
295359 entries = append (entries , virtualFiles [:nVirtual ]... )
296360 return entries , fuse .OK
297361}
@@ -301,7 +365,10 @@ func (rfs *ReverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse.
301365// Securing statfs against symlink races seems to be more trouble than
302366// it's worth, so we just ignore the path and always return info about the
303367// backing storage root dir.
304- func (rfs * ReverseFS ) StatFs (path string ) * fuse.StatfsOut {
368+ func (rfs * ReverseFS ) StatFs (relPath string ) * fuse.StatfsOut {
369+ if rfs .isExcluded (relPath ) {
370+ return nil
371+ }
305372 var s syscall.Statfs_t
306373 err := syscall .Statfs (rfs .args .Cipherdir , & s )
307374 if err != nil {
@@ -314,6 +381,9 @@ func (rfs *ReverseFS) StatFs(path string) *fuse.StatfsOut {
314381
315382// Readlink - FUSE call
316383func (rfs * ReverseFS ) Readlink (relPath string , context * fuse.Context ) (string , fuse.Status ) {
384+ if rfs .isExcluded (relPath ) {
385+ return "" , fuse .ENOENT
386+ }
317387 dirfd , name , err := rfs .openBackingDir (relPath )
318388 if err != nil {
319389 return "" , fuse .ToStatus (err )
0 commit comments