Compare commits

...

194 Commits

Author SHA1 Message Date
Abhinav Agarwal 5393ddf795 ci: add CodeQL static analysis
- New codeql.yml with manual build mode for C/C++ analysis
- Uses security-extended query suite, triggers on push/PR to master plus weekly schedule
- Upgraded to CodeQL Action v4 with full SHA pins
- Permissions scoped to contents: read and security-events: write
- Pin runner to ubuntu-24.04, add setup-python for PEP 668 compatibility, add job timeout
2026-05-21 18:35:19 -07:00
Abhinav Agarwal 25b58aad4d Merge pull request #359 from libfuse/fix-null-deref-strict-warnings
fix null-deref warning in tokenize_on_space, promote strict-warnings to required
2026-05-21 17:28:01 -07:00
Abhinav Agarwal c71eea8e68 fix null-deref warning in tokenize_on_space and promote strict-warnings to required 2026-05-20 23:48:08 -07:00
Abhinav Agarwal afbdf92fa6 Merge pull request #353 from abhinavagarwal07/ci-werror-clang
ci: add gcc/clang matrix with -Werror
2026-05-20 23:41:00 -07:00
Abhinav Agarwal daac34f7c8 ci: add gcc/clang matrix with -Werror
- Expand build job into gcc/clang matrix with fail-fast: false
- Pass -Dwerror=true so compiler warnings fail the build
- Pin all actions to Node 24-capable full SHAs (checkout v6.0.2, setup-python v6.2.0, upload-artifact v7.0.1)
- Add least-privilege permissions, concurrency cancellation
- Pin python-version to 3.12, pin runner to ubuntu-24.04
- Add explicit SSH setup with sshd start, hard-fail FUSE preflight
- Add pytest --timeout=300, --maxfail=99, JUnit XML, test result artifacts per compiler
2026-05-20 23:28:17 -07:00
h4sh 88692b79eb Merge pull request #352 from abhinavagarwal07/ci-hygiene
ci: harden workflow with SHA pins, permissions, timeouts, and dependabot
2026-05-19 16:48:12 +10:00
Abhinav Agarwal 035dcde9ba ci: harden workflow with SHA pins, permissions, timeouts, and dependabot
- Pin all actions to Node 24-capable full SHAs (checkout v6.0.2, setup-python v6.2.0, upload-artifact v7.0.1)
- Add least-privilege permissions (contents: read) and concurrency cancellation
- Pin python-version to 3.12, pin runner to ubuntu-24.04
- Add pytest --timeout=300, --maxfail=99 (overrides pytest.ini -x), JUnit XML output
- Add explicit SSH setup with sshd start and connectivity preflight
- Hard-fail FUSE preflight (job stops if /dev/fuse or fusermount3 missing)
- Upload test results and meson logs as artifacts
- Add dependabot config for weekly action version updates
2026-05-18 21:04:29 -07:00
h4sh cc14ae1c79 Merge pull request #351 from abhinavagarwal07/fix-conntab-null-deref
fix conntab lookup after rename in sshfs_release
2026-05-18 12:39:06 +10:00
h4sh 78f807b3dd Merge pull request #350 from abhinavagarwal07/fix-statfs-div-by-zero
fix statfs divide-by-zero when blksize is 0
2026-05-18 12:38:39 +10:00
h4sh fd885f023c Merge pull request #349 from abhinavagarwal07/fix-sftp-read-buffer-safety
fix malformed SFTP reply handling
2026-05-18 12:37:56 +10:00
Abhinav Agarwal 033a1d48f5 fix conntab lookup after rename in sshfs_release
sshfs_release looked up the conntab entry by path, but sshfs_rename
moves that entry to the new path. Closing the original handle after a
rename can miss the entry and dereference NULL.

Store the conntab_entry pointer on sshfs_file and use it for release
and open-failure cleanup. Add a regression test for open -> rename -> close.
2026-05-17 18:58:06 -07:00
Abhinav Agarwal 82285f9c28 fix statfs divide-by-zero when blksize is 0
On macOS, sshfs.blksize is set to 0. The statfs fallback path
(used when the server lacks the statvfs extension) divides by
f_frsize, which inherits that zero. Use 4096 as the default.
2026-05-17 18:17:23 -07:00
Abhinav Agarwal 43b2708186 fix malformed SFTP reply handling 2026-05-17 18:12:57 -07:00
h4sh d749988c80 Merge pull request #348 from abhinavagarwal07/add-tests-round-2
add tests for error paths, option coverage, and fix existing test bugs
2026-05-18 07:28:44 +10:00
Abhinav Agarwal 7921230f83 add tests for error paths, option coverage, and fix existing test bugs 2026-05-16 22:48:04 -07:00
h4sh ea60e3466e Merge pull request #347 from abhinavagarwal07/add-tests-rename-chmod-fsync-statvfs
add tests for rename, chmod, fsync, and statvfs values
2026-05-16 06:18:44 +10:00
Abhinav Agarwal 033a88349c add tests for rename, chmod, fsync, and statvfs values 2026-05-15 11:27:49 -07:00
h4sh5 1cd6995713 remove basically unused -v and --verbose flag 2026-04-15 14:49:05 +10:00
h4sh5 9e35c39ba8 version bump and new changelog 2025-11-12 05:46:43 +10:00
h4sh bf540dd68b Merge pull request #336 from libfuse/ipv6-connect
ipv6 support for directport connection
2025-11-11 23:09:38 +10:00
h4sh5 882d3a0185 add documentation on bypassing SSH 2025-11-11 17:53:52 +10:00
h4sh5 113882adda ipv6 support for directport connection 2025-11-11 16:30:19 +10:00
Benjamin Fleischer ccb6821019 Disable macOS specific FUSE API extensions
Some macOS specific features require FUSE API modifications and
extensions that break compatibility with the vanilla FUSE API. Setting
the compile-time flag FUSE_DARWIN_ENABLE_EXTENSIONS to 0, when building
a file system, disables those API extensions. By default, the macOS
specific API modifications and extensions are enabled.
2025-03-09 00:10:32 +01:00
Benjamin Fleischer ed0825440c Mountpoint does not have to exist on macOS
macFUSE will create the mounpoint (in case it does not exist) before
mounting the volume. This allows unprivileged users to mount volumes
under /Volumes.
2025-03-09 00:10:32 +01:00
Benjamin Fleischer a9eb71cb1c Remove conditional fuse_darwin.h include
The header file is no longer available in macFUSE 4.
2025-03-09 00:10:32 +01:00
Benjamin Fleischer b1715a5a62 Remove fuse_opt compatibility code
The fuse_opt compatibility code is no longer needed on macOS. Remove
it since it is not included on any other platform.
2025-03-09 00:10:32 +01:00
h4sh5 21fdcad6fb bump to upload-artifact@v4 2025-03-09 08:58:15 +10:00
Haoxi Tan ef94977c5a Merge pull request #314 from manu0401/windows_openssh
Windows native OpenSSH fix
2024-11-30 08:25:19 +10:00
Emmanuel Dreyfus d78a624756 Windows native OpenSSH fix
Windows native OpenSSH has alternative behavior for standard I/O
descriptors, which can be selected through the OPENSSH_STDIO_MODE
environement variable. Setting it to "nonsock" is required for
sshfs compatibility.

See https://github.com/PowerShell/openssh-portable/pull/759
for details.
2024-11-20 16:00:40 +01:00
Haoxi Tan ddf1e42ce7 Merge pull request #306 from jpalus/fill-stat-info-from-cache
Fill stat info when returning cached data for readdir
2024-11-05 08:07:34 +10:00
Haoxi Tan 1a52814a4e Merge branch 'master' into fill-stat-info-from-cache 2024-11-05 08:05:23 +10:00
Haoxi Tan c9bf8ad451 Merge pull request #305 from jpalus/fix-link-leak
Fix memleak in cache after readlink
2024-11-05 08:01:26 +10:00
Jan Palus 5f767dec5b Fill stat info when returning cached data for readdir
Uncached and cached results for readdir were inconsistent -- the former
returned correct stat info for directory entries while the latter
didn't. That's because only names of entries were saved in cache without
stat info. In turn this leads to issues like
https://github.com/junegunn/fzf/issues/3832 since directory traversal
library (https://github.com/charlievieth/fastwalk in this case) relies
on proper stat info returned by readdir. Hence when unchached result was
returned it gave proper outcome, while with cached result it was wrong.

Cache stat info next to entry name to fix the issue. While file
attributes are saved in cache already, they use full path as key. To
avoid potentially plenty of allocations, string copying and cache
lookups to get each attr, let's keep a copy of stat struct independently
to be on the fast path.
2024-06-17 11:48:44 +02:00
Jan Palus 8bb9d33d16 Fix memleak in cache after readlink 2024-06-17 00:10:02 +02:00
Haoxi Tan eadf7f104a Merge pull request #295 from libfuse/rm-rst
remove README.rst file since README.md file exists
2024-02-27 14:09:05 +10:00
h4sh5 1af815b7dc remove README.rst file since README.md file exists 2024-02-27 14:04:42 +10:00
Thomas Merz 683b8c2454 👷 use latest major version for actions/checkout (#289) 2023-12-22 18:05:32 +00:00
Haoxi Tan 70c8fd9031 Merge pull request #277 from g-easy/master
Implement connect to vsock.
2023-11-06 19:55:14 +10:00
h4sh 47a580dd77 test improvements 2023-09-23 23:05:21 +10:00
h4sh e5f4fcaad7 github action build+test 2023-09-23 23:00:12 +10:00
Haoxi Tan 8c97da5b16 Update README.md 2023-09-23 22:52:14 +10:00
Haoxi Tan 551752c3a5 No longer orphaned 2023-09-23 22:51:16 +10:00
Nikolaus Rath c91eb9a9a9 Released 3.7.3 2022-05-26 14:23:35 +01:00
Peter Wienemann 103c6ba68b Fix typo: occured -> occurred (#280) 2022-03-07 19:41:43 +00:00
easy 3aa3efcb52 Implement connect to vsock.
"sshfs -o vsock=CID:PORT" will cause sshfs to connect directly to the
given vsock, bypassing ssh, and allowing high performance sshfs mounts
of a VM guest.
2022-02-15 14:45:26 +11:00
Matthew Berginski a2054a2b73 Typo Fix (#274) 2021-12-27 11:27:40 +00:00
Kim Brose fd02499b4c add missing backtick (#270) 2021-10-30 13:04:15 +01:00
Antonio Rojas c2715f7453 Fix typo in ssh_opts (#269)
Add a missing comma that prevents using the PubkeyAcceptedKeyTypes option
2021-10-06 08:19:22 +01:00
Nikolaus Rath 1abde6e779 Clarify the need for libfuse3. 2021-09-24 13:36:56 +01:00
Cam Cope a181b9b60b add .git-blame-ignore-revs (#261) 2021-08-30 18:08:07 +01:00
Cam Cope d54c7ecbd6 Fixup whitespace and configure CI to keep it that way 2021-08-30 15:35:33 +01:00
a1346054 803e0e65cf Fix script issues identified through shellcheck (#258)
* Fix spelling

* Fix shellcheck-identified warnings in shell scripts
2021-08-25 14:45:42 +02:00
Nikolaus Rath 9700b35370 Released 3.7.2 2021-06-08 09:52:08 +01:00
Andrew Stone 6c1b92df81 Fix deadlock in conn cleanup (#244)
Calling through to request_free() from clean_req() causes deadlock since
sshfs.lock is already held by the caller of clean_req().
2021-02-25 12:13:30 +00:00
Nikolaus Rath d18869a307 Update to newer CI environment.
The current one causes build failures since recent pytest versions are incompatble with
Python 3.5.
2021-01-19 10:17:10 +00:00
Peter Belm dfd4cba385 Workaround for mkdir on existing directory (#242)
Added a secondary check so if a mkdir request fails with EPERM an access request will be tried - returning EEXIST if the access was successful. This matches the correct behaviour expected by applications such as git.

Co-authored-by: Peter Belm <peter.belm@dataalchemist.co.uk>
2021-01-19 10:13:09 +00:00
Nikolaus Rath 8059e2ce63 Released 3.7.1 2020-11-09 09:52:00 +00:00
Junichi Uekawa 9e01ffd161 Rename option to 'passive' and add some example in manual. (#232) 2020-11-02 10:51:48 +00:00
Fabrice Fontaine de0504e45b sshfs.c: fix build with gcc 4.8 (#233)
Fix the following build failure with gcc 4.8:

../sshfs.c:1092:2: error: 'for' loop initial declarations are only allowed in C99 mode
  for (int i = 0; i < sshfs.max_conns; i++) {
  ^

This build failure has been added with
https://github.com/libfuse/sshfs/commit/8822b60d9dbd9907065e7999f616b11ddce6d584

Fixes:
 - http://autobuild.buildroot.org/results/2dbdc579c55543175716d5f739cabe2ad0864ed6

Signed-off-by: Fabrice Fontaine <fontaine.fabrice@gmail.com>
2020-11-02 09:31:23 +00:00
Junichi Uekawa 6625146af9 Update sshfs.rst (#220)
This / is probably meant to be a .
2020-09-04 12:06:30 +01:00
Simon Arlott 76ffb37444 Unset OLDPWD environment variable (#227)
If ssh is configured to use "Match exec" and the previous working
directory is the mount point, then the shell (bash) hangs calling
stat() on OLDPWD.

Unset OLDPWD so that this doesn't happen.

Fixes #206.
2020-09-03 08:13:24 +01:00
Nikolaus Rath a96e521474 Remove pointer to professional consulting offers
I do not want to offer this actively anymore.
2020-08-10 19:57:05 +01:00
bjoe2k4 11df8874dc Fix typo in docs (#213) 2020-05-17 10:30:59 +01:00
Nikolaus Rath a7e1038203 Released 3.7.0 2020-01-03 11:04:02 +00:00
Dominique Martinet 8340a67b31 sftp_init_reply_ok: fix small memory leak (#198)
The leak was identified with ASAN: configure the project with
meson -Db_sanitize=address to reproduce.
2019-11-30 11:42:40 +00:00
Nikolaus Rath ab0e339e80 Protect changes to conn->req_count with mutex. 2019-11-27 20:35:38 +00:00
Nikolaus Rath b19e3b8001 Fix memory leak in conntab
References need to be counted per-path, rather than per connection.
2019-11-27 20:35:38 +00:00
Nikolaus Rath e910453156 Disable buflimit workaround by default.
The corresponding bug in OpenSSH has been fixed in
2007 (cf. https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=365541#37), so this shouldn't
be needed anymore.
2019-11-27 10:30:35 +00:00
Nikolaus Rath 28c22270f8 Add missing mutex lock/unlocks when accessing conntab
(problem introduced in parent commit).
2019-11-27 09:42:30 +00:00
Timo Savola 8822b60d9d Add support for using multiple connections
The -o max_conns=N option causes multiple SSH processes and response processing threads to
be created. This means that e.g. reading a large file no longer blocks all access to the
filesystem.

The connection is chosen by checking the per-connection statistics:

  1. Choose connection with least outstanding requests; if it's a tie,
  2. choose connection with least directory handles; if it's a tie,
  3. choose connection with least file handles; if it's a tie,
  4. choose connection which has already been established.

The implementation assumes that the max_conns values will be small; it
uses linear search.

Example benchmark:

With single connection:

$ sshfs -o max_conns=1,workaround=nobuflimit ebox: mnt
$ cat mnt/tmp/bigfile > /dev/null &
$ time find mnt > /dev/null

real	1m50.432s
user	0m0.133s
sys	0m0.467s

With multiple connections:

$ ~/in-progress/sshfs/build/sshfs -o max_conns=5,workaround=nobuflimit ebox: mnt
$ cat mnt/tmp/bigfile > /dev/null &
$ time find mnt > /dev/null

real	1m15.338s
user	0m0.142s
sys	0m0.491s

This feature was implemented to large extend by Timo Savola <timo.savola@iki.fi>. Thanks
to CEA.fr for sponsoring the remaining work to complete this feature and integrate it into
SSHFS!
2019-11-24 12:01:01 +00:00
Nikolaus Rath 0f3ab4fd4f Drop reference counter for struct sshfs_file
Variables of this kind are created in sshfs_open_common() and freed
in sshfs_release(). Since sshfs_release() calls sshfs_flush(), there
can be no pending write requests before at the time of freeing, so
there is no need for reference counting.
2019-11-24 10:23:42 +00:00
Nikolaus Rath ddd968b025 Do not fail tests if connecting to localhost for first time. 2019-11-24 10:19:13 +00:00
Nikolaus Rath 2421675f3f Added build/ to gitignore.
This is the default build directory.
2019-11-23 20:58:41 +00:00
jeg139 eade8feb05 fix some whitespace and indentation (#194) 2019-11-23 11:07:19 +00:00
Michael Forney 4d866526dc Fix some inconsistent whitespace (#192) 2019-11-23 11:06:05 +00:00
Nikolaus Rath 6935b49eea Add documentation of permission handling to manpage.
Fixes: #182
2019-11-03 13:39:23 +00:00
Nikolaus Rath a548abd1f3 Fix markup. 2019-11-03 09:38:31 +00:00
Nikolaus Rath a1e5f12c53 Released 3.6.0 2019-11-03 09:34:29 +00:00
kalvdans 011986306b Remedy gcc 7.4.0 warnings (#187)
* Use logical not for booleans

Fixes a gcc 7.4.0 complaint:

sshfs.c:1743:28: warning: ‘~’ on a boolean expression [-Wbool-operation]
sshfs.c:1743:28: note: did you mean to use logical not?

* strdup argument to keep it alive after function return

Fixes gcc 7.4.0 complaint:

sshfs.c: In function ‘sshfs_opt_proc’:
sshfs.c:3488:50: warning: assignment discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
2019-11-03 09:28:36 +00:00
sunwire e1a9050c61 Added direct_io option (#173) 2019-10-19 20:26:10 +01:00
kalvdans af63f35ac1 Free addrinfo and close socket when returning error (#186) 2019-10-19 20:23:45 +01:00
Viktor Szakats 667cf34622 sshfs: fix another instance preventing use of global I/O size on macOS (#185)
Following-up on [1], there was another instance where blksize
was set to a non-zero value, thus making it impossible to
configure global I/O size on macOS, and using [2] the hard-wired
value of 4096 bytes instead, resulting in uniformly poor
performance [3].

With this patch, setting I/O size to a reasonable large value,
will result in much improved performance, e.g.:
  -o iosize=1048576

[1] https://github.com/osxfuse/sshfs/commit/5c0dbfe3eb40100f9277e863926f2e7d7c9a5a4c
[2] https://github.com/libfuse/sshfs/blob/4c21d696e9d46bebae0a936e2aec72326c5954ea/sshfs.c#L812
[3] https://github.com/libfuse/sshfs/issues/11#issuecomment-339407557
2019-09-12 05:33:32 -07:00
Michael Forney 4c21d696e9 Include poll.h instead of sys/poll.h (#178)
The standard header for the poll(3) interface is poll.h[0]. This prevents
a warning when building with musl libc:

    In file included from sshfs.c:44:
    /usr/include/sys/poll.h:1:2: warning: #warning redirecting incorrect #include <sys/poll.h> to <poll.h> [-Wcpp]
     #warning redirecting incorrect #include <sys/poll.h> to <poll.h>
      ^~~~~~~

[0] https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/poll.h.html
2019-06-30 10:12:19 +01:00
Galen Getsov 469c96b6d2 Add --verbose option 2019-06-30 10:10:41 +01:00
Nikolaus Rath c4485188c9 Update issue templates 2019-04-30 06:06:39 -07:00
Nikolaus Rath 4dc9b612ec Documented development status. 2019-04-29 11:58:52 -07:00
Nikolaus Rath 695cd8916f Released 3.5.2 2019-04-13 09:25:45 +01:00
mssalvatore 7364b73e80 Gracefully handle multiple spaces in ssh_command option (#169)
When using the "ssh_command" option, commands with multiple spaces in a
row will not be properly parsed. Example:

Properly parsed:
    ssh_command = "ssh -o IdentityFile=~/.ssh/id_rsa"

Improperly parsed:
    ssh_command = "ssh -o      IdentityFile=~/.ssh/id_rsa"

This commit changes the ssh_command parsing logic so that both of the
above examples are considered valid and properly handled. 

Fixes: #114.
2019-04-08 21:50:10 +01:00
DrDaveD 1dbc89f959 allow /dev/fd/N as a mountpoint (#166) 2019-03-31 04:57:21 -04:00
Nikolaus Rath d299217510 Travis CI: Use Xenial instead of Trusty. 2019-02-27 21:27:39 +00:00
mssalvatore fda6c8f862 Update clean_req() to match the definition of GHRFunc (#160)
The current definition of the clean_req() function produces a compiler
warning as it expects 2 parameters but GHRFunc expects 3. This commit
resolves issue #157.
2019-02-25 21:00:53 +00:00
Clayton G. Hobbs 6b10b3c8c0 Also remap GID under non-MacOS
The manpage says that -o idmap=user maps the UID and GID, but it
apparently only mapped the UID.  The code was in place to map the GID as
well, but it was hidden behind #ifdef __APPLE__.  This commit removes
those #ifdefs, and in a couple of cases the code inside an #else, to
make the option behave as documented.
2019-01-04 19:39:47 +00:00
Nikolaus Rath d3c6c338ae Bump meson dependency
build_by_default arg in custom_target needs 0.40.0
2018-12-22 14:34:39 +00:00
Nikolaus Rath 93c052a3cd Released 3.5.1 2018-12-22 14:33:52 +00:00
Nikolaus Rath 76d11fa6f4 Use fusermount3 instead of fusermount
We're using libfuse3, so we should be using its fusermount command.
2018-12-22 14:31:52 +00:00
Nikolaus Rath 9ced6ca681 Kill filesystem process on test cleanup. 2018-12-22 14:31:52 +00:00
Tim Harder de11ec091e meson: fallback to looking for rst2man.py
As that's what upstream docutils installs by default.
2018-12-22 14:22:24 +00:00
smheidrich 37b79d06f8 Docs: add section on interrupted connections
Also rename "SSHFS hangs" section to something more specific to
differentiate it from this new section.

Cf. issues #77 and #3.
2018-12-22 14:22:01 +00:00
S. D. Cloudt 2f1147c7d6 Added "BindInterface" as valid "-o" option. 2018-12-22 14:20:54 +00:00
Nikolaus Rath b5d1484314 Released 3.5.0 2018-08-28 14:58:39 +01:00
Bill Zissimopoulos d0c5fa3ed6 Add AppVeyor CI for Cygwin 2018-08-11 10:33:42 +01:00
Bill Zissimopoulos eac420791c Port SSHFS to Cygwin 2018-08-01 14:39:23 +01:00
Quentin Rameau 6480b66bd6 Fix error return value from rename()
Fuse operations expect negated errno values.
2018-07-25 11:03:01 +01:00
Nikolaus Rath 30a2668b99 Released 3.4.0 2018-06-29 09:34:57 +01:00
Samuel Murray 4ed15c4c04 Update README.rst
OS-X is now called macOS
2018-06-28 20:08:50 +01:00
Rian Hunter fc7a57f0dc Correct workaround condition for not using handle in sshfs_getattr (#127)
In libfuse<3, when `fstat_workaround` was true, that meant to always
use the `path` argument to resolve fgetattr instead of the supplied
handle.  Before this change, the logic was interpreting
`fstat_workaround` to not use the `path` argument when it was
true. This change reverts to the libfuse<3 behavior.
2018-06-20 20:31:53 +01:00
Daniel Lublin f0452119e0 Make utimens(NULL) work correctly 2018-06-18 19:12:38 +01:00
Josh Triplett fb174704dd Add workaround to always pass a 0 mode when creating a file (#128)
Add workaround to always pass a 0 mode when creating a file

This works around servers that produce an error for any non-zero mode.
2018-06-09 11:28:55 +01:00
Nikolaus Rath 39663c8288 Released 3.3.2 2018-04-29 18:06:03 +01:00
Nikolaus Rath 646a3c417b Fix Travis build environment.
Newest Meson requires Python 3.5.which isn't available in Trusty.
Pip version pin no longer necessary.
2018-03-31 13:26:15 +01:00
G.raud Meyer 303126bad9 New workaround renamexdev to enable moving files across remote filesystems
sshfs.rst: update the documentation.
2018-03-28 19:48:02 +01:00
Nikolaus Rath f0a0cc2b7e Fix rst markup 2017-11-27 19:55:09 +00:00
Brandon Carter 2c7c667576 change make directory command in readme 2017-11-17 18:17:41 +00:00
Nikolaus Rath 1846b685c4 Updated professional support information. 2017-11-13 20:09:54 +00:00
Nikolaus Rath 7f56cfc602 Released 3.3.1 2017-10-25 19:32:34 +01:00
Nikolaus Rath fd3aa6594f meson.build: don't use cp -p instead of --preserve-mode
The latter is not supported on OS X.
2017-10-25 19:30:34 +01:00
Timo Savola 2731a001e5 Fix memory leak on opendir error 2017-10-15 14:31:56 +01:00
Nikolaus Rath 608e7ab89c Ignore fstab-specific mount options
Fixes: #96.
2017-09-26 12:41:30 +01:00
Nikolaus Rath a73dda48bd Install manpage in man1/, not 1/
Fixes: #95.
2017-09-22 10:35:52 +01:00
Nikolaus Rath 7c2550a7e4 Don't attempt to remove non-existing file. 2017-09-20 18:09:59 +01:00
Nikolaus Rath 71aa3b97f6 Released 3.3.0 2017-09-20 18:08:27 +01:00
Nikolaus Rath 14636931ac Add support for printing release contributors. 2017-09-20 18:07:29 +01:00
Nikolaus Rath d193b19a8f Dropped support for writeback caching
As of kernel 4.14, the FUSE module's + writeback implementation is not
compatible with network filesystems, and there are no imminent plans
to change that.

For more details, see
https://marc.info/?l=fuse-devel&m=150592103107662&w=2 or

As a consequence, the -o unreliable_append option has become obsolete
as well.

Fixes: #93
Fixes: #88
Fixes: #81
2017-09-20 17:56:44 +01:00
Nikolaus Rath 949d76d1a2 Add support for mounting from /etc/fstab
Fixes: #92.
2017-09-20 14:43:34 +01:00
Nikolaus Rath 848ff30d90 Dropped FAQ file
Most entries were obsolete. Useful content has been moved to the man
page.
2017-09-20 14:43:34 +01:00
Nikolaus Rath 37c8c1c80b Dropped support for autotools build
It's just too much of a pain to maintain.
2017-09-20 14:43:31 +01:00
Nikolaus Rath 85b950d3c6 Updated manual page with missing options.
Fixes: #82.
2017-09-20 13:32:33 +01:00
Nikolaus Rath b32c4a4ca1 tst_link(): fix test failure 2017-09-20 12:06:31 +01:00
Nikolaus Rath bc47eeb68c travis: fix build procedure. 2017-09-20 11:58:43 +01:00
Nikolaus Rath e4de7d257f Improve documentation for symlink handling options.
Fixes: #51.
2017-09-19 22:34:50 +01:00
Nikolaus Rath 2a818faea5 Use 'buildtype' to set debug/optimization options
Until now, running `mesonconf -D buildtype=debug` didn't have any
effect - which was confusing.
2017-08-22 11:09:03 +02:00
Nikolaus Rath 48cf30d3e2 Converted manpage to Restructured Text 2017-08-18 10:58:47 +02:00
Anatol Pomozov 1f20b5dff5 Bump version number 2017-08-14 10:28:29 +02:00
Nikolaus Rath 5d64c08f69 Released 3.2.0 2017-08-06 12:38:50 +02:00
Nikolaus Rath 586ad60414 Drop -u option to cp
Not supported on OS X, and not strictly necessary.

Fixes: #64.
2017-08-06 11:51:06 +02:00
harrim4n 0bb569557e removed obsolete answers from FAQ 2017-08-06 11:35:37 +02:00
harrim4n da3a3f5bd1 Removed obsolete options from man page 2017-08-06 11:35:37 +02:00
Nikolaus Rath d141ea44c1 Re-enabled writeback cache.
Fixes: #72.
2017-08-06 11:07:23 +02:00
Nikolaus Rath 6ac4046bee Support O_APPEND.
Fixes: #76.
2017-08-06 11:07:23 +02:00
Nikolaus Rath 95c65a6246 Added seek and append tests. 2017-08-06 11:07:23 +02:00
Nikolaus Rath f8df458f23 Actually disable writeback cache, instead of just claiming to do so. 2017-08-06 11:07:23 +02:00
Nikolaus Rath e2a4bc904e Run tests with and without writeback cache. 2017-08-06 10:22:59 +02:00
Nikolaus Rath 70acee8c4b Include ChangeLog.rst in release tarballs. 2017-08-06 10:21:48 +02:00
Nikolaus Rath fc63c64602 Released 3.1.0 2017-08-04 15:42:38 +02:00
Nikolaus Rath 1e6e067fdf Disable writeback cache for now
Writeback cache seems to cause dataloss in some situations.
We need to investigate this first.

See https://github.com/libfuse/sshfs/issues/72.
2017-08-04 15:39:25 +02:00
Nikolaus Rath 44a56f0cac Removed unused variable. 2017-08-04 15:39:16 +02:00
Nikolaus Rath 653de120c8 Don't check st_mode of mountpoint
It is not clear what this check is supposed to achieve, and it seems to
fail in some situations (cf issue #57).

Fixes: #57.
2017-08-03 18:48:37 +02:00
Nikolaus Rath 63300fd564 Accept -o cache_* options for backward compatibility.
Fixes: #73.
2017-08-03 18:26:19 +02:00
Nikolaus Rath 45710c81d7 Travis: use alternative workaround suggested by support. 2017-07-27 21:21:46 +02:00
Nikolaus Rath 161ece55a3 Travis: disable trusty workaround
According to Travis support, this should be working now. We will see...
2017-07-27 19:15:30 +02:00
Benjamin Fleischer db149d1d87 Fall back to global I/O size on macOS
The st_blksize value of struct stat represents the optimal block size
for file I/O operations. FUSE for macOS will use this value when
preforming read or write operations on the file. The smaller st_blksize
is the more context switches are required to complete the operation.

Setting st_blksize to 0 results in FUSE for macOS falling back to the
global I/O size, that can be specified through the "-o iosize=..."
mount-time option.

Fixes osxfuse/osxfuse#389 and osxfuse/sshfs#33
2017-07-13 16:06:05 +02:00
Benjamin Fleischer 861d308a03 Don't require mount point to exists on macOS
By default volumes are mounted under /Volumes on macOS. Since macOS
10.12 the /Volumes directory is root-owned. In order to allow non-
privileged users to mount FUSE volumes under /Volumes FUSE will create
non-existent mount points automatically.

Fixes osxfuse/sshfs#27
2017-07-13 16:04:12 +02:00
Nikolaus Rath 319f0dde18 sshfs_open_commin(): fix compiler warning
wrctr cannot be used without having been initialized,
but the compiler is too dumb to see that.
2017-07-12 16:46:02 +02:00
Nikolaus Rath 82766d1093 sftp_readdir_async(): don't access request when it may have been freed
Fixes: #7
2017-07-12 16:45:17 +02:00
Nikolaus Rath 6f6491cd13 Travis: add /usr/local/lib* to ld configuration 2017-07-08 13:30:44 +02:00
Nikolaus Rath 03a693d295 Travis: install libfuse from git master 2017-07-08 13:23:18 +02:00
Nikolaus Rath 55eb1f00e6 Travis: Build libfuse with gcc-6
gcc-4.8 has trouble with libfuse 3.1.0 symbol versioning.
2017-07-08 13:14:21 +02:00
Nikolaus Rath 37b6969067 Travis: use libfuse 3.1.0 2017-07-08 12:59:20 +02:00
Nikolaus Rath ff6194b5c7 Released 3.0.0 2017-07-08 12:47:09 +02:00
Nikolaus Rath af31551e18 Include test files in "make dist" 2017-07-08 12:43:18 +02:00
Nikolaus Rath 82e899fb21 Switch to libfuse 3.1.0
libfuse 3.0.0 is rather new and not widely used yet, so requiring
libfuse 3.1.0 right away shouldn't be a problem.
2017-07-08 12:26:45 +02:00
Nikolaus Rath 34146444ce Switch to libfuse 3.0. 2017-07-08 12:25:13 +02:00
Nikolaus Rath 6cc86fc0bd Abort on glib assertion errors. 2017-07-08 00:37:25 +02:00
Nikolaus Rath 73e4cf79b0 Travis: use sudo-enabled environment. 2017-07-08 00:34:03 +02:00
Nikolaus Rath a202e2dcc8 Added information about professional support. 2017-07-08 00:34:02 +02:00
Nikolaus Rath c711bf738f Re-enabled cache support. 2017-07-08 00:34:01 +02:00
Nikolaus Rath 493081e8f6 Split readdir() into opendir(), readdir() and releasedir()
This re-enables support for nullpath_ok. The cache remains disabled.
2017-06-22 14:22:58 -07:00
Nikolaus Rath 3033dbc22e Transition from getdir() to readdir(), temporarily disable cache
This commit enables the use of readdir() instead of getdir(). It also
completely disables the cache and the nullpath_ok feature. This will
be fixed in the next commits.
2017-06-22 14:22:58 -07:00
Nikolaus Rath 5f4619bac3 Added unit tests and travis integration 2017-06-22 14:22:57 -07:00
Nikolaus Rath b66ecb9c3a Fix Meson manpage install
Without this, Meson is looking for sshfs.1 in the source
directory. However, since that is a generated file it is in the build
directory.
2017-06-22 14:21:33 -07:00
Nikolaus Rath 9ac5829b89 Compile with -g -O2 by default. 2017-06-22 14:21:07 -07:00
Nikolaus Rath 9cfeaf5120 Added .rst suffix to ChangeLog.
That way, programs are more likely to highlight the file correctly.
2017-06-20 16:39:18 -07:00
Nikolaus Rath 56d6030f9c Document hardlink limitation. 2017-06-20 16:39:16 -07:00
Nikolaus Rath 152617e53f Converted README from Markdown to Restructured Text. 2017-06-20 16:38:46 -07:00
Nikolaus Rath 0908f570dc Dropped obsolete FUSE 2.x #ifdefs 2017-06-20 11:29:19 -07:00
Nikolaus Rath 5bc9ff371d Added .dir-locals.el to setup correct indentation in Emacs 2017-06-19 15:18:11 -07:00
Nikolaus Rath 4379773a9e Updated mailing list information. 2017-06-08 16:53:26 -04:00
Nikolaus Rath 6056149d16 Fixed compiler warnings 2017-06-07 13:42:34 -04:00
Nikolaus Rath 53a14aeaf1 Dropped support for libfuse < 2.9
This is in preparation for the move to libfuse 3.0
2017-06-07 13:42:34 -04:00
Nikolaus Rath 8999210c93 Remove support for "all" workarounds
This should never be used. We may add new workarounds in the future,
and they should only be enabled explicitly when needed.
2017-06-06 16:45:15 -04:00
Nikolaus Rath 5ed2cf8a8b Add fstat workaround to help output. 2017-06-06 16:45:15 -04:00
Nikolaus Rath 9f2eb04371 Document rename workaround. 2017-06-06 16:45:15 -04:00
Nikolaus Rath 6c301e043b Dropped nodelaysrv workaround
The same effect (enabling NODELAY on the server side *and* enabling
X11 forwarding) can be achieved by explicitly passing `-o ForwardX11`
2017-06-06 16:45:15 -04:00
Nikolaus Rath 6d7ce1607a Added support for building with Meson. 2017-06-06 16:45:15 -04:00
Nikolaus Rath 5659d0d2ea Don't call g_thread_init()
No longer required since glib 2.32.
2017-06-06 11:03:14 -04:00
Nikolaus Rath fe3ce10ff8 Fixed compiler warning 2017-06-06 11:02:11 -04:00
Nikolaus Rath 132dd88755 Dropped nodelay workaround
This was a performance improvement for SSH versions prior to
4.4. There is no reason to continue to support this.
2017-06-06 11:01:21 -04:00
Nikolaus Rath af1f3c6235 Fixed Markdown markup. 2017-06-06 10:05:21 -04:00
Nikolaus Rath 8cc069f666 Reworked README
Now also includes mailing list.
2017-06-06 10:03:15 -04:00
Nikolaus Rath d7cb098f1d main(): setup signal handlers before connecting
Otherwise, aborting the password prompt with Ctrl+C will
also kill the sshfs process and leave the mountpoint inaccessible.

Fixes: #9.
2017-06-05 17:28:44 -04:00
Nikolaus Rath c7d8500634 Added changelog entry for commit d93f1. 2017-05-31 13:54:18 -07:00
Nikolaus Rath 2896aed825 Merge branch 'master' of github.com:libfuse/sshfs 2017-05-31 13:52:44 -07:00
tpoindessous 110163d8d1 Update README.md for macOS
On macOS, we don't have fusermount, so we use umount command.

Thanks.
2017-05-31 13:51:30 -07:00
Jakub Jelen d93f137c8f Update the missing options (that make sense from ssh_config) 2017-05-31 13:51:09 -07:00
40 changed files with 3798 additions and 2510 deletions
+19
View File
@@ -0,0 +1,19 @@
version: '{build}'
install:
# install WinFsp
- appveyor DownloadFile https://github.com/billziss-gh/winfsp/releases/download/v1.4B2/winfsp-1.4.18211.msi
- for %%f in ("winfsp-*.msi") do start /wait msiexec /i %%f /qn INSTALLLEVEL=1000
# install FUSE for Cygwin (64-bit and 32-bit)
- C:\cygwin64\bin\env.exe -i PATH=/bin bash "%ProgramFiles(x86)%\WinFsp\opt\cygfuse\install.sh"
- C:\cygwin\bin\env.exe -i PATH=/bin bash "%ProgramFiles(x86)%\WinFsp\opt\cygfuse\install.sh"
# install additional Cygwin packages (64-bit and 32-bit)
- C:\cygwin64\setup-x86_64.exe -qnNdO -R C:\cygwin64 -s http://cygwin.mirror.constant.com -l C:\cygwin64\var\cache\setup -P libglib2.0-devel -P meson
- C:\cygwin\setup-x86.exe -qnNdO -R C:\cygwin -s http://cygwin.mirror.constant.com -l C:\cygwin\var\cache\setup -P libglib2.0-devel -P meson
build_script:
- C:\cygwin64\bin\env.exe -i PATH=/bin bash test\appveyor-build.sh
- C:\cygwin\bin\env.exe -i PATH=/bin bash test\appveyor-build.sh
+25
View File
@@ -0,0 +1,25 @@
((python-mode . ((indent-tabs-mode . nil)))
(autoconf-mode . ((indent-tabs-mode . t)))
(c-mode . ((c-file-style . "stroustrup")
(indent-tabs-mode . t)
(tab-width . 8)
(c-basic-offset . 8)
(c-file-offsets .
((block-close . 0)
(brace-list-close . 0)
(brace-list-entry . 0)
(brace-list-intro . +)
(case-label . 0)
(class-close . 0)
(defun-block-intro . +)
(defun-close . 0)
(defun-open . 0)
(else-clause . 0)
(inclass . +)
(label . 0)
(statement . 0)
(statement-block-intro . +)
(statement-case-intro . +)
(statement-cont . +)
(substatement . +)
(topmost-intro . 0))))))
+1
View File
@@ -0,0 +1 @@
d54c7ecbd618afb4df524e0d96dec7fe7cc2935d
+17
View File
@@ -0,0 +1,17 @@
---
name: Issue report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
PLEASE READ BEFORE REPORTING AN ISSUE
SSHFS does not have any active, regular contributors or developers. The current maintainer continues to apply pull requests and tries to make regular releases, but unfortunately has no capacity to do any development beyond addressing high-impact issues. When reporting bugs, please understand that unless you are including a pull request or are reporting a critical issue, you will probably not get a response.
To prevent the issue tracker from being flooded with issues that no-one is intending to work on, and to give more visibility to critical issues that users should be aware of and that most urgently need attention, I will also close most bug reports once they've been inactive for a while.
Please note that this isn't meant to imply that you haven't found a bug - you most likely have and I'm grateful that you took the time to report it. Unfortunately, SSHFS is a purely volunteer driven project,
and at the moment there simply aren't any volunteers.
+10
View File
@@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
groups:
github-actions:
patterns:
- "*"
+143
View File
@@ -0,0 +1,143 @@
name: build and test
on:
push:
pull_request:
workflow_dispatch: # this is a nice option that will enable a button w/ inputs
inputs:
git-ref:
description: Git Ref (Optional)
required: false
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build-and-test:
name: ${{ matrix.compiler }} / ${{ matrix.buildtype }}
runs-on: ubuntu-24.04
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
buildtype: [debugoptimized, release]
include:
- compiler: gcc
cc: gcc
- compiler: clang
cc: clang
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.12'
- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y gcc clang ninja-build libglib2.0-dev libfuse3-dev openssh-server openssh-client fuse3
- name: Install meson
run: pip3 install meson pytest pytest-timeout
- name: Print tool versions
run: |
${{ matrix.cc }} --version
meson --version
- name: Setup SSH
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -q -N ""
cat ~/.ssh/id_ed25519.pub > ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
sudo systemctl start ssh || sudo service ssh start
ssh -o StrictHostKeyChecking=no -o BatchMode=yes localhost true
- name: Check FUSE availability
run: |
test -e /dev/fuse
command -v fusermount3
- name: Build
env:
CC: ${{ matrix.cc }}
run: |
meson setup build --buildtype=${{ matrix.buildtype }} -Dwerror=true
ninja -C build
- name: Upload build artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sshfs-${{ matrix.compiler }}-${{ matrix.buildtype }}
path: build/sshfs
if-no-files-found: ignore
- name: Run tests
timeout-minutes: 20
run: |
cd build
python3 -m pytest --maxfail=99 --timeout=300 --junitxml=test-results.xml test/
- name: Upload test results
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
with:
name: test-results-${{ matrix.compiler }}-${{ matrix.buildtype }}
path: |
build/test-results.xml
build/meson-logs/
strict-warnings:
name: ${{ matrix.compiler }} / strict warnings
runs-on: ubuntu-24.04
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
include:
- compiler: gcc
cc: gcc
extra_cflags: "-Wformat=2 -Wformat-security -Wundef -Wstrict-prototypes -Wold-style-definition -Wmissing-prototypes -Wnull-dereference"
- compiler: clang
cc: clang
extra_cflags: "-Wformat=2 -Wformat-security -Wundef -Wstrict-prototypes -Wold-style-definition -Wmissing-prototypes -Wnull-dereference"
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.12'
- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y gcc clang ninja-build libglib2.0-dev libfuse3-dev
- name: Install meson
run: pip3 install meson
- name: Print tool versions
run: |
${{ matrix.cc }} --version
meson --version
- name: Build with strict warnings
env:
CC: ${{ matrix.cc }}
CFLAGS: ${{ matrix.extra_cflags }}
run: |
meson setup build -Dwerror=true
ninja -C build
+51
View File
@@ -0,0 +1,51 @@
name: CodeQL
on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
security-events: write
jobs:
analyze:
name: Analyze C
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.12'
- name: Initialize CodeQL
uses: github/codeql-action/init@bc0b696b4103f5fe60f15749af68a046868d511a # codeql-bundle-v2.25.4
with:
languages: c-cpp
build-mode: manual
queries: security-extended
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y gcc ninja-build pkg-config libglib2.0-dev libfuse3-dev
pip3 install meson
- name: Build
run: |
meson setup build --buildtype=debug
ninja -C build sshfs
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@bc0b696b4103f5fe60f15749af68a046868d511a # codeql-bundle-v2.25.4
+2 -2
View File
@@ -6,8 +6,6 @@
# NOTE! Please use 'git ls-files -i --exclude-standard'
# command after changing this file, to see if there are
# any tracked files which get ignored after the change.
.*
!.gitignore
*.o
*.lo
*.la
@@ -35,3 +33,5 @@ sshfs.1
/.pc
/patches
/m4
.deps/
/build
+14
View File
@@ -0,0 +1,14 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/jumanjihouse/pre-commit-hooks
rev: 2.1.5
hooks:
- id: shellcheck
+31
View File
@@ -0,0 +1,31 @@
dist: focal
language: c
cache:
- pip
addons:
apt:
packages:
- shellcheck
- valgrind
- gcc
- clang
- python-docutils
- python3-pip
- python3-setuptools
- ninja-build
- meson
- python3-pytest
- libglib2.0-dev
install: test/travis-install.sh
jobs:
include:
- name: Lint
script: ./test/lint.sh
install: skip
- name: Build + Test
script: test/travis-build.sh
+42 -1
View File
@@ -1,33 +1,74 @@
Current Maintainer
------------------
Nikolaus Rath <Nikolaus@rath.org>
None.
Past Maintainers
----------------
Nikolaus Rath <Nikolaus@rath.org> (until 05/2022)
Miklos Szeredi <miklos@szeredi.hu> (until 12/2015)
Contributors (autogenerated list)
---------------------------------
a1346054 <36859588+a1346054@users.noreply.github.com>
Alan Jenkins <alan.christopher.jenkins@gmail.com>
Alexander Neumann <alexander@bumpern.de>
Anatol Pomozov <anatol.pomozov@gmail.com>
Andrew Stone <a@stne.dev>
Antonio Rojas <arojas@archlinux.org>
Benjamin Fleischer <fleiben@gmail.com>
Berserker <berserker.troll@yandex.com>
Bill Zissimopoulos <billziss@navimatics.com>
bjoe2k4 <bjoe2k4@users.noreply.github.com>
Brandon Carter <b-carter@users.noreply.github.com>
Cam Cope <github@camcope.me>
Chris Wolfe <cwolfe@chromium.org>
Clayton G. Hobbs <clay@lakeserv.net>
Daniel Lublin <daniel@lublin.se>
Dominique Martinet <asmadeus@codewreck.org>
DrDaveD <2129743+DrDaveD@users.noreply.github.com>
Fabrice Fontaine <fontaine.fabrice@gmail.com>
gala <gala132@users.noreply.github.com>
Galen Getsov <4815620+ggetsov@users.noreply.github.com>
George Vlahavas <vlahavas@gmail.com>
G.raud Meyer <graud@gmx.com>
harrim4n <git@harrim4n.com>
Jakub Jelen <jjelen@redhat.com>
jeg139 <54814784+jeg139@users.noreply.github.com>
Josh Triplett <josh@joshtriplett.org>
Julio Merino <jmmv@google.com>
Julio Merino <jmmv@meroh.net>
Junichi Uekawa <dancerj@gmail.com>
Junichi Uekawa <dancer@netfort.gr.jp>
kalvdans <github@kalvdans.no-ip.org>
Kim Brose <kim.brose@rwth-aachen.de>
Matthew Berginski <matthew.berginski@gmail.com>
Michael Forney <mforney@mforney.org>
Mike Kelly <mike@pair.com>
Mike Salvatore <mike.s.salvatore@gmail.com>
Miklos Szeredi <miklos@szeredi.hu>
Miklos Szeredi <mszeredi@suse.cz>
mssalvatore <mike.s.salvatore@gmail.com>
Nikolaus Rath <Nikolaus@rath.org>
Percy Jahn <email@percyjahn.de>
Peter Belm <peterbelm@gmail.com>
Peter Wienemann <peter.wienemann@uni-bonn.de>
Qais Patankar <qaisjp@gmail.com>
Quentin Rameau <quinq@fifth.space>
Reid Wagner <wagnerreid@gmail.com>
Rian Hunter <rian@alum.mit.edu>
Rian Hunter <rianhunter@users.noreply.github.com>
Samuel Murray <samuel.murray@outlook.com>
S. D. Cloudt <s.d.cloudt@student.tue.nl>
Simon Arlott <70171+nomis@users.noreply.github.com>
smheidrich <smheidrich@weltenfunktion.de>
sunwire <50745572+sunwire@users.noreply.github.com>
Tim Harder <radhermit@gmail.com>
Timo Savola <timo.savola@iki.fi>
tpoindessous <thomas@poindessous.com>
Viktor Szakats <vszakats@users.noreply.github.com>
Zoltan Kuscsik <zoltan.kuscsik@linaro.org>
-160
View File
@@ -1,160 +0,0 @@
Release 2.9 (2017-04-17)
------------------------
* Improved support for Cygwin.
* Various small bugfixes.
Release 2.8 (2016-06-22)
------------------------
* Added support for the "fsync" extension.
* Fixed a build problem with bitbake
Release 2.7 (2016-03-01)
------------------------
* Integrated osxfuse's copy of sshfs, which means that sshfs now works
on OS X out of the box.
* Added -o cache_max_size=N option to let users tune the maximum size of
the cache in number of entries.
* Added -o cache_clean_interval=N and -o cache_min_clean_interval=N
options to let users tune the cleaning behavior of the cache.
Release 2.6 (2015-01-28)
------------------------
* New maintainer (Nikolaus Rath <Nikolaus@rath.org>)
Release 2.5 (2014-01-14)
------------------------
* Some performance improvements for large directories.
* New `disable_hardlink` option.
* Various small bugfixes.
Release 2.4 (2012-03-08)
------------------------
* New `slave` option.
* New `idmap`, `uidmap` and `gidmap` options.
* Various small bugfixes.
Release 2.3 (2011-07-01)
------------------------
* Support hard link creation if server is OpenSSH 5.7 or later
* Small improvements and bug fixes
* Check mount point and options before connecting to ssh server
* New 'delay_connect' option
Release 2.2 (2008-10-20)
------------------------
* Handle numerical IPv6 addresses enclosed in square brackets
* Handle commas in usernames
Release 2.1 (2008-07-11)
------------------------
* Small improvements and bug fixes
Release 2.0 (2008-04-23)
------------------------
* Support password authentication with pam_mount
* Support atomic renames if server is OpenSSH 4.9 or later
* Support getting disk usage if server is OpenSSH 5.1 or later
* Small enhancements and bug fixes
What is new in 1.9
------------------
* Fix a serious bug, that could result in sshfs hanging, crashing, or
reporting out-of-memory
What is new in 1.8
------------------
* Bug fixes
What is new in 1.7
------------------
* Tolerate servers which print a banner on login
* Small improvements
What is new in 1.6
------------------
* Workaround for missing truncate operation on old sftp servers
* Bug fixes
What is new in 1.5
------------------
* Improvements to read performance. Now both read and write
throughput should be very close to 'scp'
* If used with FUSE 2.6.0 or later, then perform better data caching.
This should show dramatic speed improvements when a file is opened
more than once
* Bug fixes
What is new in 1.4
------------------
* Updated to version 25 of libfuse API
* This means that the 'cp' of readonly file to sshfs bug is finally
solved (as long as using libfuse 2.5.0 or later *and* Linux 2.6.15
or later)
* Sshfs now works on FreeBSD
* Added option to "transform" absolute symbolic links
What is new in 1.3
------------------
* Add workaround for failure to rename to an existing file
* Simple user ID mapping
* Estimate disk usage of files based on size
* Report "infinite" disk space
* Bug fixes
What is new in 1.2
------------------
* Better compatibility with different sftp servers
* Automatic reconnect (optional)
What is new in 1.1
------------------
* Performance improvements:
- directory content caching
- symlink caching
- asynchronous writeback
- readahead
* Fixed '-p' option
What is new in 1.0
------------------
* Initial release
+317
View File
@@ -0,0 +1,317 @@
Release 3.7.5 (2025-11-11)
--------------------------
* New maintainers added
* Main documentation updates to using Markdown
* Updated Windows and macOS support
* Added IPv6 support in -o directport
* Added -o vsock option
* Minor bugfixes
Release 3.7.3 (2022-05-26)
--------------------------
* Minor bugfixes.
* This is the last release from the current maintainer. SSHFS is now no longer maintained
or developed. Github issue tracking and pull requests have therefore been disabled. The
mailing list (see below) is still available for use.
If you would like to take over this project, you are welcome to do so. Please fork it
and develop the fork for a while. Once there has been 6 months of reasonable activity,
please contact Nikolaus@rath.org and I'll be happy to give you ownership of this
repository or replace with a pointer to the fork.
Release 3.7.2 (2021-06-08)
--------------------------
* Added a secondary check so if a mkdir request fails with EPERM an access request will be
tried - returning EEXIST if the access was successful.
Fixes: https://github.com/libfuse/sshfs/issues/243
Release 3.7.1 (2020-11-09)
--------------------------
* Minor bugfixes.
Release 3.7.0 (2020-01-03)
--------------------------
* New max_conns option enables the use of multiple connections to improve responsiveness
during large file transfers. Thanks to Timo Savola for doing most of the implementation
work, and thanks to CEA.fr for sponsoring remaining bugfixes and cleanups!
* The `buflimit` workaround is now disabled by default. The corresponding bug in OpenSSH
has been fixed in 2007
(cf. https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=365541#37), so this shouldn't be
needed anymore. If you depend on this workaround, please let the SSHFS maintainers know,
otherwise support for the workaround will be removed completely in a future version.
Release 3.6.0 (2019-11-03)
--------------------------
* Added "-o direct_io" option.
This option disables the use of page cache in kernel.
This is useful for example if the file size is not known before reading it.
For example if you mount /proc dir from a remote host without the direct_io
option, the read always will return zero bytes instead of actual data.
* Added --verbose option.
* Fixed a number of compiler warnings.
* Improved performance under OS X.
Release 3.5.2 (2019-04-13)
--------------------------
* Fixed "-o idmap=user" to map both UID and GID on all OSs.
* Fixed improper handling of sequential spaces spaces in "ssh_command" option
Release 3.5.1 (2018-12-22)
--------------------------
* Documentation updates
* Build system updates
* Added "BindInterface" as valid "-o" option.
Release 3.5.0 (2018-08-28)
--------------------------
* Fixed error code returned by rename(), allowing proper fallback.
* Port to Cygwin.
Release 3.4.0 (2018-06-29)
--------------------------
* Make utimens(NULL) result in timestamp "now" -- no more touched files
dated 1970-01-01
* New `createmode` workaround.
* Fix `fstat` workaround regression.
Release 3.3.2 (2018-04-29)
--------------------------
* New `renamexdev` workaround.
Release 3.3.1 (2017-10-25)
--------------------------
* Manpage is now installed in correct directory.
* SSHFS now supports (or rather: ignores) some options that it may
receive as result of being mounted from ``/etc/mtab``. This includes
things like ``user``, ``netdev``, or ``auto``.
SSHFS 3.3.0 (2017-09-20)
------------------------
* Dropped support for writeback caching (and, as a consequence,
"unreliable append" operation). As of kernel 4.14, the FUSE module's
writeback implementation is not compatible with network filesystems
and there are no imminent plans to change that.
* Add support for mounting from /etc/fstab
* Dropped support for building with autotools.
* Added missing options to man page.
Release 3.2.0 (2017-08-06)
--------------------------
* Re-enabled writeback cache.
* SSHFS now supports O_APPEND.
Release 3.1.0 (2017-08-04)
--------------------------
* Temporarily disabled the writeback cache feature, since there
have been reports of dataloss when appending to files when
writeback caching is enabled.
* Fixed a crash due to a race condition when listing
directory contents.
* For improved backwards compatibility, SSHFS now also silently
accepts the old ``-o cache_*`` options.
Release 3.0.0 (2017-07-08)
--------------------------
* sshfs now requires libfuse 3.1.0 or newer.
* When supported by the kernel, sshfs now uses writeback caching.
* The `cache` option has been renamed to `dir_cache` for clarity.
* Added unit tests
* --debug now behaves like -o debug_sshfs, i.e. it enables sshfs
debugging messages rather than libfuse debugging messages.
* Documented limited hardlink support.
* Added support for building with Meson.
* Added support for more SSH options.
* Dropped support for the *nodelay* workaround - the last OpenSSH
version for which this was useful was released in 2006.
* Dropped support for the *nodelaysrv* workaround. The same effect
(enabling NODELAY on the server side *and* enabling X11 forwarding)
can be achieved by explicitly passing `-o ForwardX11`
* Removed support for `-o workaround=all`. Workarounds should always
enabled explicitly and only when needed. There is no point in always
enabling a potentially changing set of workarounds.
Release 2.9 (2017-04-17)
------------------------
* Improved support for Cygwin.
* Various small bugfixes.
Release 2.8 (2016-06-22)
------------------------
* Added support for the "fsync" extension.
* Fixed a build problem with bitbake
Release 2.7 (2016-03-01)
------------------------
* Integrated osxfuse's copy of sshfs, which means that sshfs now works
on OS X out of the box.
* Added -o cache_max_size=N option to let users tune the maximum size of
the cache in number of entries.
* Added -o cache_clean_interval=N and -o cache_min_clean_interval=N
options to let users tune the cleaning behavior of the cache.
Release 2.6 (2015-01-28)
------------------------
* New maintainer (Nikolaus Rath <Nikolaus@rath.org>)
Release 2.5 (2014-01-14)
------------------------
* Some performance improvements for large directories.
* New `disable_hardlink` option.
* Various small bugfixes.
Release 2.4 (2012-03-08)
------------------------
* New `slave` option.
* New `idmap`, `uidmap` and `gidmap` options.
* Various small bugfixes.
Release 2.3 (2011-07-01)
------------------------
* Support hard link creation if server is OpenSSH 5.7 or later
* Small improvements and bug fixes
* Check mount point and options before connecting to ssh server
* New 'delay_connect' option
Release 2.2 (2008-10-20)
------------------------
* Handle numerical IPv6 addresses enclosed in square brackets
* Handle commas in usernames
Release 2.1 (2008-07-11)
------------------------
* Small improvements and bug fixes
Release 2.0 (2008-04-23)
------------------------
* Support password authentication with pam_mount
* Support atomic renames if server is OpenSSH 4.9 or later
* Support getting disk usage if server is OpenSSH 5.1 or later
* Small enhancements and bug fixes
What is new in 1.9
------------------
* Fix a serious bug, that could result in sshfs hanging, crashing, or
reporting out-of-memory
What is new in 1.8
------------------
* Bug fixes
What is new in 1.7
------------------
* Tolerate servers which print a banner on login
* Small improvements
What is new in 1.6
------------------
* Workaround for missing truncate operation on old sftp servers
* Bug fixes
What is new in 1.5
------------------
* Improvements to read performance. Now both read and write
throughput should be very close to 'scp'
* If used with FUSE 2.6.0 or later, then perform better data caching.
This should show dramatic speed improvements when a file is opened
more than once
* Bug fixes
What is new in 1.4
------------------
* Updated to version 25 of libfuse API
* This means that the 'cp' of readonly file to sshfs bug is finally
solved (as long as using libfuse 2.5.0 or later *and* Linux 2.6.15
or later)
* Sshfs now works on FreeBSD
* Added option to "transform" absolute symbolic links
What is new in 1.3
------------------
* Add workaround for failure to rename to an existing file
* Simple user ID mapping
* Estimate disk usage of files based on size
* Report "infinite" disk space
* Bug fixes
What is new in 1.2
------------------
* Better compatibility with different sftp servers
* Automatic reconnect (optional)
What is new in 1.1
------------------
* Performance improvements:
- directory content caching
- symlink caching
- asynchronous writeback
- readahead
* Fixed '-p' option
What is new in 1.0
------------------
* Initial release
-263
View File
@@ -1,263 +0,0 @@
SSHFS FAQ
=========
1. I've found a bug and there's no solution in this FAQ, what
should I do?
2. Advantage of sshfs over NFS and Samba
3. Create the device node
4. mv fails with "Operation not permitted"
5. cvs fails with "cvs [status aborted]: cannot get working
directory: No such file or directory" in a sshfs mounted directory
6. Changes on the server are not immediately visible in the
mounted directory.
7. Configuring the ssh connection
8. What are the no_readahead and sshfs_sync options for?
9. Why does df return strange values on partitions mounted via
sshfs?
10. How do I specify the remote mount point (since the example
defaults to the home directory)
11. sshfs hangs after a while
12. Following symlinks on the server side
13. Making absolute symlinks work
14. Mounting as root
15. Exporting via NFS
16. Automatical mounting using /etc/fstab
17. Why does SVN (etc...) fail with permission denied?
18. Why does SVN (etc...) fail to rename files?
19. Is there some neat way to do it in reverse?
20. Might it be reasonable to disallow loops?
21. How to mount through an intermediary ssh server, eg:
localhost -> A -> B; mount B from localhost
22. Alternative Solution
23. I seem to have successfully mounted a remote directory, but
performing an `ls -l` on the directory above the mount point shows
the mount point's attributes as `? ? ? ? ? ?`. Nothing shows up in
the directory either. What am I doing wrong?
--------------------------------------------------------------------------
1. I've found a bug and there's no solution in this FAQ, what should I do?
Please report the bug in the Github issue tracker:
https://github.com/libfuse/sshfs/issues
Also logs with debugging output can be useful for diagnosing the
problem. Try running sshfs with the following options:
sshfs -odebug,sshfs_debug,loglevel=debug ...
Doing strace on the application which fails may also sometimes help:
strace -f -o /tmp/strace application args ...
Note that large messages (over 40k) will be rejected from the mailing
list. So try to keep the logs as short as possible.
2. Advantage of sshfs over NFS and Samba
Users can mount remote resources they already have ssh access to,
without requiring the remote machine to export the resource.
The remote resource can be mounted when it is needed in a location
that is convenient for the user at that time, without needing to rely
on a central, root-controlled file system table.
Automatic mounting, if desired, can be added to a shell script such as
.bashrc (provided authentication is done using RSA/DSA keys).
Resources can be mounted over slow and unreliable (distant)
connections.
3. Create the device node
If you don't use udev, you may get this error message:
fusermount: failed to open /dev/fuse: No such device or address
Before loading the fuse kernel module, create the device node
manually:
mknod -m 666 /dev/fuse c 10 229
4. mv fails with "Operation not permitted".
Use -o workaround=rename (requires sshfs version >= 1.3).
5. cvs fails with "cvs status aborted?: cannot get working directory: No such
file or directory" in a sshfs mounted directory
Use the -oreaddir_ino option. Example:
sshfs -oreaddir_ino hostname:remote_dir mount_point
6. Changes on the server are not immediately visible in the mounted directory.
By default, sshfs caches things for 20 seconds, use -o cache_timeout=N
to change the default cache timeout (in seconds) or -o cache=no for
disabling the cache.
You can also control cache timeouts for directory listing etc with
-o cache_stat_timeout=N,
-o cache_dir_timout=N, and
-o cache_link_timout=N.
7. Configuring the ssh connection
In addition to flags like -C, -p, and -o SSHOPT...=, you may find it
easier to edit your /.ssh/config file. You can add an entry with any
customization you want, test it with ssh, and finally use it with
sshfs. As a bonus, you get a short mnemonic for your configuration.
8. What are the no_readahead and sshfs_sync options for?
These disable read and write optimizations respectively. They don't
really make sense unless you're doing something special.
9. Why does df return strange values on partitions mounted via sshfs?
Because the SFTP protocol doesn't have a statfs operation this is
currently not possible to display proper usage on remote partition.
10. How do I specfy the remote mount point (since the example defaults to the
home directory)
The example shows:
sshfs hostname: mountpoint
To specify a remote mount point use:
sshfs hostname:remotemountpoint mountpoint
This might be obvious to others, but I ended up looking up the
interface to sftp to see if I could learn how to specify the remote
mount point, then thought about the way that scp specifies the remote
directory, and it worked.
11. sshfs hangs after a while
Mounting works fine, I can use the files in Mountpoint as good as any
other files on my system, but after bit of time, changing nothing on
the remote files sshfs crashes. This means, I can not cd into the
Mountpoint (xterm hangs, nautilus hangs... every program trying to
access the Mountpoint gets stuck, and won't return).
Solution: add
ServerAliveInterval 15
in your .ssh/config (or use -o ServerAliveInterval=15 on the sshfs
command line but I did not test that solution). This will force the
ssh connection to stay alive even if you have no activity.
12. Following symlinks on the server side
The -o follow_symlinks option will enable this.
13. Making absolute symlinks work
Use the -o transform_symlinks option, which will transform absolute
symlinks (ones which point somewhere inside the mount) into relative
ones.
14. Mounting as root
Generally it's not possible to use an sshfs mount as a "real"
filesystem shared between multiple users. Some of this functionality
can be enabled with the -o allow_other and -o default_permissions
options, but files will not be created with the correct ownership,
etc...
15. Exporting via NFS
Use the userspace NFS daemon http://sourceforge.net/projects/unfs
16. Automatical mounting using /etc/fstab
A line in /etc/fstab has the following format:
sshfs#USERNAME@REMOTE_HOST:REMOTE_PATH MOUNT_POINT fuse SSHFS_OPTIONS 0 0
eg.
sshfs#guest@guest.login.com:data /mnt/guest fuse \
uid=1003,gid=100,umask=0,allow_other 0 0
17. Why does SVN (etc...) fail with permission denied?
This is a bug that happens when an application creates a read-only
file opened for writing (e.g. open("foo", O_WRONLY|O_CREAT, 0444))
It has been fixed in sshfs version 1.3, but also requires FUSE version
>=2.5.X and Linux kernel version >=2.6.15.
18. Why does SVN (etc...) fail to rename files?
$ svn co svn://anonsvn.kde.org/home/kde/trunk/KDE/kdelibs
svn: Can't move 'kdelibs/.svn/tmp/entries' to 'kdelibs/.svn/entries':
Operation not permitted
The reason is that SFTP protocol version 3 (which is implemented by
OpenSSH's sftp-server) defines the rename operation slightly
differently than POSIX. The difference is that renaming to an existing
file or directory will fail instead of atomically replacing the old
file.
The -o workaround=rename option will try to emulate POSIX rename
semantics, but it cannot guarantee atomicity. In most of the cases
this doesn't matter, and things will work fine with this option.
19. Is there some neat way to do it in reverse?
You want to mount a USB thumb drive onto a file server that is rather
remote.
Assuming this is difficult because the laptop with the thumb drive is
sitting behind NAT, firewalls, etc. then you need to create a
port-forward:
client$ ssh -R 2222:localhost:22 server
server$ sshfs -p 2222 localhost:/media/usb1 myusb1
(Now, is there is there a smarter way that does not involve port
opening login permissions in an undesireable direction?)
20. Might it be reasonable to disallow loops?
sshfs localhost:/mnt /mnt
This seems to produce undesirable results. --JoshuaRodman
21. How to mount through an intermediary ssh server, eg: localhost -> A -> B;
mount B from localhost
Start by mounting the folder you need that is on "a" to a folder on
"b" then mount the new folder that is on "a" to a folder on localhost.
IE: (These are NOT real commands, but a sequence of steps.
A mounts B:/home/x on /mnt/Bx
localhost mounts A:/mnt/Bx on ~/mydir
22. Alternative Solution:
1) Create a shell script to wrap the tunneling of one ssh command over
another,
$ cat >Atunnel <<EOF
#!/bin/bash
ssh -q A ssh -q "$@"
EOF
$ chmod u+x Atunnel
N.B. make sure to put this somewhere on your path. 2) sshfs mount as
normal but using this script as the ssh command.
$ sshfs -o ssh_command='Atunnel' B: ~/mydir
23. I seem to have successfully mounted a remote directory, but performing an
`ls -l` on the directory above the mount point shows the mount point's
attributes as `? ? ? ? ? ?`. Nothing shows up in the directory either. What
am I doing wrong?
You probably specified a remote path with the tilde (~) in it. This
doesn't seem to work. Instead, specify an absolute remote path:
sshfs username@remote.host:/home/username/whatever my/mount
What options do i use to make playing media files (music) over sshfs more
efficient?
MacFUSE doesn't seem to let me move files from one directory to another. It
first asks for my local user password (i.e. the password on my Macbook Pro)
and then produces the error message "The operation cannot be completed
because one or more required items cannot be found. (Error code -120).
-42
View File
@@ -1,42 +0,0 @@
## Process this file with automake to produce Makefile.in
bin_PROGRAMS = sshfs
sshfs_SOURCES = sshfs.c cache.c cache.h
if FUSE_OPT_COMPAT
sshfs_SOURCES += compat/fuse_opt.c compat/fuse_opt.h
endif
if DARWIN_COMPAT
sshfs_SOURCES += compat/darwin_compat.c compat/darwin_compat.h
endif
sshfs_LDADD = $(SSHFS_LIBS)
sshfs_CFLAGS = $(SSHFS_CFLAGS)
sshfs_CPPFLAGS = -D_REENTRANT -DFUSE_USE_VERSION=26 -DLIBDIR=\"$(libdir)\" \
-DIDMAP_DEFAULT="\"$(IDMAP_DEFAULT)\""
EXTRA_DIST = sshnodelay.c sshfs.1.in
CLEANFILES = sshnodelay.so sshfs.1 sshfs.1.tmp
dist_man_MANS = sshfs.1
sshfs.1: sshfs.1.in
$(AM_V_GEN)sed \
-e 's,__IDMAP_DEFAULT__,$(IDMAP_DEFAULT),g' \
-e 's,__UNMOUNT_COMMAND__,$(UNMOUNT_COMMAND),g' \
<$(srcdir)/sshfs.1.in >sshfs.1.tmp || exit 1; \
mv sshfs.1.tmp sshfs.1
if SSH_NODELAY_SO
all-local: sshnodelay.so
install-exec-local: sshnodelay.so
test -z "$(libdir)" || $(mkdir_p) "$(DESTDIR)$(libdir)"
$(INSTALL) -m 755 sshnodelay.so "$(DESTDIR)$(libdir)/sshnodelay.so"
uninstall-local:
rm -f "$(DESTDIR)$(libdir)/sshnodelay.so"
sshnodelay.so:
$(CC) -Wall -W -s --shared -fPIC $(sshnodelay_libs) sshnodelay.c -o sshnodelay.so
endif
+122 -52
View File
@@ -1,72 +1,142 @@
Abstract
========
This is a filesystem client based on the SSH File Transfer Protocol.
Since most SSH servers already support this protocol it is very easy
to set up: i.e. on the server side there's nothing to do. On the
client side mounting the filesystem is as easy as logging into the
server with ssh.
The idea of sshfs was taken from the SSHFS filesystem distributed with
LUFS, which I found very useful. There were some limitations of that
codebase, so I rewrote it. Features of this implementation are:
- Based on FUSE (the best userspace filesystem framework for Linux ;)
- Multithreading: more than one request can be on it's way to the
server
- Allowing large reads (max 64k)
- Caching directory contents
- Reconnect on failure
Latest version
==============
The latest version and more information can be found on
http://github.com/libfuse/sshfs
# SSHFS
How to mount a filesystem
=========================
## About
SSHFS allows you to mount a remote filesystem using SFTP. Most SSH
servers support and enable this SFTP access by default, so SSHFS is
very simple to use - there's nothing to do on the server-side.
## Development Status
SSHFS is shipped by all major Linux distributions and has been in
production use across a wide range of systems for many years. However,
at present SSHFS does not have any active, regular contributors, and
there are a number of known issues (see the [bugtracker](https://github.com/libfuse/sshfs/issues)).
The current maintainer continues to apply pull requests and makes regular
releases, but unfortunately has no capacity to do any development
beyond addressing high-impact issues. When reporting bugs, please
understand that unless you are including a pull request or are
reporting a critical issue, you will probably not get a response.
## How to use
Once sshfs is installed (see next section) running it is very simple:
sshfs hostname: mountpoint
```
sshfs [user@]hostname:[directory] mountpoint
```
Note, that it's recommended to run it as user, not as root. For this
to work the mountpoint must be owned by the user. If the username is
different on the host you are connecting to, then use the
"username@host:" form. If you need to enter a password sshfs will ask
for it (actually it just runs ssh which ask for the password if
needed). You can also specify a directory after the ":". The default
is the home directory.
It is recommended to run SSHFS as regular user (not as root). For
this to work the mountpoint must be owned by the user. If username is
omitted SSHFS will use the local username. If the directory is
omitted, SSHFS will mount the (remote) home directory. If you need to
enter a password sshfs will ask for it (actually it just runs ssh
which asks for the password if needed).
Also many ssh options can be specified (see the manual pages for
sftp(1) and ssh_config(5)), including the remote port number
*sftp(1)* and *ssh_config(5)*), including the remote port number
(`-oport=PORT`)
To unmount the filesystem:
fusermount -u mountpoint
```
fusermount -u mountpoint
```
On BSD and macOS, to unmount the filesystem:
```
umount mountpoint
```
### Bypassing SSH
#### Using directport
Using direct connections to sftp-server to bypass SSH for performance is also possible. To do this, start a network service using sftp-server (part of OpenSSH) on a server, then connect directly using `-o directport=PORT` option.
On server (listen on port 1234 using socat):
`socat tcp-listen:1234,reuseaddr,fork exec:/usr/lib/openssh/sftp-server`
On client:
`sshfs -o directport=1234 127.0.0.1:/tmp /tmp/mnt`
Note that this is insecure as connection will happen without encryption. Only use this on localhost or trusted networks. This option is sometimes used by other projects to mount folders inside VMs.
IPv6 is also possible:
`socat tcp6-listen:1234,reuseaddr,fork exec:/usr/lib/openssh/sftp-server`
`sshfs -o directport=1234 [::1]:/tmp /tmp/mnt`
#### Using vsock
Similarly to above, Linux [vsock](https://man7.org/linux/man-pages/man7/vsock.7.html) can be used to connect directly to sockets within VMs using `-o vsock=CID:PORT`.
```
# on the host
socat VSOCK-LISTEN:12345 EXEC:"/usr/lib/openssh/sftp-server",nofork
# on the clientside
sshfs -o vsock=2:12345 unused_host: ./tmp
```
## Installation
Installing
==========
First, download the latest SSHFS release from
https://github.com/libfuse/sshfs/releases. You also need [libfuse](http://github.com/libfuse/libfuse) 3.1.0 or newer (or a
similar library that provides a libfuse3 compatible interface for your operating
system). Finally, you need the [Glib](https://developer.gnome.org/glib/stable/) library with development headers (which should be
available from your operating system's package manager).
First you need to download FUSE 2.2 or later from
http://github.com/libfuse/libfuse.
To build and install, we recommend to use [Meson](http://mesonbuild.com/) (version 0.38 or
newer) and [Ninja](https://ninja-build.org/). After extracting the sshfs tarball, create a
(temporary) build directory and run Meson:
You also need to install the devel package for glib2.0. After
installing FUSE, compile sshfs the usual way:
```
$ mkdir build; cd build
$ meson ..
```
./configure
make
make install (as root)
Normally, the default build options will work fine. If you
nevertheless want to adjust them, you can do so with the *mesonconf*
command:
And you are ready to go.
```
$ mesonconf # list options
$ mesonconf -D strip=true # set an option
```
If checking out from git for the first time also do `autoreconf -i`
before doing `./configure`.
To build, test and install SSHFS, you then use Ninja (running the
tests requires the [py.test](http://www.pytest.org/) Python module):
```
$ ninja
$ python3 -m pytest test/ # optional, but recommended
$ sudo ninja install
```
## Getting Help
If you need help, please ask on the <fuse-sshfs@lists.sourceforge.net>
mailing list (subscribe at
https://lists.sourceforge.net/lists/listinfo/fuse-sshfs).
Please report any bugs on the GitHub issue tracker at
https://github.com/libfuse/sshfs/issues.
## Packaging Status
<a href="https://repology.org/project/fusefs:sshfs/versions">
<img src="https://repology.org/badge/vertical-allrepos/fusefs:sshfs.svg" alt="Packaging status" >
</a>
+205 -177
View File
@@ -8,11 +8,13 @@
#include "cache.h"
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <glib.h>
#include <pthread.h>
#include <sys/stat.h>
#define DEFAULT_CACHE_TIMEOUT_SECS 20
#define DEFAULT_MAX_CACHE_SIZE 10000
@@ -27,7 +29,7 @@ struct cache {
unsigned int max_size;
unsigned int clean_interval_secs;
unsigned int min_clean_interval_secs;
struct fuse_cache_operations *next_oper;
struct fuse_operations *next_oper;
GHashTable *table;
pthread_mutex_t lock;
time_t last_cleaned;
@@ -39,28 +41,51 @@ static struct cache cache;
struct node {
struct stat stat;
time_t stat_valid;
char **dir;
GPtrArray *dir;
time_t dir_valid;
char *link;
time_t link_valid;
time_t valid;
};
struct fuse_cache_dirhandle {
struct readdir_handle {
const char *path;
fuse_dirh_t h;
fuse_dirfil_t filler;
void *buf;
fuse_fill_dir_t filler;
GPtrArray *dir;
uint64_t wrctr;
};
struct file_handle {
/* Did we send an open request to the underlying fs? */
int is_open;
/* If so, this will hold its handle */
unsigned long fs_fh;
};
struct cache_dirent {
char *name;
struct stat stat;
};
static void free_node(gpointer node_)
{
struct node *node = (struct node *) node_;
g_strfreev(node->dir);
if (node->dir != NULL) {
g_ptr_array_free(node->dir, TRUE);
}
g_free(node);
}
static void free_cache_dirent(gpointer data) {
struct cache_dirent *cache_dirent = (struct cache_dirent *) data;
if (cache_dirent != NULL) {
g_free(cache_dirent->name);
g_free(cache_dirent);
}
}
static int cache_clean_entry(void *key_, struct node *node, time_t *now)
{
(void) key_;
@@ -108,15 +133,12 @@ static void cache_purge_parent(const char *path)
void cache_invalidate(const char *path)
{
if (!cache.on)
return;
pthread_mutex_lock(&cache.lock);
cache_purge(path);
pthread_mutex_unlock(&cache.lock);
}
void cache_invalidate_write(const char *path)
static void cache_invalidate_write(const char *path)
{
pthread_mutex_lock(&cache.lock);
cache_purge(path);
@@ -168,9 +190,6 @@ void cache_add_attr(const char *path, const struct stat *stbuf, uint64_t wrctr)
{
struct node *node;
if (!cache.on)
return;
pthread_mutex_lock(&cache.lock);
if (wrctr == cache.write_ctr) {
node = cache_get(path);
@@ -183,13 +202,15 @@ void cache_add_attr(const char *path, const struct stat *stbuf, uint64_t wrctr)
pthread_mutex_unlock(&cache.lock);
}
static void cache_add_dir(const char *path, char **dir)
static void cache_add_dir(const char *path, GPtrArray *dir)
{
struct node *node;
pthread_mutex_lock(&cache.lock);
node = cache_get(path);
g_strfreev(node->dir);
if (node->dir != NULL) {
g_ptr_array_free(node->dir, TRUE);
}
node->dir = dir;
node->dir_valid = time(NULL) + cache.dir_timeout_secs;
if (node->dir_valid > node->valid)
@@ -248,12 +269,25 @@ uint64_t cache_get_write_ctr(void)
return res;
}
static int cache_getattr(const char *path, struct stat *stbuf)
static void *cache_init(struct fuse_conn_info *conn,
struct fuse_config *cfg)
{
void *res;
res = cache.next_oper->init(conn, cfg);
// Cache requires a path for each request
cfg->nullpath_ok = 0;
return res;
}
static int cache_getattr(const char *path, struct stat *stbuf,
struct fuse_file_info *fi)
{
int err = cache_get_attr(path, stbuf);
if (err) {
uint64_t wrctr = cache_get_write_ctr();
err = cache.next_oper->oper.getattr(path, stbuf);
err = cache.next_oper->getattr(path, stbuf, fi);
if (!err)
cache_add_attr(path, stbuf, wrctr);
}
@@ -277,19 +311,58 @@ static int cache_readlink(const char *path, char *buf, size_t size)
}
}
pthread_mutex_unlock(&cache.lock);
err = cache.next_oper->oper.readlink(path, buf, size);
err = cache.next_oper->readlink(path, buf, size);
if (!err)
cache_add_link(path, buf, size);
return err;
}
static int cache_dirfill(fuse_cache_dirh_t ch, const char *name,
const struct stat *stbuf)
static int cache_opendir(const char *path, struct fuse_file_info *fi)
{
int err = ch->filler(ch->h, name, 0, 0);
(void) path;
struct file_handle *cfi;
cfi = malloc(sizeof(struct file_handle));
if(cfi == NULL)
return -ENOMEM;
cfi->is_open = 0;
fi->fh = (unsigned long) cfi;
return 0;
}
static int cache_releasedir(const char *path, struct fuse_file_info *fi)
{
int err;
struct file_handle *cfi;
cfi = (struct file_handle*) fi->fh;
if(cfi->is_open) {
fi->fh = cfi->fs_fh;
err = cache.next_oper->releasedir(path, fi);
} else
err = 0;
free(cfi);
return err;
}
static int cache_dirfill (void *buf, const char *name,
const struct stat *stbuf, off_t off,
enum fuse_fill_dir_flags flags)
{
int err;
struct readdir_handle *ch;
ch = (struct readdir_handle*) buf;
err = ch->filler(ch->buf, name, stbuf, off, flags);
if (!err) {
g_ptr_array_add(ch->dir, g_strdup(name));
struct cache_dirent *cdent = g_malloc(sizeof(struct cache_dirent));
cdent->name = g_strdup(name);
cdent->stat = *stbuf;
g_ptr_array_add(ch->dir, cdent);
if (stbuf->st_mode & S_IFMT) {
char *fullpath;
const char *basepath = !ch->path[1] ? "" : ch->path;
@@ -302,61 +375,67 @@ static int cache_dirfill(fuse_cache_dirh_t ch, const char *name,
return err;
}
static int cache_getdir(const char *path, fuse_dirh_t h, fuse_dirfil_t filler)
static int cache_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi,
enum fuse_readdir_flags flags)
{
struct fuse_cache_dirhandle ch;
struct readdir_handle ch;
struct file_handle *cfi;
int err;
char **dir;
GPtrArray *dir;
struct node *node;
struct cache_dirent **cdent;
assert(offset == 0);
pthread_mutex_lock(&cache.lock);
node = cache_lookup(path);
if (node != NULL && node->dir != NULL) {
time_t now = time(NULL);
if (node->dir_valid - now >= 0) {
for(dir = node->dir; *dir != NULL; dir++)
filler(h, *dir, 0, 0);
for(cdent = (struct cache_dirent**)node->dir->pdata; *cdent != NULL; cdent++) {
filler(buf, (*cdent)->name, &(*cdent)->stat, 0, 0);
}
pthread_mutex_unlock(&cache.lock);
return 0;
}
}
pthread_mutex_unlock(&cache.lock);
cfi = (struct file_handle*) fi->fh;
if(cfi->is_open)
fi->fh = cfi->fs_fh;
else {
if(cache.next_oper->opendir) {
err = cache.next_oper->opendir(path, fi);
if(err)
return err;
}
cfi->is_open = 1;
cfi->fs_fh = fi->fh;
}
ch.path = path;
ch.h = h;
ch.buf = buf;
ch.filler = filler;
ch.dir = g_ptr_array_new();
g_ptr_array_set_free_func(ch.dir, free_cache_dirent);
ch.wrctr = cache_get_write_ctr();
err = cache.next_oper->cache_getdir(path, &ch, cache_dirfill);
err = cache.next_oper->readdir(path, &ch, cache_dirfill, offset, fi, flags);
g_ptr_array_add(ch.dir, NULL);
dir = (char **) ch.dir->pdata;
if (!err)
dir = ch.dir;
if (!err) {
cache_add_dir(path, dir);
else
g_strfreev(dir);
g_ptr_array_free(ch.dir, FALSE);
} else {
g_ptr_array_free(dir, TRUE);
}
return err;
}
static int cache_unity_dirfill(fuse_cache_dirh_t ch, const char *name,
const struct stat *stbuf)
{
(void) stbuf;
return ch->filler(ch->h, name, 0, 0);
}
static int cache_unity_getdir(const char *path, fuse_dirh_t h,
fuse_dirfil_t filler)
{
struct fuse_cache_dirhandle ch;
ch.h = h;
ch.filler = filler;
return cache.next_oper->cache_getdir(path, &ch, cache_unity_dirfill);
}
static int cache_mknod(const char *path, mode_t mode, dev_t rdev)
{
int err = cache.next_oper->oper.mknod(path, mode, rdev);
int err = cache.next_oper->mknod(path, mode, rdev);
if (!err)
cache_invalidate_dir(path);
return err;
@@ -364,7 +443,7 @@ static int cache_mknod(const char *path, mode_t mode, dev_t rdev)
static int cache_mkdir(const char *path, mode_t mode)
{
int err = cache.next_oper->oper.mkdir(path, mode);
int err = cache.next_oper->mkdir(path, mode);
if (!err)
cache_invalidate_dir(path);
return err;
@@ -372,7 +451,7 @@ static int cache_mkdir(const char *path, mode_t mode)
static int cache_unlink(const char *path)
{
int err = cache.next_oper->oper.unlink(path);
int err = cache.next_oper->unlink(path);
if (!err)
cache_invalidate_dir(path);
return err;
@@ -380,7 +459,7 @@ static int cache_unlink(const char *path)
static int cache_rmdir(const char *path)
{
int err = cache.next_oper->oper.rmdir(path);
int err = cache.next_oper->rmdir(path);
if (!err)
cache_invalidate_dir(path);
return err;
@@ -388,15 +467,15 @@ static int cache_rmdir(const char *path)
static int cache_symlink(const char *from, const char *to)
{
int err = cache.next_oper->oper.symlink(from, to);
int err = cache.next_oper->symlink(from, to);
if (!err)
cache_invalidate_dir(to);
return err;
}
static int cache_rename(const char *from, const char *to)
static int cache_rename(const char *from, const char *to, unsigned int flags)
{
int err = cache.next_oper->oper.rename(from, to);
int err = cache.next_oper->rename(from, to, flags);
if (!err)
cache_do_rename(from, to);
return err;
@@ -404,7 +483,7 @@ static int cache_rename(const char *from, const char *to)
static int cache_link(const char *from, const char *to)
{
int err = cache.next_oper->oper.link(from, to);
int err = cache.next_oper->link(from, to);
if (!err) {
cache_invalidate(from);
cache_invalidate_dir(to);
@@ -412,33 +491,28 @@ static int cache_link(const char *from, const char *to)
return err;
}
static int cache_chmod(const char *path, mode_t mode)
static int cache_chmod(const char *path, mode_t mode,
struct fuse_file_info *fi)
{
int err = cache.next_oper->oper.chmod(path, mode);
int err = cache.next_oper->chmod(path, mode, fi);
if (!err)
cache_invalidate(path);
return err;
}
static int cache_chown(const char *path, uid_t uid, gid_t gid)
static int cache_chown(const char *path, uid_t uid, gid_t gid,
struct fuse_file_info *fi)
{
int err = cache.next_oper->oper.chown(path, uid, gid);
int err = cache.next_oper->chown(path, uid, gid, fi);
if (!err)
cache_invalidate(path);
return err;
}
static int cache_truncate(const char *path, off_t size)
static int cache_utimens(const char *path, const struct timespec tv[2],
struct fuse_file_info *fi)
{
int err = cache.next_oper->oper.truncate(path, size);
if (!err)
cache_invalidate(path);
return err;
}
static int cache_utime(const char *path, struct utimbuf *buf)
{
int err = cache.next_oper->oper.utime(path, buf);
int err = cache.next_oper->utimens(path, tv, fi);
if (!err)
cache_invalidate(path);
return err;
@@ -447,140 +521,95 @@ static int cache_utime(const char *path, struct utimbuf *buf)
static int cache_write(const char *path, const char *buf, size_t size,
off_t offset, struct fuse_file_info *fi)
{
int res = cache.next_oper->oper.write(path, buf, size, offset, fi);
int res = cache.next_oper->write(path, buf, size, offset, fi);
if (res >= 0)
cache_invalidate_write(path);
return res;
}
#if FUSE_VERSION >= 25
static int cache_create(const char *path, mode_t mode,
struct fuse_file_info *fi)
{
int err = cache.next_oper->oper.create(path, mode, fi);
int err = cache.next_oper->create(path, mode, fi);
if (!err)
cache_invalidate_dir(path);
return err;
}
static int cache_ftruncate(const char *path, off_t size,
struct fuse_file_info *fi)
static int cache_truncate(const char *path, off_t size,
struct fuse_file_info *fi)
{
int err = cache.next_oper->oper.ftruncate(path, size, fi);
int err = cache.next_oper->truncate(path, size, fi);
if (!err)
cache_invalidate(path);
return err;
}
static int cache_fgetattr(const char *path, struct stat *stbuf,
struct fuse_file_info *fi)
{
int err = cache_get_attr(path, stbuf);
if (err) {
uint64_t wrctr = cache_get_write_ctr();
err = cache.next_oper->oper.fgetattr(path, stbuf, fi);
if (!err)
cache_add_attr(path, stbuf, wrctr);
}
return err;
}
#endif
static void cache_unity_fill(struct fuse_cache_operations *oper,
struct fuse_operations *cache_oper)
{
#if FUSE_VERSION >= 23
cache_oper->init = oper->oper.init;
#endif
cache_oper->getattr = oper->oper.getattr;
cache_oper->access = oper->oper.access;
cache_oper->readlink = oper->oper.readlink;
cache_oper->getdir = cache_unity_getdir;
cache_oper->mknod = oper->oper.mknod;
cache_oper->mkdir = oper->oper.mkdir;
cache_oper->symlink = oper->oper.symlink;
cache_oper->unlink = oper->oper.unlink;
cache_oper->rmdir = oper->oper.rmdir;
cache_oper->rename = oper->oper.rename;
cache_oper->link = oper->oper.link;
cache_oper->chmod = oper->oper.chmod;
cache_oper->chown = oper->oper.chown;
cache_oper->truncate = oper->oper.truncate;
cache_oper->utime = oper->oper.utime;
cache_oper->open = oper->oper.open;
cache_oper->read = oper->oper.read;
cache_oper->write = oper->oper.write;
cache_oper->flush = oper->oper.flush;
cache_oper->release = oper->oper.release;
cache_oper->fsync = oper->oper.fsync;
cache_oper->statfs = oper->oper.statfs;
cache_oper->setxattr = oper->oper.setxattr;
cache_oper->getxattr = oper->oper.getxattr;
cache_oper->listxattr = oper->oper.listxattr;
cache_oper->removexattr = oper->oper.removexattr;
#if FUSE_VERSION >= 25
cache_oper->create = oper->oper.create;
cache_oper->ftruncate = oper->oper.ftruncate;
cache_oper->fgetattr = oper->oper.fgetattr;
#endif
#if FUSE_VERSION >= 29
cache_oper->flag_nullpath_ok = oper->oper.flag_nullpath_ok;
cache_oper->flag_nopath = oper->oper.flag_nopath;
#endif
}
static void cache_fill(struct fuse_cache_operations *oper,
static void cache_fill(struct fuse_operations *oper,
struct fuse_operations *cache_oper)
{
cache_oper->getattr = oper->oper.getattr ? cache_getattr : NULL;
cache_oper->readlink = oper->oper.readlink ? cache_readlink : NULL;
cache_oper->getdir = oper->cache_getdir ? cache_getdir : NULL;
cache_oper->mknod = oper->oper.mknod ? cache_mknod : NULL;
cache_oper->mkdir = oper->oper.mkdir ? cache_mkdir : NULL;
cache_oper->symlink = oper->oper.symlink ? cache_symlink : NULL;
cache_oper->unlink = oper->oper.unlink ? cache_unlink : NULL;
cache_oper->rmdir = oper->oper.rmdir ? cache_rmdir : NULL;
cache_oper->rename = oper->oper.rename ? cache_rename : NULL;
cache_oper->link = oper->oper.link ? cache_link : NULL;
cache_oper->chmod = oper->oper.chmod ? cache_chmod : NULL;
cache_oper->chown = oper->oper.chown ? cache_chown : NULL;
cache_oper->truncate = oper->oper.truncate ? cache_truncate : NULL;
cache_oper->utime = oper->oper.utime ? cache_utime : NULL;
cache_oper->write = oper->oper.write ? cache_write : NULL;
#if FUSE_VERSION >= 25
cache_oper->create = oper->oper.create ? cache_create : NULL;
cache_oper->ftruncate = oper->oper.ftruncate ? cache_ftruncate : NULL;
cache_oper->fgetattr = oper->oper.fgetattr ? cache_fgetattr : NULL;
#endif
#if FUSE_VERSION >= 29
cache_oper->flag_nullpath_ok = 0;
cache_oper->flag_nopath = 0;
#endif
cache_oper->access = oper->access;
cache_oper->chmod = oper->chmod ? cache_chmod : NULL;
cache_oper->chown = oper->chown ? cache_chown : NULL;
cache_oper->create = oper->create ? cache_create : NULL;
cache_oper->flush = oper->flush;
cache_oper->fsync = oper->fsync;
cache_oper->getattr = oper->getattr ? cache_getattr : NULL;
cache_oper->getxattr = oper->getxattr;
cache_oper->init = cache_init;
cache_oper->link = oper->link ? cache_link : NULL;
cache_oper->listxattr = oper->listxattr;
cache_oper->mkdir = oper->mkdir ? cache_mkdir : NULL;
cache_oper->mknod = oper->mknod ? cache_mknod : NULL;
cache_oper->open = oper->open;
cache_oper->opendir = cache_opendir;
cache_oper->read = oper->read;
cache_oper->readdir = oper->readdir ? cache_readdir : NULL;
cache_oper->readlink = oper->readlink ? cache_readlink : NULL;
cache_oper->release = oper->release;
cache_oper->releasedir = cache_releasedir;
cache_oper->removexattr = oper->removexattr;
cache_oper->rename = oper->rename ? cache_rename : NULL;
cache_oper->rmdir = oper->rmdir ? cache_rmdir : NULL;
cache_oper->setxattr = oper->setxattr;
cache_oper->statfs = oper->statfs;
cache_oper->symlink = oper->symlink ? cache_symlink : NULL;
cache_oper->truncate = oper->truncate ? cache_truncate : NULL;
cache_oper->unlink = oper->unlink ? cache_unlink : NULL;
cache_oper->utimens = oper->utimens ? cache_utimens : NULL;
cache_oper->write = oper->write ? cache_write : NULL;
}
struct fuse_operations *cache_init(struct fuse_cache_operations *oper)
struct fuse_operations *cache_wrap(struct fuse_operations *oper)
{
static struct fuse_operations cache_oper;
cache.next_oper = oper;
cache_unity_fill(oper, &cache_oper);
if (cache.on) {
cache_fill(oper, &cache_oper);
pthread_mutex_init(&cache.lock, NULL);
cache.table = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, free_node);
if (cache.table == NULL) {
fprintf(stderr, "failed to create cache\n");
return NULL;
}
cache_fill(oper, &cache_oper);
pthread_mutex_init(&cache.lock, NULL);
cache.table = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, free_node);
if (cache.table == NULL) {
fprintf(stderr, "failed to create cache\n");
return NULL;
}
return &cache_oper;
}
static const struct fuse_opt cache_opts[] = {
{ "cache=yes", offsetof(struct cache, on), 1 },
{ "cache=no", offsetof(struct cache, on), 0 },
{ "dcache_timeout=%u", offsetof(struct cache, stat_timeout_secs), 0 },
{ "dcache_timeout=%u", offsetof(struct cache, dir_timeout_secs), 0 },
{ "dcache_timeout=%u", offsetof(struct cache, link_timeout_secs), 0 },
{ "dcache_stat_timeout=%u", offsetof(struct cache, stat_timeout_secs), 0 },
{ "dcache_dir_timeout=%u", offsetof(struct cache, dir_timeout_secs), 0 },
{ "dcache_link_timeout=%u", offsetof(struct cache, link_timeout_secs), 0 },
{ "dcache_max_size=%u", offsetof(struct cache, max_size), 0 },
{ "dcache_clean_interval=%u", offsetof(struct cache,
clean_interval_secs), 0 },
{ "dcache_min_clean_interval=%u", offsetof(struct cache,
min_clean_interval_secs), 0 },
/* For backwards compatibility */
{ "cache_timeout=%u", offsetof(struct cache, stat_timeout_secs), 0 },
{ "cache_timeout=%u", offsetof(struct cache, dir_timeout_secs), 0 },
{ "cache_timeout=%u", offsetof(struct cache, link_timeout_secs), 0 },
@@ -589,9 +618,9 @@ static const struct fuse_opt cache_opts[] = {
{ "cache_link_timeout=%u", offsetof(struct cache, link_timeout_secs), 0 },
{ "cache_max_size=%u", offsetof(struct cache, max_size), 0 },
{ "cache_clean_interval=%u", offsetof(struct cache,
clean_interval_secs), 0 },
clean_interval_secs), 0 },
{ "cache_min_clean_interval=%u", offsetof(struct cache,
min_clean_interval_secs), 0 },
min_clean_interval_secs), 0 },
FUSE_OPT_END
};
@@ -603,7 +632,6 @@ int cache_parse_options(struct fuse_args *args)
cache.max_size = DEFAULT_MAX_CACHE_SIZE;
cache.clean_interval_secs = DEFAULT_CACHE_CLEAN_INTERVAL_SECS;
cache.min_clean_interval_secs = DEFAULT_MIN_CACHE_CLEAN_INTERVAL_SECS;
cache.on = 1;
return fuse_opt_parse(args, &cache, cache_opts, NULL);
}
+1 -14
View File
@@ -9,20 +9,7 @@
#include <fuse.h>
#include <fuse_opt.h>
#ifndef FUSE_VERSION
#define FUSE_VERSION (FUSE_MAJOR_VERSION * 10 + FUSE_MINOR_VERSION)
#endif
typedef struct fuse_cache_dirhandle *fuse_cache_dirh_t;
typedef int (*fuse_cache_dirfil_t) (fuse_cache_dirh_t h, const char *name,
const struct stat *stbuf);
struct fuse_cache_operations {
struct fuse_operations oper;
int (*cache_getdir) (const char *, fuse_cache_dirh_t, fuse_cache_dirfil_t);
};
struct fuse_operations *cache_init(struct fuse_cache_operations *oper);
struct fuse_operations *cache_wrap(struct fuse_operations *oper);
int cache_parse_options(struct fuse_args *args);
void cache_add_attr(const char *path, const struct stat *stbuf, uint64_t wrctr);
void cache_invalidate(const char *path);
-364
View File
@@ -1,364 +0,0 @@
/*
FUSE: Filesystem in Userspace
Copyright (C) 2001-2006 Miklos Szeredi <miklos@szeredi.hu>
This program can be distributed under the terms of the GNU LGPL.
See the file COPYING.LIB
*/
#include "fuse_opt.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
struct fuse_opt_context {
void *data;
const struct fuse_opt *opt;
fuse_opt_proc_t proc;
int argctr;
int argc;
char **argv;
struct fuse_args outargs;
char *opts;
int nonopt;
};
void fuse_opt_free_args(struct fuse_args *args)
{
if (args && args->argv && args->allocated) {
int i;
for (i = 0; i < args->argc; i++)
free(args->argv[i]);
free(args->argv);
args->argv = NULL;
args->allocated = 0;
}
}
static int alloc_failed(void)
{
fprintf(stderr, "fuse: memory allocation failed\n");
return -1;
}
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
{
char **newargv;
char *newarg;
assert(!args->argv || args->allocated);
newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *));
newarg = newargv ? strdup(arg) : NULL;
if (!newargv || !newarg)
return alloc_failed();
args->argv = newargv;
args->allocated = 1;
args->argv[args->argc++] = newarg;
args->argv[args->argc] = NULL;
return 0;
}
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
{
assert(pos <= args->argc);
if (fuse_opt_add_arg(args, arg) == -1)
return -1;
if (pos != args->argc - 1) {
char *newarg = args->argv[args->argc - 1];
memmove(&args->argv[pos + 1], &args->argv[pos],
sizeof(char *) * (args->argc - pos - 1));
args->argv[pos] = newarg;
}
return 0;
}
static int next_arg(struct fuse_opt_context *ctx, const char *opt)
{
if (ctx->argctr + 1 >= ctx->argc) {
fprintf(stderr, "fuse: missing argument after `%s'\n", opt);
return -1;
}
ctx->argctr++;
return 0;
}
static int add_arg(struct fuse_opt_context *ctx, const char *arg)
{
return fuse_opt_add_arg(&ctx->outargs, arg);
}
int fuse_opt_add_opt(char **opts, const char *opt)
{
char *newopts;
if (!*opts)
newopts = strdup(opt);
else {
unsigned oldlen = strlen(*opts);
newopts = realloc(*opts, oldlen + 1 + strlen(opt) + 1);
if (newopts) {
newopts[oldlen] = ',';
strcpy(newopts + oldlen + 1, opt);
}
}
if (!newopts)
return alloc_failed();
*opts = newopts;
return 0;
}
static int add_opt(struct fuse_opt_context *ctx, const char *opt)
{
return fuse_opt_add_opt(&ctx->opts, opt);
}
static int call_proc(struct fuse_opt_context *ctx, const char *arg, int key,
int iso)
{
if (key == FUSE_OPT_KEY_DISCARD)
return 0;
if (key != FUSE_OPT_KEY_KEEP && ctx->proc) {
int res = ctx->proc(ctx->data, arg, key, &ctx->outargs);
if (res == -1 || !res)
return res;
}
if (iso)
return add_opt(ctx, arg);
else
return add_arg(ctx, arg);
}
static int match_template(const char *t, const char *arg, unsigned *sepp)
{
int arglen = strlen(arg);
const char *sep = strchr(t, '=');
sep = sep ? sep : strchr(t, ' ');
if (sep && (!sep[1] || sep[1] == '%')) {
int tlen = sep - t;
if (sep[0] == '=')
tlen ++;
if (arglen >= tlen && strncmp(arg, t, tlen) == 0) {
*sepp = sep - t;
return 1;
}
}
if (strcmp(t, arg) == 0) {
*sepp = 0;
return 1;
}
return 0;
}
static const struct fuse_opt *find_opt(const struct fuse_opt *opt,
const char *arg, unsigned *sepp)
{
for (; opt && opt->templ; opt++)
if (match_template(opt->templ, arg, sepp))
return opt;
return NULL;
}
int fuse_opt_match(const struct fuse_opt *opts, const char *opt)
{
unsigned dummy;
return find_opt(opts, opt, &dummy) ? 1 : 0;
}
static int process_opt_param(void *var, const char *format, const char *param,
const char *arg)
{
assert(format[0] == '%');
if (format[1] == 's') {
char *copy = strdup(param);
if (!copy)
return alloc_failed();
*(char **) var = copy;
} else {
if (sscanf(param, format, var) != 1) {
fprintf(stderr, "fuse: invalid parameter in option `%s'\n", arg);
return -1;
}
}
return 0;
}
static int process_opt(struct fuse_opt_context *ctx,
const struct fuse_opt *opt, unsigned sep,
const char *arg, int iso)
{
if (opt->offset == -1U) {
if (call_proc(ctx, arg, opt->value, iso) == -1)
return -1;
} else {
void *var = ctx->data + opt->offset;
if (sep && opt->templ[sep + 1]) {
const char *param = arg + sep;
if (opt->templ[sep] == '=')
param ++;
if (process_opt_param(var, opt->templ + sep + 1,
param, arg) == -1)
return -1;
} else
*(int *)var = opt->value;
}
return 0;
}
static int process_opt_sep_arg(struct fuse_opt_context *ctx,
const struct fuse_opt *opt, unsigned sep,
const char *arg, int iso)
{
int res;
char *newarg;
char *param;
if (next_arg(ctx, arg) == -1)
return -1;
param = ctx->argv[ctx->argctr];
newarg = malloc(sep + strlen(param) + 1);
if (!newarg)
return alloc_failed();
memcpy(newarg, arg, sep);
strcpy(newarg + sep, param);
res = process_opt(ctx, opt, sep, newarg, iso);
free(newarg);
return res;
}
static int process_gopt(struct fuse_opt_context *ctx, const char *arg, int iso)
{
unsigned sep;
const struct fuse_opt *opt = find_opt(ctx->opt, arg, &sep);
if (opt) {
for (; opt; opt = find_opt(opt + 1, arg, &sep)) {
int res;
if (sep && opt->templ[sep] == ' ' && !arg[sep])
res = process_opt_sep_arg(ctx, opt, sep, arg, iso);
else
res = process_opt(ctx, opt, sep, arg, iso);
if (res == -1)
return -1;
}
return 0;
} else
return call_proc(ctx, arg, FUSE_OPT_KEY_OPT, iso);
}
static int process_real_option_group(struct fuse_opt_context *ctx, char *opts)
{
char *sep;
do {
int res;
sep = strchr(opts, ',');
if (sep)
*sep = '\0';
res = process_gopt(ctx, opts, 1);
if (res == -1)
return -1;
opts = sep + 1;
} while (sep);
return 0;
}
static int process_option_group(struct fuse_opt_context *ctx, const char *opts)
{
int res;
char *copy;
const char *sep = strchr(opts, ',');
if (!sep)
return process_gopt(ctx, opts, 1);
copy = strdup(opts);
if (!copy) {
fprintf(stderr, "fuse: memory allocation failed\n");
return -1;
}
res = process_real_option_group(ctx, copy);
free(copy);
return res;
}
static int process_one(struct fuse_opt_context *ctx, const char *arg)
{
if (ctx->nonopt || arg[0] != '-')
return call_proc(ctx, arg, FUSE_OPT_KEY_NONOPT, 0);
else if (arg[1] == 'o') {
if (arg[2])
return process_option_group(ctx, arg + 2);
else {
if (next_arg(ctx, arg) == -1)
return -1;
return process_option_group(ctx, ctx->argv[ctx->argctr]);
}
} else if (arg[1] == '-' && !arg[2]) {
if (add_arg(ctx, arg) == -1)
return -1;
ctx->nonopt = ctx->outargs.argc;
return 0;
} else
return process_gopt(ctx, arg, 0);
}
static int opt_parse(struct fuse_opt_context *ctx)
{
if (ctx->argc) {
if (add_arg(ctx, ctx->argv[0]) == -1)
return -1;
}
for (ctx->argctr = 1; ctx->argctr < ctx->argc; ctx->argctr++)
if (process_one(ctx, ctx->argv[ctx->argctr]) == -1)
return -1;
if (ctx->opts) {
if (fuse_opt_insert_arg(&ctx->outargs, 1, "-o") == -1 ||
fuse_opt_insert_arg(&ctx->outargs, 2, ctx->opts) == -1)
return -1;
}
if (ctx->nonopt && ctx->nonopt == ctx->outargs.argc) {
free(ctx->outargs.argv[ctx->outargs.argc - 1]);
ctx->outargs.argv[--ctx->outargs.argc] = NULL;
}
return 0;
}
int fuse_opt_parse(struct fuse_args *args, void *data,
const struct fuse_opt opts[], fuse_opt_proc_t proc)
{
int res;
struct fuse_opt_context ctx = {
.data = data,
.opt = opts,
.proc = proc,
};
if (!args || !args->argv || !args->argc)
return 0;
ctx.argc = args->argc;
ctx.argv = args->argv;
res = opt_parse(&ctx);
if (res != -1) {
struct fuse_args tmp = *args;
*args = ctx.outargs;
ctx.outargs = tmp;
}
free(ctx.opts);
fuse_opt_free_args(&ctx.outargs);
return res;
}
-258
View File
@@ -1,258 +0,0 @@
/*
FUSE: Filesystem in Userspace
Copyright (C) 2001-2006 Miklos Szeredi <miklos@szeredi.hu>
This program can be distributed under the terms of the GNU GPL.
See the file COPYING.
*/
#ifndef _FUSE_OPT_H_
#define _FUSE_OPT_H_
/* This file defines the option parsing interface of FUSE */
#ifdef __cplusplus
extern "C" {
#endif
/**
* Option description
*
* This structure describes a single option, and and action associated
* with it, in case it matches.
*
* More than one such match may occur, in which case the action for
* each match is executed.
*
* There are three possible actions in case of a match:
*
* i) An integer (int or unsigned) variable determined by 'offset' is
* set to 'value'
*
* ii) The processing function is called, with 'value' as the key
*
* iii) An integer (any) or string (char *) variable determined by
* 'offset' is set to the value of an option parameter
*
* 'offset' should normally be either set to
*
* - 'offsetof(struct foo, member)' actions i) and iii)
*
* - -1 action ii)
*
* The 'offsetof()' macro is defined in the <stddef.h> header.
*
* The template determines which options match, and also have an
* effect on the action. Normally the action is either i) or ii), but
* if a format is present in the template, then action iii) is
* performed.
*
* The types of templates are:
*
* 1) "-x", "-foo", "--foo", "--foo-bar", etc. These match only
* themselves. Invalid values are "--" and anything beginning
* with "-o"
*
* 2) "foo", "foo-bar", etc. These match "-ofoo", "-ofoo-bar" or
* the relevant option in a comma separated option list
*
* 3) "bar=", "--foo=", etc. These are variations of 1) and 2)
* which have a parameter
*
* 4) "bar=%s", "--foo=%lu", etc. Same matching as above but perform
* action iii).
*
* 5) "-x ", etc. Matches either "-xparam" or "-x param" as
* two separate arguments
*
* 6) "-x %s", etc. Combination of 4) and 5)
*
* If the format is "%s", memory is allocated for the string unlike
* with scanf().
*/
struct fuse_opt {
/** Matching template and optional parameter formatting */
const char *templ;
/**
* Offset of variable within 'data' parameter of fuse_opt_parse()
* or -1
*/
unsigned long offset;
/**
* Value to set the variable to, or to be passed as 'key' to the
* processing function. Ignored if template has a format
*/
int value;
};
/**
* Key option. In case of a match, the processing function will be
* called with the specified key.
*/
#define FUSE_OPT_KEY(templ, key) { templ, -1U, key }
/**
* Last option. An array of 'struct fuse_opt' must end with a NULL
* template value
*/
#define FUSE_OPT_END { .templ = NULL }
/**
* Argument list
*/
struct fuse_args {
/** Argument count */
int argc;
/** Argument vector. NULL terminated */
char **argv;
/** Is 'argv' allocated? */
int allocated;
};
/**
* Initializer for 'struct fuse_args'
*/
#define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 }
/**
* Key value passed to the processing function if an option did not
* match any template
*/
#define FUSE_OPT_KEY_OPT -1
/**
* Key value passed to the processing function for all non-options
*
* Non-options are the arguments beginning with a charater other than
* '-' or all arguments after the special '--' option
*/
#define FUSE_OPT_KEY_NONOPT -2
/**
* Special key value for options to keep
*
* Argument is not passed to processing function, but behave as if the
* processing function returned 1
*/
#define FUSE_OPT_KEY_KEEP -3
/**
* Special key value for options to discard
*
* Argument is not passed to processing function, but behave as if the
* processing function returned zero
*/
#define FUSE_OPT_KEY_DISCARD -4
/**
* Processing function
*
* This function is called if
* - option did not match any 'struct fuse_opt'
* - argument is a non-option
* - option did match and offset was set to -1
*
* The 'arg' parameter will always contain the whole argument or
* option including the parameter if exists. A two-argument option
* ("-x foo") is always converted to single arguemnt option of the
* form "-xfoo" before this function is called.
*
* Options of the form '-ofoo' are passed to this function without the
* '-o' prefix.
*
* The return value of this function determines whether this argument
* is to be inserted into the output argument vector, or discarded.
*
* @param data is the user data passed to the fuse_opt_parse() function
* @param arg is the whole argument or option
* @param key determines why the processing function was called
* @param outargs the current output argument list
* @return -1 on error, 0 if arg is to be discarded, 1 if arg should be kept
*/
typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key,
struct fuse_args *outargs);
/**
* Option parsing function
*
* If 'args' was returned from a previous call to fuse_opt_parse() or
* it was constructed from
*
* A NULL 'args' is equivalent to an empty argument vector
*
* A NULL 'opts' is equivalent to an 'opts' array containing a single
* end marker
*
* A NULL 'proc' is equivalent to a processing function always
* returning '1'
*
* @param args is the input and output argument list
* @param data is the user data
* @param opts is the option description array
* @param proc is the processing function
* @return -1 on error, 0 on success
*/
int fuse_opt_parse(struct fuse_args *args, void *data,
const struct fuse_opt opts[], fuse_opt_proc_t proc);
/**
* Add an option to a comma separated option list
*
* @param opts is a pointer to an option list, may point to a NULL value
* @param opt is the option to add
* @return -1 on allocation error, 0 on success
*/
int fuse_opt_add_opt(char **opts, const char *opt);
/**
* Add an argument to a NULL terminated argument vector
*
* @param args is the structure containing the current argument list
* @param arg is the new argument to add
* @return -1 on allocation error, 0 on success
*/
int fuse_opt_add_arg(struct fuse_args *args, const char *arg);
/**
* Add an argument at the specified position in a NULL terminated
* argument vector
*
* Adds the argument to the N-th position. This is useful for adding
* options at the beggining of the array which must not come after the
* special '--' option.
*
* @param args is the structure containing the current argument list
* @param pos is the position at which to add the argument
* @param arg is the new argument to add
* @return -1 on allocation error, 0 on success
*/
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg);
/**
* Free the contents of argument list
*
* The structure itself is not freed
*
* @param args is the structure containing the argument list
*/
void fuse_opt_free_args(struct fuse_args *args);
/**
* Check if an option matches
*
* @param opts is the option description array
* @param opt is the option to match
* @return 1 if a match is found, 0 if not
*/
int fuse_opt_match(const struct fuse_opt opts[], const char *opt);
#ifdef __cplusplus
}
#endif
#endif /* _FUSE_OPT_H_ */
-71
View File
@@ -1,71 +0,0 @@
AC_INIT(sshfs, 2.9)
AC_CANONICAL_TARGET
AM_INIT_AUTOMAKE([foreign subdir-objects])
AM_CONFIG_HEADER(config.h)
AC_PROG_CC
AM_PROG_CC_C_O
CFLAGS="$CFLAGS -Wall -W"
LIBS=
AC_SEARCH_LIBS(dlsym, [dl])
sshnodelay_libs=$LIBS
AC_SUBST(sshnodelay_libs)
LIBS=
case "$target_os" in
*linux*) osname=linux;;
*darwin*) osname=darwin;;
*) osname=unknown;;
esac
AC_ARG_ENABLE(sshnodelay,
[ --disable-sshnodelay Don't compile NODELAY workaround for ssh])
if test -z "$enable_sshnodelay"; then
AC_MSG_CHECKING([OpenSSH version])
[eval `ssh -V 2>&1 | sed -n 's/^OpenSSH_\([1-9][0-9]*\)\.\([0-9][0-9]*\).*/ssh_major=\1 ssh_minor=\2/p'`]
if test "x$ssh_major" != x -a "x$ssh_minor" != x; then
if test $ssh_major -gt 4 -o \( $ssh_major = 4 -a $ssh_minor -ge 4 \); then
AC_MSG_RESULT([$ssh_major.$ssh_minor >= 4.4, disabling NODELAY workaround])
enable_sshnodelay=no
else
AC_MSG_RESULT([$ssh_major.$ssh_minor < 4.4, enabling NODELAY workaround])
enable_sshnodelay=yes
fi
else
AC_MSG_RESULT([not found])
fi
fi
if test "$enable_sshnodelay" = "yes"; then
AC_DEFINE(SSH_NODELAY_WORKAROUND, 1, [Compile ssh NODELAY workaround])
fi
AM_CONDITIONAL(SSH_NODELAY_SO, test "$enable_sshnodelay" = "yes")
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH
PKG_CHECK_MODULES([SSHFS], [fuse >= 2.3 glib-2.0 gthread-2.0])
have_fuse_opt_parse=no
oldlibs="$LIBS"
LIBS="$LIBS $SSHFS_LIBS"
AC_CHECK_FUNC([fuse_opt_parse], [have_fuse_opt_parse=yes])
LIBS="$oldlibs"
if test "$have_fuse_opt_parse" = no -o "$osname" = darwin; then
CFLAGS="$CFLAGS -I${srcdir}/compat"
fi
AM_CONDITIONAL(FUSE_OPT_COMPAT, test "$have_fuse_opt_parse" = no)
AM_CONDITIONAL(DARWIN_COMPAT, test "$osname" = darwin)
AC_CHECK_PROG(UNMOUNT_COMMAND, fusermount, fusermount -u, umount)
# TODO: Figure out why we special-case this in Darwin. Would be nice if
# the default setting was consistent across platforms so we wouldn't need
# to care about it here.
case "$osname" in
darwin) IDMAP_DEFAULT=user ;;
*) IDMAP_DEFAULT=none ;;
esac
AC_SUBST(IDMAP_DEFAULT)
AC_CONFIG_FILES([Makefile])
AC_OUTPUT
+29
View File
@@ -0,0 +1,29 @@
#!/bin/sh
#
# Create tarball from Git tag, removing and adding
# some files.
#
set -e
if [ -z "$1" ]; then
TAG="$(git tag --list 'sshfs-3*' --sort=-taggerdate | head -1)"
else
TAG="$1"
fi
echo "Creating release tarball for ${TAG}..."
mkdir "${TAG}"
git archive --format=tar "${TAG}" | tar -x "--directory=${TAG}"
find "${TAG}" -name .gitignore -delete
rm "${TAG}/make_release_tarball.sh" \
"${TAG}/.travis.yml" \
"${TAG}/test/travis-build.sh" \
"${TAG}/test/travis-install.sh"
tar -cJf "${TAG}.tar.xz" "${TAG}/"
gpg --armor --detach-sign "${TAG}.tar.xz"
PREV_TAG="$(git tag --list 'sshfs-3*' --sort=-taggerdate --merged "${TAG}^"| head -1)"
echo "Contributors from ${PREV_TAG} to ${TAG}:"
git log --pretty="format:%an <%aE>" "${PREV_TAG}..${TAG}" | sort -u
+72
View File
@@ -0,0 +1,72 @@
project('sshfs', 'c', version: '3.7.5',
meson_version: '>= 0.40',
default_options: [ 'buildtype=debugoptimized' ])
add_global_arguments('-D_REENTRANT', '-DHAVE_CONFIG_H',
'-Wall', '-Wextra', '-Wno-sign-compare',
'-Wmissing-declarations', '-Wwrite-strings',
language: 'c')
# Some (stupid) GCC versions warn about unused return values even when they are
# casted to void. This makes -Wunused-result pretty useless, since there is no
# way to suppress the warning when we really *want* to ignore the value.
cc = meson.get_compiler('c')
code = '''
__attribute__((warn_unused_result)) int get_4() {
return 4;
}
int main(void) {
(void) get_4();
return 0;
}'''
if not cc.compiles(code, args: [ '-O0', '-Werror=unused-result' ])
message('Compiler warns about unused result even when casting to void')
add_global_arguments('-Wno-unused-result', language: 'c')
endif
rst2man = find_program('rst2man', 'rst2man.py', required: false)
cfg = configuration_data()
cfg.set_quoted('PACKAGE_VERSION', meson.project_version())
include_dirs = [ include_directories('.') ]
sshfs_sources = ['sshfs.c', 'cache.c']
if target_machine.system() == 'darwin'
add_global_arguments('-DFUSE_DARWIN_ENABLE_EXTENSIONS=0', language: 'c')
cfg.set_quoted('IDMAP_DEFAULT', 'user')
sshfs_sources += [ 'compat/darwin_compat.c' ]
include_dirs += [ include_directories('compat') ]
else
cfg.set_quoted('IDMAP_DEFAULT', 'none')
endif
configure_file(output: 'config.h',
configuration : cfg)
sshfs_deps = [ dependency('fuse3', version: '>= 3.1.0'),
dependency('glib-2.0'),
dependency('gthread-2.0') ]
executable('sshfs', sshfs_sources,
include_directories: include_dirs,
dependencies: sshfs_deps,
c_args: ['-DFUSE_USE_VERSION=31'],
install: true,
install_dir: get_option('bindir'))
if rst2man.found()
custom_target('manpages', input: [ 'sshfs.rst' ], output: [ 'sshfs.1' ],
command: [rst2man, '@INPUT@', '@OUTPUT@'], install: true,
install_dir: join_paths(get_option('mandir'), 'man1'))
else
message('rst2man not found, not building manual page.')
endif
meson.add_install_script('utils/install_helper.sh',
get_option('sbindir'),
get_option('bindir'))
subdir('test')
-284
View File
@@ -1,284 +0,0 @@
.TH SSHFS "1" "April 2008" "SSHFS version 2.0" "User Commands"
.SH NAME
SSHFS \- filesystem client based on ssh
.SH SYNOPSIS
.SS mounting
.TP
\fBsshfs\fP [\fIuser\fP@]\fBhost\fP:[\fIdir\fP] \fBmountpoint\fP [\fIoptions\fP]
.SS unmounting
.TP
\fB__UNMOUNT_COMMAND__ mountpoint\fP
.SH DESCRIPTION
SSHFS (Secure SHell FileSystem) is a file system for Linux (and other
operating systems with a FUSE implementation, such as Mac OS X or FreeBSD)
capable of operating on files on a remote computer using just a secure
shell login on the remote computer. On the local computer where the SSHFS
is mounted, the implementation makes use of the FUSE (Filesystem in
Userspace) kernel module. The practical effect of this is that the end user
can seamlessly interact with remote files being securely served over SSH
just as if they were local files on his/her computer. On the remote
computer the SFTP subsystem of SSH is used.
.PP
If \fIhost\fP is a numeric IPv6 address, it needs to be enclosed in
square brackets.
.SH OPTIONS
.SS "general options:"
.TP
\fB\-o\fR opt,[opt...]
mount options
.TP
\fB\-h\fR \fB\-\-help\fR
print help
.TP
\fB\-V\fR \fB\-\-version\fR
print version
.SS "SSHFS options:"
.TP
\fB\-p\fR PORT
equivalent to '\-o port=PORT'
.TP
\fB\-C\fR
equivalent to '\-o compression=yes'
.TP
\fB\-F\fR ssh_configfile
specifies alternative ssh configuration file
.TP
\fB\-1\fR
equivalent to '\-o ssh_protocol=1'
.TP
\fB\-o\fR reconnect
reconnect to server
.TP
\fB\-o\fR delay_connect
delay connection to server
.TP
\fB\-o\fR sshfs_sync
synchronous writes
.TP
\fB\-o\fR no_readahead
synchronous reads (no speculative readahead)
.TP
\fB\-o\fR sync_readdir
synchronous readdir
.TP
\fB\-o\fR sshfs_debug
print some debugging information
.TP
\fB\-o\fR cache=BOOL
enable caching {yes,no} (default: yes)
.TP
\fB\-o\fR cache_timeout=N
sets timeout for caches in seconds (default: 20)
.TP
\fB\-o\fR cache_X_timeout=N
sets timeout for {stat,dir,link} cache
.TP
\fB\-o\fR workaround=LIST
colon separated list of workarounds
.RS 8
.TP
none
no workarounds enabled
.TP
all
all workarounds enabled
.TP
[no]rename
fix renaming to existing file (default: off)
.TP
[no]nodelaysrv
set nodelay tcp flag in ssh (default: off)
.TP
[no]truncate
fix truncate for old servers (default: off)
.TP
[no]buflimit
fix buffer fillup bug in server (default: on)
.RE
.TP
\fB\-o\fR idmap=TYPE
user/group ID mapping (default: __IDMAP_DEFAULT__)
.RS 8
.TP
none
no translation of the ID space
.TP
user
only translate UID/GID of connecting user
.TP
file
translate UIDs/GIDs based upon the contents of \fBuidfile \fR and
\fBgidfile\fR
.RE
.TP
\fB\-o\fR uidfile=FILE
file containing username:uid mappings for \fBidmap=file\fR
.RE
.TP
\fB\-o\fR gidfile=FILE
file containing groupname:gid mappings for \fBidmap=file\fR
.RE
.TP
\fB\-o\fR nomap=TYPE
with idmap=file, how to handle missing mappings
.RS 8
.TP
ignore
don't do any re-mapping
.TP
error
return an error (default)
.RE
.TP
\fB\-o\fR ssh_command=CMD
execute CMD instead of 'ssh'
.TP
\fB\-o\fR ssh_protocol=N
ssh protocol to use (default: 2)
.TP
\fB\-o\fR sftp_server=SERV
path to sftp server or subsystem (default: sftp)
.TP
\fB\-o\fR directport=PORT
directly connect to PORT bypassing ssh
.TP
\fB\-o\fR slave
communicate over stdin and stdout bypassing network
.TP
\fB\-o\fR disable_hardlink
link(2) will return with errno set to ENOSYS. Hard links don't currently work
perfectly on sshfs, and this confuses some programs. If that happens try
disabling hard links with this option.
.TP
\fB\-o\fR transform_symlinks
transform absolute symlinks to relative
.TP
\fB\-o\fR follow_symlinks
follow symlinks on the server
.TP
\fB\-o\fR no_check_root
don't check for existence of 'dir' on server
.TP
\fB\-o\fR password_stdin
read password from stdin (only for pam_mount!)
.TP
\fB\-o\fR SSHOPT=VAL
ssh options (see man ssh_config)
.SS "FUSE options:"
.TP
\fB\-d\fR \fB\-o\fR debug
enable debug output (implies \fB\-f\fR)
.TP
\fB\-f\fR
foreground operation
.TP
\fB\-s\fR
disable multi\-threaded operation
.TP
\fB\-o\fR allow_other
allow access to other users
.TP
\fB\-o\fR allow_root
allow access to root
.TP
\fB\-o\fR nonempty
allow mounts over non\-empty file/dir
.HP
\fB\-o\fR default_permissions
enable permission checking by kernel
.TP
\fB\-o\fR fsname=NAME
set filesystem name
.TP
\fB\-o\fR subtype=NAME
set filesystem type
.TP
\fB\-o\fR large_read
issue large read requests (2.4 only)
.TP
\fB\-o\fR max_read=N
set maximum size of read requests
.TP
\fB\-o\fR hard_remove
immediate removal (don't hide files)
.TP
\fB\-o\fR use_ino
let filesystem set inode numbers
.TP
\fB\-o\fR readdir_ino
try to fill in d_ino in readdir
.TP
\fB\-o\fR direct_io
use direct I/O
.TP
\fB\-o\fR kernel_cache
cache files in kernel
.TP
\fB\-o\fR [no]auto_cache
enable caching based on modification times
.TP
\fB\-o\fR umask=M
set file permissions (octal)
.TP
\fB\-o\fR uid=N
set file owner
.TP
\fB\-o\fR gid=N
set file group
.TP
\fB\-o\fR entry_timeout=T
cache timeout for names (1.0s)
.TP
\fB\-o\fR negative_timeout=T
cache timeout for deleted names (0.0s)
.TP
\fB\-o\fR attr_timeout=T
cache timeout for attributes (1.0s)
.TP
\fB\-o\fR ac_attr_timeout=T
auto cache timeout for attributes (attr_timeout)
.TP
\fB\-o\fR intr
allow requests to be interrupted
.TP
\fB\-o\fR intr_signal=NUM
signal to send on interrupt (10)
.TP
\fB\-o\fR modules=M1[:M2...]
names of modules to push onto filesystem stack
.TP
\fB\-o\fR max_write=N
set maximum size of write requests
.TP
\fB\-o\fR max_readahead=N
set maximum readahead
.TP
\fB\-o\fR async_read
perform reads asynchronously (default)
.TP
\fB\-o\fR sync_read
perform reads synchronously
.SS "Module options:"
.TP
[subdir]
.TP
\fB\-o\fR subdir=DIR
prepend this directory to all paths (mandatory)
.TP
\fB\-o\fR [no]rellinks
transform absolute symlinks to relative
.TP
[iconv]
.TP
\fB\-o\fR from_code=CHARSET
original encoding of file names (default: UTF-8)
.TP
\fB\-o\fR to_code=CHARSET
new encoding of the file names (default: ISO-8859-2)
.PD
.SH "AUTHORS"
.LP
SSHFS has been written by Miklos Szeredi <miklos@szeredi.hu>.
.LP
This man page was written by Bartosz Fenski <fenio@debian.org> for the
Debian GNU/Linux distribution (but it may be used by others).
+1061 -763
View File
File diff suppressed because it is too large Load Diff
+346
View File
@@ -0,0 +1,346 @@
=======
SSHFS
=======
---------------------------------------------
filesystem client based on SSH
---------------------------------------------
:Manual section: 1
:Manual group: User Commands
Synopsis
========
To mount a filesystem::
sshfs [user@]host:[dir] mountpoint [options]
If *host* is a numeric IPv6 address, it needs to be enclosed in square
brackets.
To unmount it::
fusermount3 -u mountpoint # Linux
umount mountpoint # OS X, FreeBSD
Description
===========
SSHFS allows you to mount a remote filesystem using SSH (more precisely, the SFTP
subsystem). Most SSH servers support and enable this SFTP access by default, so SSHFS is
very simple to use - there's nothing to do on the server-side.
By default, file permissions are ignored by SSHFS. Any user that can access the filesystem
will be able to perform any operation that the remote server permits - based on the
credentials that were used to connect to the server. If this is undesired, local
permission checking can be enabled with ``-o default_permissions``.
By default, only the mounting user will be able to access the filesystem. Access for other
users can be enabled by passing ``-o allow_other``. In this case you most likely also
want to use ``-o default_permissions``.
It is recommended to run SSHFS as regular user (not as root). For this to work the
mountpoint must be owned by the user. If username is omitted SSHFS will use the local
username. If the directory is omitted, SSHFS will mount the (remote) home directory. If
you need to enter a password sshfs will ask for it (actually it just runs ssh which ask
for the password if needed).
Options
=======
-o opt,[opt...]
mount options, see below for details. A a variety of SSH options can
be given here as well, see the manual pages for *sftp(1)* and
*ssh_config(5)*.
-h, --help
print help and exit.
-V, --version
print version information and exit.
-d, --debug
print debugging information.
-p PORT
equivalent to '-o port=PORT'
-f
do not daemonize, stay in foreground.
-s
Single threaded operation.
-C
equivalent to '-o compression=yes'
-F ssh_configfile
specifies alternative ssh configuration file
-1
equivalent to '-o ssh_protocol=1'
-o reconnect
automatically reconnect to server if connection is
interrupted. Attempts to access files that were opened before the
reconnection will give errors and need to be re-opened.
-o delay_connect
Don't immediately connect to server, wait until mountpoint is first
accessed.
-o sshfs_sync
synchronous writes. This will slow things down, but may be useful
in some situations.
-o no_readahead
Only read exactly the data that was requested, instead of
speculatively reading more to anticipate the next read request.
-o sync_readdir
synchronous readdir. This will slow things down, but may be useful
in some situations.
-o workaround=LIST
Enable the specified workaround. See the `Caveats` section below
for some additional information. Possible values are:
:rename: Emulate overwriting an existing file by deleting and
renaming.
:renamexdev: Make rename fail with EXDEV instead of the default EPERM
to allow moving files across remote filesystems.
:truncate: Work around servers that don't support truncate by
coping the whole file, truncating it locally, and sending it
back.
:fstat: Work around broken servers that don't support *fstat()* by
using *stat* instead.
:buflimit: Work around OpenSSH "buffer fillup" bug.
:createmode: Work around broken servers that produce an error when passing a
non-zero mode to create, by always passing a mode of 0.
-o idmap=TYPE
How to map remote UID/GIDs to local values. Possible values are:
:none: no translation of the ID space (default).
:user: map the UID/GID of the remote user to UID/GID of the
mounting user.
:file: translate UIDs/GIDs based upon the contents of `--uidfile`
and `--gidfile`.
-o uidfile=FILE
file containing ``username:uid`` mappings for `-o idmap=file`
-o gidfile=FILE
file containing ``groupname:gid`` mappings for `-o idmap=file`
-o nomap=TYPE
with idmap=file, how to handle missing mappings:
:ignore: don't do any re-mapping
:error: return an error (default)
-o ssh_command=CMD
execute CMD instead of 'ssh'
-o ssh_protocol=N
ssh protocol to use (default: 2)
-o sftp_server=SERV
path to sftp server or subsystem (default: sftp)
-o directport=PORT
directly connect to PORT bypassing ssh
-o vsock=CID:PORT
directly connect using a vsock to CID:PORT bypassing ssh
-o passive
communicate over stdin and stdout bypassing network. Useful for
mounting local filesystem on the remote side. An example using
dpipe command would be ``dpipe /usr/lib/openssh/sftp-server = ssh
RemoteHostname sshfs :/directory/to/be/shared ~/mnt/src -o passive``
-o disable_hardlink
With this option set, attempts to call `link(2)` will fail with
error code ENOSYS.
-o transform_symlinks
transform absolute symlinks on remote side to relative
symlinks. This means that if e.g. on the server side
``/foo/bar/com`` is a symlink to ``/foo/blub``, SSHFS will
transform the link target to ``../blub`` on the client side.
-o follow_symlinks
follow symlinks on the server, i.e. present them as regular
files on the client. If a symlink is dangling (i.e, the target does
not exist) the behavior depends on the remote server - the entry
may appear as a symlink on the client, or it may appear as a
regular file that cannot be accessed.
-o no_check_root
don't check for existence of 'dir' on server
-o password_stdin
read password from stdin (only for pam_mount!)
-o dir_cache=BOOL
Enables (*yes*) or disables (*no*) the SSHFS directory cache. The
directory cache holds the names of directory entries. Enabling it
allows `readdir(3)` system calls to be processed without network
access.
-o dcache_max_size=N
sets the maximum size of the directory cache.
-o dcache_timeout=N
sets timeout for directory cache in seconds.
-o dcache_{stat,link,dir}_timeout=N
sets separate timeout for {attributes, symlinks, names} in the
directory cache.
-o dcache_clean_interval=N
sets the interval for automatic cleaning of the directory cache.
-o dcache_min_clean_interval=N
sets the interval for forced cleaning of the directory cache
when full.
-o direct_io
This option disables the use of page cache (file content cache) in
the kernel for this filesystem.
This has several affects:
1. Each read() or write() system call will initiate one or more read or
write operations, data will not be cached in the kernel.
2. The return value of the read() and write() system calls will correspond
to the return values of the read and write operations. This is useful
for example if the file size is not known in advance (before reading it).
e.g. /proc filesystem
-o max_conns=N
sets the maximum number of simultaneous SSH connections
to use. Each connection is established with a separate SSH process.
The primary purpose of this feature is to improve the responsiveness of the
file system during large file transfers. When using more than once
connection, the *password_stdin* and *passive* options can not be
used, and the *buflimit* workaround is not supported.
In addition, SSHFS accepts several options common to all FUSE file
systems. These are described in the `mount.fuse` manpage (look
for "general", "libfuse specific", and "high-level API" options).
Caveats / Workarounds
=====================
Hardlinks
~~~~~~~~~
If the SSH server supports the *hardlinks* extension, SSHFS will allow
you to create hardlinks. However, hardlinks will always appear as
individual files when seen through an SSHFS mount, i.e. they will
appear to have different inodes and an *st_nlink* value of 1.
Rename
~~~~~~
Some SSH servers do not support atomically overwriting the destination
when renaming a file. In this case you will get an error when you
attempt to rename a file and the destination already exists. A
workaround is to first remove the destination file, and then do the
rename. SSHFS can do this automatically if you call it with `-o
workaround=rename`. However, in this case it is still possible that
someone (or something) recreates the destination file after SSHFS has
removed it, but before SSHFS had the time to rename the old file. In
this case, the rename will still fail.
Permission denied when moving files across remote filesystems
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Most SFTP servers return only a generic "failure" when failing to rename
across filesystem boundaries (EXDEV). sshfs normally converts this generic
failure to a permission denied error (EPERM). If the option ``-o
workaround=renamexdev`` is given, generic failures will be considered EXDEV
errors which will make programs like `mv(1)` attempt to actually move the
file after the failed rename.
SSHFS hangs for no apparent reason
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In some cases, attempts to access the SSHFS mountpoint may freeze if
no filesystem activity has occurred for some time. This is typically
caused by the SSH connection being dropped because of inactivity
without SSHFS being informed about that. As a workaround, you can try
to mount with ``-o ServerAliveInterval=15``. This will force the SSH
connection to stay alive even if you have no activity.
SSHFS hangs after the connection was interrupted
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
By default, network operations in SSHFS run without timeouts, mirroring the
default behavior of SSH itself. As a consequence, if the connection to the
remote host is interrupted (e.g. because a network cable was removed),
operations on files or directories under the mountpoint will block until the
connection is either restored or closed altogether (e.g. manually).
Applications that try to access such files or directories will generally appear
to "freeze" when this happens.
If it is acceptable to discard data being read or written, a quick workaround
is to kill the responsible ``sshfs`` process, which will make any blocking
operations on the mounted filesystem error out and thereby "unfreeze" the
relevant applications. Note that force unmounting with ``fusermount -zu``, on
the other hand, does not help in this case and will leave read/write operations
in the blocking state.
For a more automatic solution, one can use the ``-o ServerAliveInterval=15``
option mentioned above, which will drop the connection after not receiving a
response for 3 * 15 = 45 seconds from the remote host. By also supplying ``-o
reconnect``, one can ensure that the connection is re-established as soon as
possible afterwards. As before, this will naturally lead to loss of data that
was in the process of being read or written at the time when the connection was
interrupted.
Mounting from /etc/fstab
========================
To mount an SSHFS filesystem from ``/etc/fstab``, simply use ``sshfs``
as the file system type. (For backwards compatibility, you may also
use ``fuse.sshfs``).
See also
========
The `mount.fuse(8)` manpage.
Getting Help
============
If you need help, please ask on the <fuse-sshfs@lists.sourceforge.net>
mailing list (subscribe at
https://lists.sourceforge.net/lists/listinfo/fuse-sshfs).
Please report any bugs on the GitHub issue tracker at
https://github.com/libfuse/libfuse/issues.
Authors
=======
SSHFS is currently maintained by Nikolaus Rath <Nikolaus@rath.org>,
and was created by Miklos Szeredi <miklos@szeredi.hu>.
This man page was originally written by Bartosz Fenski
<fenio@debian.org> for the Debian GNU/Linux distribution (but it may
be used by others).
-59
View File
@@ -1,59 +0,0 @@
#define _GNU_SOURCE
#include <dlfcn.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
/* Wrapper around connect(2) to explicitly set TCP_NODELAY. */
static int nodelay_connect(
int (*real_connect)(int, const struct sockaddr *, socklen_t),
int sock, const struct sockaddr *addr, socklen_t addrlen)
{
int res = real_connect(sock, addr, addrlen);
if (!res && addr->sa_family == AF_INET) {
int opt = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));
}
return res;
}
#if __APPLE__
/* OS X does not have LD_PRELOAD but has DYLD_INSERT_LIBRARIES. The right
* environment variable is set by sshfs.c when attempting to load the
* sshnodelay workaround.
*
* However, things are not that simple: DYLD_INSERT_LIBRARIES does not
* behave exactly like LD_PRELOAD. Instead, the dyld dynamic linker will
* look for __DATA __interpose sections on the libraries given via the
* DYLD_INSERT_LIBRARIES variable. The contents of this section are pairs
* of replacement functions and functions to be replaced, respectively.
* Prepare such section here. */
int custom_connect(int sock, const struct sockaddr *addr, socklen_t addrlen);
typedef struct interpose_s {
void *new_func;
void *orig_func;
} interpose_t;
static const interpose_t interposers[] \
__attribute__ ((section("__DATA, __interpose"))) = {
{ (void *)custom_connect, (void *)connect },
};
int custom_connect(int sock, const struct sockaddr *addr, socklen_t addrlen)
{
return nodelay_connect(connect, sock, addr, addrlen);
}
#else /* !__APPLE__ */
int connect(int sock, const struct sockaddr *addr, socklen_t addrlen)
{
return nodelay_connect(dlsym(RTLD_NEXT, "connect"),
sock, addr, addrlen);
}
#endif /* !__APPLE__ */
+1
View File
@@ -0,0 +1 @@
__pycache__/
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
set -e
machine=$(uname -m)
mkdir "build-$machine"
cd "build-$machine"
meson ..
ninja
+112
View File
@@ -0,0 +1,112 @@
import sys
import pytest
import time
import re
# If a test fails, wait a moment before retrieving the captured stdout/stderr.
# When using a server process, this makes sure that we capture any potential
# output of the server that comes *after* a test has failed. For example, if a
# request handler raises an exception, the server first signals an error to
# FUSE (causing the test to fail), and then logs the exception. Without the
# extra delay, the exception will go into nowhere.
@pytest.hookimpl(hookwrapper=True)
def pytest_pyfunc_call(pyfuncitem):
outcome = yield
failed = outcome.excinfo is not None
if failed:
time.sleep(1)
@pytest.fixture()
def pass_capfd(request, capfd):
"""Provide capfd object to UnitTest instances"""
request.instance.capfd = capfd
def check_test_output(capfd):
(stdout, stderr) = capfd.readouterr()
# Write back what we've read (so that it will still be printed.
sys.stdout.write(stdout)
sys.stderr.write(stderr)
# Strip out false positives
for (pattern, flags, count) in capfd.false_positives:
cp = re.compile(pattern, flags)
(stdout, cnt) = cp.subn("", stdout, count=count)
if count == 0 or count - cnt > 0:
stderr = cp.sub("", stderr, count=count - cnt)
patterns = [
r"\b{}\b".format(x)
for x in (
"exception",
"error",
"warning",
"fatal",
"traceback",
"fault",
"crash(?:ed)?",
"abort(?:ed)",
"uninitiali[zs]ed",
)
]
patterns += ["^==[0-9]+== "]
for pattern in patterns:
cp = re.compile(pattern, re.IGNORECASE | re.MULTILINE)
hit = cp.search(stderr)
if hit:
raise AssertionError(
'Suspicious output to stderr (matched "%s")' % hit.group(0)
)
hit = cp.search(stdout)
if hit:
raise AssertionError(
'Suspicious output to stdout (matched "%s")' % hit.group(0)
)
def register_output(self, pattern, count=1, flags=re.MULTILINE):
"""Register *pattern* as false positive for output checking
This prevents the test from failing because the output otherwise
appears suspicious.
"""
self.false_positives.append((pattern, flags, count))
# This is a terrible hack that allows us to access the fixtures from the
# pytest_runtest_call hook. Among a lot of other hidden assumptions, it probably
# relies on tests running sequential (i.e., don't dare to use e.g. the xdist
# plugin)
current_capfd = None
@pytest.fixture(autouse=True)
def save_cap_fixtures(request, capfd):
global current_capfd
capfd.false_positives = []
# Monkeypatch in a function to register false positives
type(capfd).register_output = register_output
if request.config.getoption("capture") == "no":
capfd = None
current_capfd = capfd
bak = current_capfd
yield
# Try to catch problems with this hack (e.g. when running tests
# simultaneously)
assert bak is current_capfd
current_capfd = None
@pytest.hookimpl(trylast=True)
def pytest_runtest_call(item):
capfd = current_capfd
if capfd is not None:
check_test_output(capfd)
Executable
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
set -e
pip3 install --user pre-commit
pre-commit run --all-files --show-diff-on-failure
+11
View File
@@ -0,0 +1,11 @@
# Suppression file for address sanitizer.
# There are some leaks in command line option parsing. They should be
# fixed at some point, but are harmless since the consume just a small,
# constant amount of memory and do not grow.
leak:fuse_opt_parse
# Leaks in fusermount3 are harmless as well (it's a short-lived
# process) - but patches are welcome!
leak:fusermount.c
+11
View File
@@ -0,0 +1,11 @@
test_scripts = [ 'conftest.py', 'pytest.ini', 'test_sshfs.py',
'util.py' ]
custom_target('test_scripts', input: test_scripts,
output: test_scripts, build_by_default: true,
command: ['cp', '-fPp',
'@INPUT@', meson.current_build_dir() ])
# Provide something helpful when running 'ninja test'
wrong_cmd = executable('wrong_command', 'wrong_command.c',
install: false)
test('wrong_cmd', wrong_cmd)
+4
View File
@@ -0,0 +1,4 @@
[pytest]
addopts = --verbose --assert=rewrite --tb=native -x -r a
markers = uses_fuse: Mark to indicate that FUSE is available on the system running the test
+912
View File
@@ -0,0 +1,912 @@
#!/usr/bin/env python3
if __name__ == "__main__":
import pytest
import sys
sys.exit(pytest.main([__file__] + sys.argv[1:]))
import subprocess
import os
import sys
import pytest
import stat
import shutil
import filecmp
import errno
from tempfile import NamedTemporaryFile
from util import (
wait_for_mount,
umount,
cleanup,
base_cmdline,
basename,
fuse_test_marker,
safe_sleep,
os_create,
os_open,
)
from os.path import join as pjoin
TEST_FILE = __file__
pytestmark = fuse_test_marker()
with open(TEST_FILE, "rb") as fh:
TEST_DATA = fh.read()
def name_generator(__ctr=[0]) -> str:
"""Generate a fresh filename on each call"""
__ctr[0] += 1
return f"testfile_{__ctr[0]}"
@pytest.mark.parametrize(
"debug",
[pytest.param(False, id="debug=false"), pytest.param(True, id="debug=true")],
)
@pytest.mark.parametrize(
"cache_timeout",
[pytest.param(0, id="cache_timeout=0"), pytest.param(1, id="cache_timeout=1")],
)
@pytest.mark.parametrize(
"sync_rd",
[pytest.param(True, id="sync_rd=true"), pytest.param(False, id="sync_rd=false")],
)
@pytest.mark.parametrize(
"multiconn",
[
pytest.param(True, id="multiconn=true"),
pytest.param(False, id="multiconn=false"),
],
)
def test_sshfs(
tmpdir, debug: bool, cache_timeout: int, sync_rd: bool, multiconn: bool, capfd
) -> None:
# Avoid false positives from debug messages
# if debug:
# capfd.register_output(r'^ unique: [0-9]+, error: -[0-9]+ .+$',
# count=0)
# Avoid false positives from storing key for localhost
capfd.register_output(r"^Warning: Permanently added 'localhost' .+", count=0)
# Test if we can ssh into localhost without password
try:
res = subprocess.call(
[
"ssh",
"-o",
"StrictHostKeyChecking=no",
"-o",
"KbdInteractiveAuthentication=no",
"-o",
"ChallengeResponseAuthentication=no",
"-o",
"PasswordAuthentication=no",
"localhost",
"--",
"true",
],
stdin=subprocess.DEVNULL,
timeout=10,
)
except subprocess.TimeoutExpired:
res = 1
if res != 0:
pytest.fail("Unable to ssh into localhost without password prompt.")
mnt_dir = str(tmpdir.mkdir("mnt"))
src_dir = str(tmpdir.mkdir("src"))
cmdline = base_cmdline + [
pjoin(basename, "sshfs"),
"-f",
f"localhost:{src_dir}",
mnt_dir,
]
if debug:
cmdline += ["-o", "sshfs_debug"]
if sync_rd:
cmdline += ["-o", "sync_readdir"]
# SSHFS Cache
if cache_timeout == 0:
cmdline += ["-o", "dir_cache=no"]
else:
cmdline += ["-o", f"dcache_timeout={cache_timeout}", "-o", "dir_cache=yes"]
# FUSE Cache
cmdline += ["-o", "entry_timeout=0", "-o", "attr_timeout=0"]
if multiconn:
cmdline += ["-o", "max_conns=3"]
new_env = dict(os.environ) # copy, don't modify
# Abort on warnings from glib
new_env["G_DEBUG"] = "fatal-warnings"
mount_process = subprocess.Popen(cmdline, env=new_env)
try:
wait_for_mount(mount_process, mnt_dir)
tst_statvfs(src_dir, mnt_dir)
tst_readdir(src_dir, mnt_dir)
tst_open_read(src_dir, mnt_dir)
tst_open_write(src_dir, mnt_dir)
tst_append(src_dir, mnt_dir)
tst_seek(src_dir, mnt_dir)
tst_create(mnt_dir)
tst_passthrough(src_dir, mnt_dir, cache_timeout)
tst_mkdir(mnt_dir)
tst_rmdir(src_dir, mnt_dir, cache_timeout)
tst_rename(mnt_dir)
tst_rename_over(mnt_dir)
tst_chmod(mnt_dir)
tst_fsync(src_dir, mnt_dir)
tst_unlink(src_dir, mnt_dir, cache_timeout)
tst_symlink(mnt_dir)
if os.getuid() == 0:
tst_chown(mnt_dir)
# SSHFS only supports one second resolution when setting
# file timestamps.
tst_utimens(mnt_dir, tol=1)
tst_utimens_now(mnt_dir)
tst_link(mnt_dir, cache_timeout)
tst_truncate_path(mnt_dir)
tst_truncate_fd(mnt_dir)
tst_open_unlink(mnt_dir)
tst_open_writeonly_read(mnt_dir)
tst_access(mnt_dir)
tst_mkdir_exist(mnt_dir)
tst_readdir_repeated(mnt_dir)
tst_rename_sibling(mnt_dir)
tst_rename_open_release(mnt_dir)
except Exception as exc:
cleanup(mount_process, mnt_dir)
raise exc
else:
umount(mount_process, mnt_dir)
def tst_unlink(src_dir, mnt_dir, cache_timeout):
name = name_generator()
fullname = mnt_dir + "/" + name
with open(pjoin(src_dir, name), "wb") as fh:
fh.write(b"hello")
if cache_timeout:
safe_sleep(cache_timeout + 1)
assert name in os.listdir(mnt_dir)
os.unlink(fullname)
with pytest.raises(OSError) as exc_info:
os.stat(fullname)
assert exc_info.value.errno == errno.ENOENT
assert name not in os.listdir(mnt_dir)
assert name not in os.listdir(src_dir)
def tst_mkdir(mnt_dir):
dirname = name_generator()
fullname = mnt_dir + "/" + dirname
os.mkdir(fullname)
fstat = os.stat(fullname)
assert stat.S_ISDIR(fstat.st_mode)
assert os.listdir(fullname) == []
assert fstat.st_nlink in (1, 2)
assert dirname in os.listdir(mnt_dir)
def tst_rmdir(src_dir, mnt_dir, cache_timeout):
name = name_generator()
fullname = mnt_dir + "/" + name
os.mkdir(pjoin(src_dir, name))
if cache_timeout:
safe_sleep(cache_timeout + 1)
assert name in os.listdir(mnt_dir)
os.rmdir(fullname)
with pytest.raises(OSError) as exc_info:
os.stat(fullname)
assert exc_info.value.errno == errno.ENOENT
assert name not in os.listdir(mnt_dir)
assert name not in os.listdir(src_dir)
def tst_rename(mnt_dir):
src_name = pjoin(mnt_dir, name_generator())
dst_name = pjoin(mnt_dir, name_generator())
data = b"rename test data\n"
with open(src_name, "wb") as fh:
fh.write(data)
assert os.path.exists(src_name)
os.rename(src_name, dst_name)
assert not os.path.exists(src_name)
assert os.path.basename(src_name) not in os.listdir(mnt_dir)
assert os.path.basename(dst_name) in os.listdir(mnt_dir)
with open(dst_name, "rb") as fh:
assert fh.read() == data
os.unlink(dst_name)
def tst_rename_over(mnt_dir):
src_name = pjoin(mnt_dir, name_generator())
dst_name = pjoin(mnt_dir, name_generator())
src_data = b"source content\n"
dst_data = b"destination content\n"
with open(src_name, "wb") as fh:
fh.write(src_data)
with open(dst_name, "wb") as fh:
fh.write(dst_data)
os.rename(src_name, dst_name)
assert not os.path.exists(src_name)
assert os.path.basename(src_name) not in os.listdir(mnt_dir)
with open(dst_name, "rb") as fh:
assert fh.read() == src_data
os.unlink(dst_name)
def tst_chmod(mnt_dir):
filename = pjoin(mnt_dir, name_generator())
with open(filename, "wb") as fh:
fh.write(b"chmod test\n")
os.chmod(filename, 0o644)
fstat = os.stat(filename)
assert stat.S_IMODE(fstat.st_mode) == 0o644
os.chmod(filename, 0o755)
fstat = os.stat(filename)
assert stat.S_IMODE(fstat.st_mode) == 0o755
os.unlink(filename)
def tst_fsync(src_dir, mnt_dir):
name = name_generator()
mnt_name = pjoin(mnt_dir, name)
src_name = pjoin(src_dir, name)
data = b"fsync test data\n"
fd = os.open(mnt_name, os.O_CREAT | os.O_WRONLY)
try:
os.write(fd, data)
os.fsync(fd)
# Read from backing store while fd is still open, before
# close/release has a chance to flush
with open(src_name, "rb") as fh:
assert fh.read() == data
finally:
os.close(fd)
os.unlink(mnt_name)
def tst_symlink(mnt_dir):
linkname = name_generator()
fullname = mnt_dir + "/" + linkname
os.symlink("/imaginary/dest", fullname)
fstat = os.lstat(fullname)
assert stat.S_ISLNK(fstat.st_mode)
assert os.readlink(fullname) == "/imaginary/dest"
assert fstat.st_nlink == 1
assert linkname in os.listdir(mnt_dir)
os.unlink(fullname)
assert linkname not in os.listdir(mnt_dir)
def tst_create(mnt_dir):
name = name_generator()
fullname = pjoin(mnt_dir, name)
with pytest.raises(OSError) as exc_info:
os.stat(fullname)
assert exc_info.value.errno == errno.ENOENT
assert name not in os.listdir(mnt_dir)
fd = os.open(fullname, os.O_CREAT | os.O_RDWR)
os.close(fd)
assert name in os.listdir(mnt_dir)
fstat = os.lstat(fullname)
assert stat.S_ISREG(fstat.st_mode)
assert fstat.st_nlink == 1
assert fstat.st_size == 0
def tst_chown(mnt_dir):
filename = pjoin(mnt_dir, name_generator())
os.mkdir(filename)
fstat = os.lstat(filename)
uid = fstat.st_uid
gid = fstat.st_gid
uid_new = uid + 1
os.chown(filename, uid_new, -1)
fstat = os.lstat(filename)
assert fstat.st_uid == uid_new
assert fstat.st_gid == gid
gid_new = gid + 1
os.chown(filename, -1, gid_new)
fstat = os.lstat(filename)
assert fstat.st_uid == uid_new
assert fstat.st_gid == gid_new
def tst_open_read(src_dir, mnt_dir):
name = name_generator()
with open(pjoin(src_dir, name), "wb") as fh_out, open(TEST_FILE, "rb") as fh_in:
shutil.copyfileobj(fh_in, fh_out)
assert filecmp.cmp(pjoin(mnt_dir, name), TEST_FILE, False)
def tst_open_write(src_dir, mnt_dir):
name = name_generator()
fd = os.open(pjoin(src_dir, name), os.O_CREAT | os.O_RDWR)
os.close(fd)
fullname = pjoin(mnt_dir, name)
with open(fullname, "wb") as fh_out, open(TEST_FILE, "rb") as fh_in:
shutil.copyfileobj(fh_in, fh_out)
assert filecmp.cmp(fullname, TEST_FILE, False)
def tst_append(src_dir, mnt_dir):
name = name_generator()
os_create(pjoin(src_dir, name))
fullname = pjoin(mnt_dir, name)
with os_open(fullname, os.O_WRONLY) as fd:
os.write(fd, b"foo\n")
with os_open(fullname, os.O_WRONLY | os.O_APPEND) as fd:
os.write(fd, b"bar\n")
with open(fullname, "rb") as fh:
assert fh.read() == b"foo\nbar\n"
def tst_seek(src_dir, mnt_dir):
name = name_generator()
os_create(pjoin(src_dir, name))
fullname = pjoin(mnt_dir, name)
with os_open(fullname, os.O_WRONLY) as fd:
os.lseek(fd, 1, os.SEEK_SET)
os.write(fd, b"foobar\n")
with os_open(fullname, os.O_WRONLY) as fd:
os.lseek(fd, 4, os.SEEK_SET)
os.write(fd, b"com")
with open(fullname, "rb") as fh:
assert fh.read() == b"\0foocom\n"
def tst_open_unlink(mnt_dir):
name = pjoin(mnt_dir, name_generator())
data1 = b"foo"
data2 = b"bar"
fullname = pjoin(mnt_dir, name)
with open(fullname, "wb+", buffering=0) as fh:
fh.write(data1)
os.unlink(fullname)
with pytest.raises(OSError) as exc_info:
os.stat(fullname)
assert exc_info.value.errno == errno.ENOENT
assert name not in os.listdir(mnt_dir)
fh.write(data2)
fh.seek(0)
assert fh.read() == data1 + data2
def tst_statvfs(src_dir, mnt_dir):
vfs = os.statvfs(mnt_dir)
ref = os.statvfs(src_dir)
# When the server supports statvfs@openssh.com, values should
# match the backing store. Otherwise sshfs returns synthetic
# values that still pass the loose checks.
if vfs.f_bsize == ref.f_bsize:
assert vfs.f_frsize == ref.f_frsize
assert vfs.f_blocks == ref.f_blocks
assert vfs.f_namemax == ref.f_namemax
else:
assert vfs.f_bsize > 0
assert vfs.f_blocks > 0
assert vfs.f_namemax > 0
def tst_open_writeonly_read(mnt_dir):
name = pjoin(mnt_dir, name_generator())
fd = os.open(name, os.O_CREAT | os.O_WRONLY)
try:
os.write(fd, b"hello")
with pytest.raises(OSError) as exc_info:
os.read(fd, 10)
assert exc_info.value.errno == errno.EBADF
finally:
os.close(fd)
os.unlink(name)
def tst_access(mnt_dir):
filename = pjoin(mnt_dir, name_generator())
with open(filename, "wb") as fh:
fh.write(b"test")
os.chmod(filename, 0o644)
assert os.access(filename, os.R_OK)
if os.getuid() != 0:
assert not os.access(filename, os.X_OK)
os.unlink(filename)
def tst_mkdir_exist(mnt_dir):
name = name_generator()
fullname = pjoin(mnt_dir, name)
os.mkdir(fullname)
with pytest.raises(OSError) as exc_info:
os.mkdir(fullname)
assert exc_info.value.errno == errno.EEXIST
os.rmdir(fullname)
def tst_readdir_repeated(mnt_dir):
dirname = pjoin(mnt_dir, name_generator())
os.mkdir(dirname)
names = []
for i in range(5):
n = name_generator()
names.append(n)
with open(pjoin(dirname, n), "wb") as fh:
fh.write(b"x")
# Verify repeated directory listings return consistent results
listing1 = sorted(os.listdir(dirname))
listing2 = sorted(os.listdir(dirname))
assert listing1 == sorted(names)
assert listing1 == listing2
for n in names:
os.unlink(pjoin(dirname, n))
os.rmdir(dirname)
def tst_rename_sibling(mnt_dir):
# Verify renaming one file doesn't break access to a sibling
name_a = pjoin(mnt_dir, name_generator())
name_b = pjoin(mnt_dir, name_generator())
name_c = pjoin(mnt_dir, name_generator())
with open(name_a, "wb") as fh:
fh.write(b"aaa")
with open(name_b, "wb") as fh:
fh.write(b"bbb")
os.rename(name_a, name_c)
assert not os.path.exists(name_a)
assert os.path.exists(name_b)
with open(name_b, "rb") as fh:
assert fh.read() == b"bbb"
os.unlink(name_b)
os.unlink(name_c)
def tst_rename_open_release(mnt_dir):
src = pjoin(mnt_dir, name_generator())
dst = pjoin(mnt_dir, name_generator())
fd = os.open(src, os.O_CREAT | os.O_RDWR)
try:
os.write(fd, b"data")
os.rename(src, dst)
finally:
os.close(fd)
assert not os.path.exists(src)
with open(dst, "rb") as fh:
assert fh.read() == b"data"
os.unlink(dst)
def tst_link(mnt_dir, cache_timeout):
name1 = pjoin(mnt_dir, name_generator())
name2 = pjoin(mnt_dir, name_generator())
shutil.copyfile(TEST_FILE, name1)
assert filecmp.cmp(name1, TEST_FILE, False)
fstat1 = os.lstat(name1)
assert fstat1.st_nlink == 1
os.link(name1, name2)
# The link operation changes st_ctime, and if we're unlucky
# the kernel will keep the old value cached for name1, and
# retrieve the new value for name2 (at least, this is the only
# way I can explain the test failure). To avoid this problem,
# we need to wait until the cached value has expired.
if cache_timeout:
safe_sleep(cache_timeout)
fstat1 = os.lstat(name1)
fstat2 = os.lstat(name2)
for attr in (
"st_mode",
"st_dev",
"st_uid",
"st_gid",
"st_size",
"st_atime",
"st_mtime",
"st_ctime",
):
assert getattr(fstat1, attr) == getattr(fstat2, attr)
assert os.path.basename(name2) in os.listdir(mnt_dir)
assert filecmp.cmp(name1, name2, False)
os.unlink(name2)
assert os.path.basename(name2) not in os.listdir(mnt_dir)
with pytest.raises(FileNotFoundError):
os.lstat(name2)
if cache_timeout:
safe_sleep(cache_timeout + 1)
fstat1 = os.lstat(name1)
assert fstat1.st_nlink == 1
os.unlink(name1)
def tst_readdir(src_dir, mnt_dir):
newdir = name_generator()
src_newdir = pjoin(src_dir, newdir)
mnt_newdir = pjoin(mnt_dir, newdir)
file_ = src_newdir + "/" + name_generator()
subdir = src_newdir + "/" + name_generator()
subfile = subdir + "/" + name_generator()
os.mkdir(src_newdir)
shutil.copyfile(TEST_FILE, file_)
os.mkdir(subdir)
shutil.copyfile(TEST_FILE, subfile)
listdir_is = os.listdir(mnt_newdir)
listdir_is.sort()
listdir_should = [os.path.basename(file_), os.path.basename(subdir)]
listdir_should.sort()
assert listdir_is == listdir_should
os.unlink(file_)
os.unlink(subfile)
os.rmdir(subdir)
os.rmdir(src_newdir)
def tst_truncate_path(mnt_dir):
assert len(TEST_DATA) > 1024
filename = pjoin(mnt_dir, name_generator())
with open(filename, "wb") as fh:
fh.write(TEST_DATA)
fstat = os.stat(filename)
size = fstat.st_size
assert size == len(TEST_DATA)
# Add zeros at the end
os.truncate(filename, size + 1024)
assert os.stat(filename).st_size == size + 1024
with open(filename, "rb") as fh:
assert fh.read(size) == TEST_DATA
assert fh.read(1025) == b"\0" * 1024
# Truncate data
os.truncate(filename, size - 1024)
assert os.stat(filename).st_size == size - 1024
with open(filename, "rb") as fh:
assert fh.read(size) == TEST_DATA[: size - 1024]
# Truncate to zero
os.truncate(filename, 0)
assert os.stat(filename).st_size == 0
os.unlink(filename)
def tst_truncate_fd(mnt_dir):
assert len(TEST_DATA) > 1024
with NamedTemporaryFile("w+b", 0, dir=mnt_dir) as fh:
fd = fh.fileno()
fh.write(TEST_DATA)
fstat = os.fstat(fd)
size = fstat.st_size
assert size == len(TEST_DATA)
# Add zeros at the end
os.ftruncate(fd, size + 1024)
assert os.fstat(fd).st_size == size + 1024
fh.seek(0)
assert fh.read(size) == TEST_DATA
assert fh.read(1025) == b"\0" * 1024
# Truncate data
os.ftruncate(fd, size - 1024)
assert os.fstat(fd).st_size == size - 1024
fh.seek(0)
assert fh.read(size) == TEST_DATA[: size - 1024]
# Truncate to zero via fd
os.ftruncate(fd, 0)
assert os.fstat(fd).st_size == 0
def tst_utimens(mnt_dir, tol=0):
filename = pjoin(mnt_dir, name_generator())
os.mkdir(filename)
fstat = os.lstat(filename)
atime = fstat.st_atime + 42.28
mtime = fstat.st_mtime - 42.23
if sys.version_info < (3, 3):
os.utime(filename, (atime, mtime))
else:
atime_ns = fstat.st_atime_ns + int(42.28 * 1e9)
mtime_ns = fstat.st_mtime_ns - int(42.23 * 1e9)
os.utime(filename, None, ns=(atime_ns, mtime_ns))
fstat = os.lstat(filename)
assert abs(fstat.st_atime - atime) < tol
assert abs(fstat.st_mtime - mtime) < tol
if sys.version_info >= (3, 3):
assert abs(fstat.st_atime_ns - atime_ns) < tol * 1e9
assert abs(fstat.st_mtime_ns - mtime_ns) < tol * 1e9
def tst_utimens_now(mnt_dir):
fullname = pjoin(mnt_dir, name_generator())
fd = os.open(fullname, os.O_CREAT | os.O_RDWR)
os.close(fd)
os.utime(fullname, None)
fstat = os.lstat(fullname)
# We should get now-timestamps
assert fstat.st_atime != 0
assert fstat.st_mtime != 0
def tst_passthrough(src_dir, mnt_dir, cache_timeout):
name = name_generator()
src_name = pjoin(src_dir, name)
mnt_name = pjoin(mnt_dir, name)
assert name not in os.listdir(src_dir)
assert name not in os.listdir(mnt_dir)
with open(src_name, "w") as fh:
fh.write("Hello, world")
assert name in os.listdir(src_dir)
if cache_timeout:
safe_sleep(cache_timeout + 1)
assert name in os.listdir(mnt_dir)
src_st = os.stat(src_name)
mnt_st = os.stat(mnt_name)
assert src_st.st_size == mnt_st.st_size
assert src_st.st_uid == mnt_st.st_uid
assert src_st.st_gid == mnt_st.st_gid
assert abs(src_st.st_mtime - mnt_st.st_mtime) <= 1
name = name_generator()
src_name = pjoin(src_dir, name)
mnt_name = pjoin(mnt_dir, name)
assert name not in os.listdir(src_dir)
assert name not in os.listdir(mnt_dir)
with open(mnt_name, "w") as fh:
fh.write("Hello, world")
assert name in os.listdir(src_dir)
if cache_timeout:
safe_sleep(cache_timeout + 1)
assert name in os.listdir(mnt_dir)
src_st = os.stat(src_name)
mnt_st = os.stat(mnt_name)
assert src_st.st_size == mnt_st.st_size
assert src_st.st_uid == mnt_st.st_uid
assert src_st.st_gid == mnt_st.st_gid
assert abs(src_st.st_mtime - mnt_st.st_mtime) <= 1
def _check_ssh_localhost():
try:
res = subprocess.call(
["ssh", "-o", "StrictHostKeyChecking=no",
"-o", "KbdInteractiveAuthentication=no",
"-o", "ChallengeResponseAuthentication=no",
"-o", "PasswordAuthentication=no",
"localhost", "--", "true"],
stdin=subprocess.DEVNULL, timeout=10,
)
except subprocess.TimeoutExpired:
res = 1
if res != 0:
pytest.fail("Unable to ssh into localhost without password prompt.")
_mount_ctr = [0]
def _mount_sshfs(tmpdir, extra_opts=None):
"""Helper to mount sshfs with custom options. Returns (mount_process, mnt_dir, src_dir)."""
_check_ssh_localhost()
_mount_ctr[0] += 1
mnt_dir = str(tmpdir.mkdir(f"mnt{_mount_ctr[0]}"))
src_dir = str(tmpdir.mkdir(f"src{_mount_ctr[0]}"))
cmdline = base_cmdline + [
pjoin(basename, "sshfs"),
"-f",
f"localhost:{src_dir}",
mnt_dir,
"-o", "entry_timeout=0",
"-o", "attr_timeout=0",
]
if extra_opts:
for opt in extra_opts:
cmdline += ["-o", opt]
new_env = dict(os.environ)
new_env["G_DEBUG"] = "fatal-warnings"
mount_process = subprocess.Popen(cmdline, env=new_env)
try:
wait_for_mount(mount_process, mnt_dir)
except:
cleanup(mount_process, mnt_dir)
raise
return mount_process, mnt_dir, src_dir
def test_disable_hardlink(tmpdir, capfd):
capfd.register_output(r"^Warning: Permanently added 'localhost' .+", count=0)
# Control: verify hardlinks work without disable_hardlink.
# If the server lacks the extension, skip this test entirely.
mount_process, mnt_dir, src_dir = _mount_sshfs(tmpdir, [])
try:
name1 = pjoin(mnt_dir, name_generator())
name2 = pjoin(mnt_dir, name_generator())
with open(name1, "wb") as fh:
fh.write(b"test")
try:
os.link(name1, name2)
except OSError:
os.unlink(name1)
pytest.skip("server does not support hardlink extension")
os.unlink(name2)
os.unlink(name1)
except Exception:
cleanup(mount_process, mnt_dir)
raise
else:
umount(mount_process, mnt_dir)
# Now test with disable_hardlink — links should fail
mount_process, mnt_dir, src_dir = _mount_sshfs(tmpdir, ["disable_hardlink"])
try:
name1 = pjoin(mnt_dir, name_generator())
name2 = pjoin(mnt_dir, name_generator())
with open(name1, "wb") as fh:
fh.write(b"test")
with pytest.raises(OSError) as exc_info:
os.link(name1, name2)
assert exc_info.value.errno in (errno.ENOSYS, errno.EPERM)
os.unlink(name1)
except Exception:
cleanup(mount_process, mnt_dir)
raise
else:
umount(mount_process, mnt_dir)
def test_follow_symlinks(tmpdir, capfd):
capfd.register_output(r"^Warning: Permanently added 'localhost' .+", count=0)
mount_process, mnt_dir, src_dir = _mount_sshfs(tmpdir, ["follow_symlinks"])
try:
target_name = name_generator()
target = pjoin(src_dir, target_name)
with open(target, "wb") as fh:
fh.write(b"symlink target data")
link = pjoin(src_dir, name_generator())
os.symlink(target_name, link)
mnt_link = pjoin(mnt_dir, os.path.basename(link))
# With follow_symlinks, stat should return the target's attributes
# and the entry should appear as a regular file, not a symlink
fstat = os.lstat(mnt_link)
assert stat.S_ISREG(fstat.st_mode)
with open(mnt_link, "rb") as fh:
assert fh.read() == b"symlink target data"
os.unlink(link)
os.unlink(target)
except Exception:
cleanup(mount_process, mnt_dir)
raise
else:
umount(mount_process, mnt_dir)
def test_direct_io(tmpdir, capfd):
capfd.register_output(r"^Warning: Permanently added 'localhost' .+", count=0)
mount_process, mnt_dir, src_dir = _mount_sshfs(tmpdir, ["direct_io"])
try:
name = name_generator()
mnt_name = pjoin(mnt_dir, name)
src_name = pjoin(src_dir, name)
data = b"direct io test data\n" * 100
with open(mnt_name, "wb") as fh:
fh.write(data)
with open(mnt_name, "rb") as fh:
assert fh.read() == data
with open(src_name, "rb") as fh:
assert fh.read() == data
os.unlink(mnt_name)
except Exception:
cleanup(mount_process, mnt_dir)
raise
else:
umount(mount_process, mnt_dir)
def test_bad_sftp_reply_len(tmpdir):
"""sshfs must reject a zero-length SFTP reply instead of underflowing."""
helper = tmpdir.join("bad_sftp.py")
helper.write(
'#!/usr/bin/env python3\n'
'import os, struct, sys\n'
'def read_pkt():\n'
' hdr = os.read(0, 4)\n'
' if len(hdr) < 4: sys.exit(0)\n'
' n = struct.unpack(">I", hdr)[0]\n'
' while n:\n'
' c = os.read(0, n)\n'
' if not c: sys.exit(0)\n'
' n -= len(c)\n'
'read_pkt()\n'
'os.write(1, struct.pack(">IBI", 5, 2, 3))\n' # SSH_FXP_VERSION v3
'read_pkt()\n'
'os.write(1, struct.pack(">IB", 0, 0))\n' # len=0 reply (5 bytes on wire)
)
helper.chmod(0o755)
mnt_dir = str(tmpdir.mkdir("mnt"))
cmdline = base_cmdline + [
pjoin(basename, "sshfs"),
"-f",
"dummy:/",
mnt_dir,
"-o", f"ssh_command={helper}",
]
res = subprocess.run(
cmdline,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=10,
text=True,
)
assert res.returncode != 0
assert "bad reply len: 0" in res.stderr
+45
View File
@@ -0,0 +1,45 @@
#!/bin/bash
set -e
# Disable leak checking for now, there are some issues (or false positives)
# that we still need to fix
export ASAN_OPTIONS="detect_leaks=0"
export LSAN_OPTIONS="suppressions=${PWD}/test/lsan_suppress.txt"
export CC
TEST_CMD="python3 -m pytest --maxfail=99 test/"
# Standard build with Valgrind
for CC in gcc clang; do
(
mkdir "build-${CC}"; cd "build-${CC}"
if [ "${CC}" == 'gcc-6' ]; then
build_opts='-D b_lundef=false'
else
build_opts=''
fi
# shellcheck disable=SC2086
meson -D werror=true ${build_opts} ../
ninja
TEST_WITH_VALGRIND=true ${TEST_CMD}
)
done
(cd "build-${CC}"; sudo ninja install)
# Sanitized build
CC=clang
for san in undefined address; do
(
mkdir "build-${san}"
cd "build-${san}"
# b_lundef=false is required to work around clang
# bug, cf. https://groups.google.com/forum/#!topic/mesonbuild/tgEdAXIIdC4
meson -D b_sanitize=${san} -D b_lundef=false -D werror=true ..
ninja
${TEST_CMD}
sudo ninja install
)
done
+23
View File
@@ -0,0 +1,23 @@
#!/bin/sh
set -e
# Install fuse
wget https://github.com/libfuse/libfuse/archive/master.zip
unzip master.zip
cd libfuse-master
mkdir build
cd build
meson ..
ninja
sudo ninja install
test -e /usr/local/lib/pkgconfig || sudo mkdir /usr/local/lib/pkgconfig
sudo mv /usr/local/lib/*/pkgconfig/* /usr/local/lib/pkgconfig/
printf '%s\n' /usr/local/lib/*-linux-gnu | sudo tee /etc/ld.so.conf.d/usrlocal.conf
sudo ldconfig
# Setup ssh
ssh-keygen -b 1024 -t rsa -f ~/.ssh/id_rsa -P ''
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
ssh -o "StrictHostKeyChecking=no" localhost echo "SSH connection succeeded"
+131
View File
@@ -0,0 +1,131 @@
#!/usr/bin/env python3
import subprocess
import pytest
import os
import stat
import time
from os.path import join as pjoin
from contextlib import contextmanager
basename = pjoin(os.path.dirname(__file__), "..")
def os_create(name):
os.close(os.open(name, os.O_CREAT | os.O_RDWR))
@contextmanager
def os_open(name, flags):
fd = os.open(name, flags)
try:
yield fd
finally:
os.close(fd)
def wait_for_mount(mount_process, mnt_dir, test_fn=os.path.ismount):
elapsed = 0
while elapsed < 30:
if test_fn(mnt_dir):
return True
if mount_process.poll() is not None:
pytest.fail("file system process terminated prematurely")
time.sleep(0.1)
elapsed += 0.1
pytest.fail("mountpoint failed to come up")
def cleanup(mount_process, mnt_dir):
subprocess.call(
["fusermount", "-z", "-u", mnt_dir],
stdout=subprocess.DEVNULL,
stderr=subprocess.STDOUT,
)
mount_process.terminate()
try:
mount_process.wait(1)
except subprocess.TimeoutExpired:
mount_process.kill()
def umount(mount_process, mnt_dir):
subprocess.check_call(["fusermount3", "-z", "-u", mnt_dir])
assert not os.path.ismount(mnt_dir)
# Give mount process a little while to terminate. Popen.wait(timeout)
# was only added in 3.3...
elapsed = 0
while elapsed < 30:
code = mount_process.poll()
if code is not None:
if code == 0:
return
pytest.fail(f"file system process terminated with code {code}")
time.sleep(0.1)
elapsed += 0.1
pytest.fail("mount process did not terminate")
def safe_sleep(secs):
"""Like time.sleep(), but sleep for at least *secs*
`time.sleep` may sleep less than the given period if a signal is
received. This function ensures that we sleep for at least the
desired time.
"""
now = time.time()
end = now + secs
while now < end:
time.sleep(end - now)
now = time.time()
def fuse_test_marker():
"""Return a pytest.marker that indicates FUSE availability
If system/user/environment does not support FUSE, return
a `pytest.mark.skip` object with more details. If FUSE is
supported, return `pytest.mark.uses_fuse()`.
"""
def skip(reason: str):
return pytest.mark.skip(reason=reason)
with subprocess.Popen(
["which", "fusermount"], stdout=subprocess.PIPE, universal_newlines=True
) as which:
fusermount_path = which.communicate()[0].strip()
if not fusermount_path or which.returncode != 0:
return skip("Can't find fusermount executable")
if not os.path.exists("/dev/fuse"):
return skip("FUSE kernel module does not seem to be loaded")
if os.getuid() == 0:
return pytest.mark.uses_fuse()
mode = os.stat(fusermount_path).st_mode
if mode & stat.S_ISUID == 0:
return skip("fusermount executable not setuid, and we are not root.")
try:
fd = os.open("/dev/fuse", os.O_RDWR)
except OSError as exc:
return skip(f"Unable to open /dev/fuse: {exc.strerror}")
else:
os.close(fd)
return pytest.mark.uses_fuse()
# Use valgrind if requested
if os.environ.get("TEST_WITH_VALGRIND", "no").lower().strip() not in (
"no",
"false",
"0",
):
base_cmdline = ["valgrind", "-q", "--"]
else:
base_cmdline = []
+9
View File
@@ -0,0 +1,9 @@
#include <stdio.h>
int main(void) {
fprintf(stderr, "\x1B[31m\e[1m"
"This is not the command you are looking for.\n"
"You probably want to run 'python3 -m pytest test/' instead"
"\e[0m\n");
return 1;
}
+19
View File
@@ -0,0 +1,19 @@
#!/bin/sh
#
# Don't call this script. It is used internally by the Meson
# build system. Thank you for your cooperation.
#
set -e
bindir="$2"
sbindir="$1"
prefix="${MESON_INSTALL_DESTDIR_PREFIX}"
mkdir -p "${prefix}/${sbindir}"
ln -svf --relative "${prefix}/${bindir}/sshfs" \
"${prefix}/${sbindir}/mount.sshfs"
ln -svf --relative "${prefix}/${bindir}/sshfs" \
"${prefix}/${sbindir}/mount.fuse.sshfs"