• Kirill Smelkov's avatar
    nodefs: Don't crash on handleless WithFlags opens · 402331d4
    Kirill Smelkov authored
    Since https://git.kernel.org/linus/7678ac5061 FUSE filesystems were allowed to
    return ENOSYS from open and, open seeing this, the kernel will not send any
    open request anymore and just use Fh=0 for all opened files. This is handy for
    filesystems that don't have any per-opened-file state.
    
    However if there is at least one node for which opened files should have
    their own state, it is not correct to return ENOSYS from open _ever_, as that
    would prevent the kernel to call open on all nodes - in particular on the node
    where open needs to create a state associated with the file handle(*).
    
    It is already explicitly documented that Node.Open can return File=nil
    in which case filesystem operations like Read and Write will be called
    on the node directly. This way a filesystem could try to simulate an
    Open that was returning ENOSYS with Open that returns File=nil. However
    it is not the same as ENOSYS open also prevents the kernel from dropping
    the file cache:
    
    https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/fuse/file.c?id=v5.0-rc3-241-g7c2614bf7a1f#n131
    
    From this point of view Opens that were returning ENOSYS should be
    replaced with Open that returns WithFlags{File=nil, flags=FOPEN_KEEP_CACHE}.
    
    Unfortunately if one tries to do that, go-fuse segfaults as the test
    included in this patch demonstrates:
    
    	13:36:53.238706 rx 13: LOOKUP i1 ["world.txt"] 10b
    	13:36:53.238745 tx 13:     OK, {i4 g3 tE=1s tA=1s {M0100644 SZ=5 L=1 1000:1000 B0*0 i0:4 A 0.000000 M 0.000000 C 0.000000}}
    	13:36:53.238780 rx 14: OPEN i4 {O_RDONLY,0x8000}
    	13:36:53.238791 tx 14:     OK, {Fh 2 CACHE}				<-- NOTE Fh != 0
    	13:36:53.238706 rx 12: RELEASE i3 {Fh 0 NONBLOCK,0x8000  L0}
    	13:36:53.238804 tx 12:     OK
    	13:36:53.238823 rx 15: READ i4 {Fh 2 [0 +4096)  L 0 NONBLOCK,0x8000}
    	13:36:53.238830 tx 15:     OK,  5b data "world"
    	13:36:53.238846 rx 16: GETATTR i4 {Fh 2}
    	13:36:53.238865 tx 16:     OK, {tA=1s {M0100644 SZ=5 L=1 1000:1000 B0*0 i0:4 A 0.000000 M 0.000000 C 0.000000}}
    	13:36:53.238879 rx 17: FLUSH i4 {Fh 2}
    	panic: runtime error: invalid memory address or nil pointer dereference
    	[signal SIGSEGV: segmentation violation code=0x1 addr=0x30 pc=0x539f9f]
    
    	goroutine 6 [running]:
    	github.com/hanwen/go-fuse/fuse/nodefs.(*rawBridge).Flush(0xc000010960, 0xc000104180, 0x0)
    	        /home/kirr/src/neo/src/github.com/hanwen/go-fuse/fuse/nodefs/fsops.go:490 +0x6f
    	github.com/hanwen/go-fuse/fuse.doFlush(0xc0000de000, 0xc000104000)
    	        /home/kirr/src/neo/src/github.com/hanwen/go-fuse/fuse/opcode.go:373 +0x42
    	github.com/hanwen/go-fuse/fuse.(*Server).handleRequest(0xc0000de000, 0xc000104000, 0xc000104000)
    	        /home/kirr/src/neo/src/github.com/hanwen/go-fuse/fuse/server.go:431 +0x26b
    	github.com/hanwen/go-fuse/fuse.(*Server).loop(0xc0000de000, 0x0)
    	        /home/kirr/src/neo/src/github.com/hanwen/go-fuse/fuse/server.go:403 +0x18f
    	github.com/hanwen/go-fuse/fuse.(*Server).Serve(0xc0000de000)
    	        /home/kirr/src/neo/src/github.com/hanwen/go-fuse/fuse/server.go:331 +0x6d
    	created by github.com/hanwen/go-fuse/fuse/test.TestNoFile
    	        /home/kirr/src/neo/src/github.com/hanwen/go-fuse/fuse/test/nofile_test.go:73 +0x442
    
    Fix it by teaching registerFileHandle not to register any handle at all
    and return Fh=0, if the inner file (file itself, or the file that was wrapped
    with WithFlags) is nil.
    
    After the patch the trace for world.txt open/read (that is opened with
    WithFlags{File=nil, Flags=FOPEN_KEEP_CACHE} is as follows.
    
    	14:59:31.714048 rx 13: LOOKUP i1 ["world.txt"] 10b
    	14:59:31.714062 tx 13:     OK, {i4 g3 tE=1s tA=1s {M0100644 SZ=5 L=1 1000:1000 B0*0 i0:4 A 0.000000 M 0.000000 C 0.000000}}
    	14:59:31.714081 rx 14: OPEN i4 {O_RDONLY,0x8000}
    	14:59:31.714091 tx 14:     OK, {Fh 0 CACHE}				<-- NOTE Fh = 0
    	14:59:31.714123 rx 15: READ i4 {Fh 0 [0 +4096)  L 0 NONBLOCK,0x8000}
    	14:59:31.714132 tx 15:     OK,  5b data "world"
    	14:59:31.714150 rx 16: GETATTR i4 {Fh 0}
    	14:59:31.714159 tx 16:     OK, {tA=1s {M0100644 SZ=5 L=1 1000:1000 B0*0 i0:4 A 0.000000 M 0.000000 C 0.000000}}
    	14:59:31.714181 rx 17: FLUSH i4 {Fh 0}
    	14:59:31.714186 tx 17:     OK
    	14:59:31.714202 rx 18: RELEASE i4 {Fh 0 NONBLOCK,0x8000  L0}
    	14:59:31.714208 tx 18:     OK
    
    (*) my particular case is filesystem where many nodes are just data, but
    additionally there are files that behave like sockets - for every client
    who opens such file the filesystem establishes separate bidirectional
    channel for control exchange via that stream:
    
    https://lab.nexedi.com/kirr/wendelin.core/blob/a50da567fd/wcfs/misc.go#L205
    402331d4
fsops.go 13.6 KB