From ebeb94b5a3384ec1ae28b38396dae8f0e180c0f9 Mon Sep 17 00:00:00 2001 From: jae beller Date: Fri, 30 Jan 2026 19:06:55 -0500 Subject: [PATCH 1/2] Fix backslash escapes for `-o ssh_command` The option previously supported escaping spaces with backslash, but this was broken at some point. Spaces are correctly skipped, but the backslashes are never removed. As a result, it is impossible to use commands with arguments that include spaces. Note that escaping requires a double backslash. The first backslash is always removed by `fuse_opt_parse`'s own escape handling. --- sshfs.c | 14 ++++++++++---- test/test_sshfs.py | 27 +++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/sshfs.c b/sshfs.c index c97f66d..39d9ced 100644 --- a/sshfs.c +++ b/sshfs.c @@ -3984,6 +3984,7 @@ static char *tokenize_on_space(char *str) { static char *pos = NULL; char *start = NULL; + char *end = NULL; if (str) pos = str; @@ -3996,22 +3997,27 @@ static char *tokenize_on_space(char *str) pos++; start = pos; + end = pos; while (*pos != '\0') { // break on space, but not on '\ ' - if (*pos == ' ' && *(pos - 1) != '\\') { - break; + if (*pos == ' ') { + if (*(pos - 1) == '\\') { + end--; + } else { + break; + } } - pos++; + *end++ = *pos++; } if (*pos == '\0') { pos = NULL; } else { - *pos = '\0'; pos++; } + *end = '\0'; return start; } diff --git a/test/test_sshfs.py b/test/test_sshfs.py index c4cc64f..2da3dd4 100755 --- a/test/test_sshfs.py +++ b/test/test_sshfs.py @@ -1080,3 +1080,30 @@ def test_contain_symlinks_option_precedence(tmpdir, capfd) -> None: with pytest.raises(OSError) as exc_info: os.readlink(pjoin(mnt_dir, "abs")) assert exc_info.value.errno == errno.EPERM + + +def test_backslash_escape(capfd): + """Regression test for parsing backslash escape sequences in options""" + + cases = [ + (r"one two three", " "), + (r"one\\ two three", " "), + # fuse_opt_parse interprets "\ " as " ", so these are still separate + (r"one two\ three", " "), + ] + + for line, args in cases: + cmdline = base_cmdline + [ + pjoin(basename, "sshfs"), + "-f", + "localhost:foo", + "/dev/null", + "-o", + "sshfs_debug", + "-o", + "ssh_command=/dev/null " + line, + ] + assert subprocess.run(cmdline).returncode != 0 + + captured = capfd.readouterr() + assert "executing " + args in captured.err From 268c84083018995e6acbd7b207877a76d6f5e081 Mon Sep 17 00:00:00 2001 From: jae beller Date: Fri, 19 Jun 2026 15:49:07 -0400 Subject: [PATCH 2/2] Fix backslash escape test Some versions of `fusermount` exit early when attempting to mount `/dev/null`. Use a temporary directory instead. --- test/test_sshfs.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/test_sshfs.py b/test/test_sshfs.py index 2da3dd4..1c1d4eb 100755 --- a/test/test_sshfs.py +++ b/test/test_sshfs.py @@ -1082,7 +1082,7 @@ def test_contain_symlinks_option_precedence(tmpdir, capfd) -> None: assert exc_info.value.errno == errno.EPERM -def test_backslash_escape(capfd): +def test_backslash_escape(tmpdir, capfd): """Regression test for parsing backslash escape sequences in options""" cases = [ @@ -1092,12 +1092,15 @@ def test_backslash_escape(capfd): (r"one two\ three", " "), ] + mnt_dir = str(tmpdir.mkdir("mnt")) + # we can skip cleanup since the mount always fails + for line, args in cases: cmdline = base_cmdline + [ pjoin(basename, "sshfs"), "-f", "localhost:foo", - "/dev/null", + mnt_dir, "-o", "sshfs_debug", "-o",