fix: replace assert(offset==0) with early return in readdir#371
Conversation
There was a problem hiding this comment.
Why assert that offset must be 0, and then handle the case where it isn't?
There was a problem hiding this comment.
Because previously sshfs uses buf_get_entries (sshfs.c: 985) with offset=0, and assert(offset=0) is then used in sftp_readdir_async / sftp_readdir_sync for bug detection. In this process, all directory entries are returned from the server within one step. If the offset is non-zero, something might be wrong. See the notes in fuse.h:
/** Read directory
*
* The filesystem may choose between two modes of operation:
*
* 1) The readdir implementation ignores the offset parameter, and
* passes zero to the filler function's offset. The filler
* function will not return '1' (unless an error happens), so the
* whole directory is read in a single readdir operation.
*
* 2) The readdir implementation keeps track of the offsets of the
* directory entries. It uses the offset parameter and always
* passes non-zero offset to the filler function. When the buffer
* is full (or an error happens) the filler function will return
* '1'.
*
* When FUSE_READDIR_PLUS is not set, only some parameters of the
* fill function (the fuse_fill_dir_t parameter) are actually used:
* The file type (which is part of stat::st_mode) is used. And if
* fuse_config::use_ino is set, the inode (stat::st_ino) is also
* used. The other fields are ignored when FUSE_READDIR_PLUS is not
* set.
*/
FUSE_DARWIN_EXTEND_OPERATION(
readdir,
int (*) (const char *, void *, fuse_fill_dir_t, off_t,
struct fuse_file_info *, enum fuse_readdir_flags),
int (*) (const char *, void *, fuse_darwin_fill_dir_t, off_t,
struct fuse_file_info *, enum fuse_readdir_flags)
)
However, the updated MacFuse uses the new FSKit backend instead of kernel extension backend. This will sometimes deliver a non-zero offset, and thus lead to abort and disconnection.
There was a problem hiding this comment.
But won't the assert cause the code to bail on a non-zero offset? Do you perhaps want to #ifdef the assert so it isn't active on macOS?
There was a problem hiding this comment.
I have replaced the assert(offset == 0) with if (offset !=0) return 0, which will return early instead of aborting the sshfs process. Although this may affect the FSKit backend behavior I mentioned, it works well for me so far. By the way, the #ifdef flag suggestion is a good point. I will update the PR shortly.
There was a problem hiding this comment.
Oh, I got what you meant in the beginning. There was a typo and have been fixed in the update =)
On macOS with macFUSE 5.3.x (FSKit backend) and macOS 27 Golden Gate beta, Finder/Spotlight may call readdir with a non-zero offset even when fuse_fill_dir is always called with offset=0 (old-style readdir). This triggers the assert and aborts the sshfs process. Replace assert(offset == 0) with if (offset != 0) return 0 in: - sftp_readdir_async() in sshfs.c - sftp_readdir_sync() in sshfs.c - cache_readdir() in cache.c Since all entries are returned in the first (offset=0) call, returning 0 on subsequent calls is semantically correct and prevents the crash. Fixes: libfuse#338 Tested: macOS 27 Golden Gate beta 2, macFUSE 5.3.2
On macOS with macFUSE 5.3.x (FSKit backend) and macOS 27 Golden Gate beta, Finder/Spotlight may call readdir with a non-zero offset even when fuse_fill_dir is always called with offset=0 (old-style readdir). This triggers the assert and aborts the sshfs process.
Replace assert(offset == 0) with if (offset != 0) return 0 in:
Since all entries are returned in the first (offset=0) call, returning 0 on subsequent calls is semantically correct and prevents the crash.
Fixes: #338
Tested: macOS 27 Golden Gate beta 2, macFUSE 5.3.2