@@ -34,6 +34,9 @@ type FS struct {
3434 nameTransform * nametransform.NameTransform
3535 // Content encryption helper
3636 contentEnc * contentenc.ContentEnc
37+ // This lock is used by openWriteOnlyFile() to block concurrent opens while
38+ // it relaxes the permissions on a file.
39+ openWriteOnlyLock sync.RWMutex
3740}
3841
3942var _ pathfs.FileSystem = & FS {} // Verify that interface is implemented.
@@ -102,27 +105,79 @@ func (fs *FS) Open(path string, flags uint32, context *fuse.Context) (fuseFile n
102105 if fs .isFiltered (path ) {
103106 return nil , fuse .EPERM
104107 }
108+ // Taking this lock makes sure we don't race openWriteOnlyFile()
109+ fs .openWriteOnlyLock .RLock ()
110+ defer fs .openWriteOnlyLock .RUnlock ()
111+
105112 newFlags := fs .mangleOpenFlags (flags )
106113 cPath , err := fs .getBackingPath (path )
107114 if err != nil {
108115 tlog .Debug .Printf ("Open: getBackingPath: %v" , err )
109116 return nil , fuse .ToStatus (err )
110117 }
111118 tlog .Debug .Printf ("Open: %s" , cPath )
112- f , err := os .OpenFile (cPath , newFlags , 0666 )
119+ f , err := os .OpenFile (cPath , newFlags , 0 )
113120 if err != nil {
114- err2 := err .(* os.PathError )
115- if err2 . Err == syscall .EMFILE {
121+ sysErr := err .(* os.PathError ). Err
122+ if sysErr == syscall .EMFILE {
116123 var lim syscall.Rlimit
117124 syscall .Getrlimit (syscall .RLIMIT_NOFILE , & lim )
118125 tlog .Warn .Printf ("Open %q: too many open files. Current \" ulimit -n\" : %d" , cPath , lim .Cur )
119126 }
127+ if sysErr == syscall .EACCES && (int (flags )& os .O_WRONLY > 0 ) {
128+ return fs .openWriteOnlyFile (cPath , newFlags )
129+ }
120130 return nil , fuse .ToStatus (err )
121131 }
122-
123132 return NewFile (f , fs )
124133}
125134
135+ // Due to RMW, we always need read permissions on the backing file. This is a
136+ // problem if the file permissions do not allow reading (i.e. 0200 permissions).
137+ // This function works around that problem by chmod'ing the file, obtaining a fd,
138+ // and chmod'ing it back.
139+ func (fs * FS ) openWriteOnlyFile (cPath string , newFlags int ) (fuseFile nodefs.File , status fuse.Status ) {
140+ woFd , err := os .OpenFile (cPath , os .O_WRONLY , 0 )
141+ if err != nil {
142+ return nil , fuse .ToStatus (err )
143+ }
144+ defer woFd .Close ()
145+ fi , err := woFd .Stat ()
146+ if err != nil {
147+ return nil , fuse .ToStatus (err )
148+ }
149+ perms := fi .Mode ().Perm ()
150+ // Verify that we don't have read permissions
151+ if perms & 0400 != 0 {
152+ tlog .Warn .Printf ("openWriteOnlyFile: unexpected permissions %#o, returning EPERM" , perms )
153+ return nil , fuse .ToStatus (syscall .EPERM )
154+ }
155+ // Upgrade the lock to block other Open()s and downgrade again on return
156+ fs .openWriteOnlyLock .RUnlock ()
157+ fs .openWriteOnlyLock .Lock ()
158+ defer func () {
159+ fs .openWriteOnlyLock .Unlock ()
160+ fs .openWriteOnlyLock .RLock ()
161+ }()
162+ // Relax permissions and revert on return
163+ err = woFd .Chmod (perms | 0400 )
164+ if err != nil {
165+ tlog .Warn .Printf ("openWriteOnlyFile: changing permissions failed: %v" , err )
166+ return nil , fuse .ToStatus (err )
167+ }
168+ defer func () {
169+ err2 := woFd .Chmod (perms )
170+ if err2 != nil {
171+ tlog .Warn .Printf ("openWriteOnlyFile: reverting permissions failed: %v" , err2 )
172+ }
173+ }()
174+ rwFd , err := os .OpenFile (cPath , newFlags , 0 )
175+ if err != nil {
176+ return nil , fuse .ToStatus (err )
177+ }
178+ return NewFile (rwFd , fs )
179+ }
180+
126181// Create implements pathfs.Filesystem.
127182func (fs * FS ) Create (path string , flags uint32 , mode uint32 , context * fuse.Context ) (fuseFile nodefs.File , code fuse.Status ) {
128183 if fs .isFiltered (path ) {
0 commit comments