Compare commits

..

65 Commits

Author SHA1 Message Date
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
20 changed files with 1013 additions and 1310 deletions
+3 -4
View File
@@ -12,11 +12,10 @@ addons:
- clang
- gcc
- gcc-6
- fuse
- libfuse2
- libfuse-dev
- python-docutils
- python3-pip
before_install:
- pip install pip==8.1.1 && pip3 install pip==8.1.1
- sudo -H python3 -m pip install pip==8.1.1
install: test/travis-install.sh
script: test/travis-build.sh
+4
View File
@@ -15,12 +15,14 @@ Contributors (autogenerated list)
Alan Jenkins <alan.christopher.jenkins@gmail.com>
Alexander Neumann <alexander@bumpern.de>
Anatol Pomozov <anatol.pomozov@gmail.com>
Benjamin Fleischer <fleiben@gmail.com>
Berserker <berserker.troll@yandex.com>
Bill Zissimopoulos <billziss@navimatics.com>
Chris Wolfe <cwolfe@chromium.org>
gala <gala132@users.noreply.github.com>
George Vlahavas <vlahavas@gmail.com>
harrim4n <git@harrim4n.com>
Jakub Jelen <jjelen@redhat.com>
Julio Merino <jmmv@google.com>
Julio Merino <jmmv@meroh.net>
@@ -30,6 +32,8 @@ Miklos Szeredi <mszeredi@suse.cz>
Nikolaus Rath <Nikolaus@rath.org>
Percy Jahn <email@percyjahn.de>
Qais Patankar <qaisjp@gmail.com>
Reid Wagner <wagnerreid@gmail.com>
Rian Hunter <rian@alum.mit.edu>
Timo Savola <timo.savola@iki.fi>
tpoindessous <thomas@poindessous.com>
Zoltan Kuscsik <zoltan.kuscsik@linaro.org>
+43 -2
View File
@@ -1,9 +1,50 @@
Release 2.10 (2017-08-03)
-------------------------
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.
-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).
-30
View File
@@ -1,30 +0,0 @@
## Process this file with automake to produce Makefile.in
bin_PROGRAMS = sshfs
SUBDIRS = test
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 = sshfs.1.in meson.build
CLEANFILES = 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
+4 -44
View File
@@ -42,9 +42,10 @@ Installation
First, download the latest SSHFS release from
https://github.com/libfuse/sshfs/releases. On Linux and BSD, you will
also need to have libfuse_ installed. On OS-X, you need OSXFUSE_
instead. Finally, you need the Glib_ development package (which should
be available from your operating system's package manager).
also need to install libfuse_ 3.1.0 or newer. On OS-X, you need
OSXFUSE_ instead. Finally, you need the Glib_ library with development
headers (which should be available from your operating system's
package manager).
To build and install, we recommend to use Meson_ (version 0.38 or
newer) and Ninja_. After extracting the sshfs tarball, create a
@@ -74,47 +75,6 @@ tests requires the `py.test`_ Python module)::
.. _Ninja: https://ninja-build.org/
.. _`py.test`: http://www.pytest.org/
Alternate Installation
----------------------
If you are not able to use Meson and Ninja, please report this to the
sshfs mailing list. Until the problem is resolved, you may fall back
to an in-source build using autotools::
$ ./configure
$ make
$ sudo make install
Note that support for building with autotools may disappear at some
point, so if you depend on using autotools for some reason please let
the sshfs developers know!
Caveats
-------
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.
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.
Getting Help
------------
+173 -167
View File
@@ -8,6 +8,7 @@
#include "cache.h"
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
@@ -27,7 +28,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;
@@ -46,14 +47,22 @@ struct node {
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;
};
static void free_node(gpointer node_)
{
struct node *node = (struct node *) node_;
@@ -108,15 +117,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 +174,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);
@@ -248,12 +251,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,17 +293,53 @@ 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));
if (stbuf->st_mode & S_IFMT) {
@@ -302,61 +354,66 @@ 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;
struct node *node;
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);
// FIXME: What about st_mode?
filler(buf, *dir, NULL, 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();
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)
if (!err) {
cache_add_dir(path, dir);
else
} else {
g_strfreev(dir);
}
g_ptr_array_free(ch.dir, FALSE);
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 +421,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 +429,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 +437,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 +445,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 +461,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 +469,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 +499,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 +596,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 +610,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);
-44
View File
@@ -1,44 +0,0 @@
AC_INIT(sshfs, 2.10)
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])
LIBS=
case "$target_os" in
*linux*) osname=linux;;
*darwin*) osname=darwin;;
*) osname=unknown;;
esac
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 test/Makefile])
AC_OUTPUT
+30
View File
@@ -0,0 +1,30 @@
#!/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
+19 -11
View File
@@ -1,8 +1,8 @@
project('sshfs', 'c', version: '2.10.0',
project('sshfs', 'c', version: '3.3.1',
meson_version: '>= 0.38',
default_options: [ 'buildtype=plain' ])
default_options: [ 'buildtype=debugoptimized' ])
add_global_arguments('-D_REENTRANT', '-DHAVE_CONFIG_H', '-O2', '-g',
add_global_arguments('-D_REENTRANT', '-DHAVE_CONFIG_H',
'-Wall', '-Wextra', '-Wno-sign-compare',
'-Wmissing-declarations', '-Wwrite-strings',
language: 'c')
@@ -25,6 +25,8 @@ if not cc.compiles(code, args: [ '-O0', '-Werror=unused-result' ])
endif
rst2man = find_program('rst2man', required: false)
cfg = configuration_data()
cfg.set_quoted('PACKAGE_VERSION', meson.project_version())
@@ -39,26 +41,32 @@ else
cfg.set_quoted('IDMAP_DEFAULT', 'none')
endif
configure_file(input: 'sshfs.1.in',
output: 'sshfs.1',
configuration : cfg)
configure_file(output: 'config.h',
configuration : cfg)
sshfs_deps = [ dependency('fuse', version: '>= 2.3'),
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=26'],
c_args: ['-DFUSE_USE_VERSION=31'],
install: true,
install_dir: get_option('bindir'))
# This is a little ugly. Is there a better way to tell Meson that the
# manpage is in the build directory?
install_man(join_paths(meson.current_build_dir(), 'sshfs.1'))
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).
+368 -426
View File
File diff suppressed because it is too large Load Diff
+273
View File
@@ -0,0 +1,273 @@
=======
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.
SSHFS uses FUSE (Filesystem in Userspace) and should work on any
operating system that provides a FUSE implementation. Currently,
this includes Linux, FreeBSD and Mac OS X.
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.
: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.
-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 slave
communicate over stdin and stdout bypassing network
-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.
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.
SSHFS hangs
~~~~~~~~~~~
In some cases, attempts to access the SSHFS mountpoint may freeze if
no filesystem activity has occured 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.
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).
-4
View File
@@ -1,4 +0,0 @@
## Process this file with automake to produce Makefile.in
EXTRA_DIST = meson.build conftest.py pytest.ini test_sshfs.py \
util.py wrong_command.c
+1 -1
View File
@@ -2,7 +2,7 @@ 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', '-fPu', '--preserve=mode',
command: ['cp', '-fPp',
'@INPUT@', meson.current_build_dir() ])
# Provide something helpful when running 'ninja test'
+59 -6
View File
@@ -13,6 +13,7 @@ import stat
import shutil
import filecmp
import errno
from contextlib import contextmanager
from tempfile import NamedTemporaryFile
from util import (wait_for_mount, umount, cleanup, base_cmdline,
basename, fuse_test_marker, safe_sleep)
@@ -30,8 +31,9 @@ def name_generator(__ctr=[0]):
return 'testfile_%d' % __ctr[0]
@pytest.mark.parametrize("debug", (False, True))
@pytest.mark.parametrize("cache_timeout", (0, 1))
def test_sshfs(tmpdir, debug, cache_timeout, capfd):
@pytest.mark.parametrize("cache_timeout", (0,1))
@pytest.mark.parametrize("sync_rd", (True, False))
def test_sshfs(tmpdir, debug, cache_timeout, sync_rd, capfd):
# Avoid false positives from debug messages
#if debug:
@@ -58,11 +60,15 @@ def test_sshfs(tmpdir, debug, cache_timeout, capfd):
if debug:
cmdline += [ '-o', 'sshfs_debug' ]
if sync_rd:
cmdline += [ '-o', 'sync_readdir' ]
# SSHFS Cache
if cache_timeout == 0:
cmdline += [ '-o', 'cache=no' ]
cmdline += [ '-o', 'dir_cache=no' ]
else:
cmdline += [ '-o', 'cache_timeout=%d' % cache_timeout ]
cmdline += [ '-o', 'dcache_timeout=%d' % cache_timeout,
'-o', 'dir_cache=yes' ]
# FUSE Cache
cmdline += [ '-o', 'entry_timeout=0',
@@ -82,6 +88,8 @@ def test_sshfs(tmpdir, debug, cache_timeout, capfd):
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)
@@ -95,7 +103,7 @@ def test_sshfs(tmpdir, debug, cache_timeout, capfd):
# file timestamps.
tst_utimens(mnt_dir, tol=1)
tst_link(mnt_dir)
tst_link(mnt_dir, cache_timeout)
tst_truncate_path(mnt_dir)
tst_truncate_fd(mnt_dir)
tst_open_unlink(mnt_dir)
@@ -105,6 +113,17 @@ def test_sshfs(tmpdir, debug, cache_timeout, capfd):
else:
umount(mount_process, mnt_dir)
@contextmanager
def os_open(name, flags):
fd = os.open(name, flags)
try:
yield fd
finally:
os.close(fd)
def os_create(name):
os.close(os.open(name, os.O_CREAT | os.O_RDWR))
def tst_unlink(src_dir, mnt_dir, cache_timeout):
name = name_generator()
fullname = mnt_dir + "/" + name
@@ -210,6 +229,32 @@ def tst_open_write(src_dir, mnt_dir):
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'
@@ -229,7 +274,7 @@ def tst_open_unlink(mnt_dir):
def tst_statvfs(mnt_dir):
os.statvfs(mnt_dir)
def tst_link(mnt_dir):
def tst_link(mnt_dir, cache_timeout):
name1 = pjoin(mnt_dir, name_generator())
name2 = pjoin(mnt_dir, name_generator())
shutil.copyfile(TEST_FILE, name1)
@@ -240,6 +285,14 @@ def tst_link(mnt_dir):
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',
+1 -8
View File
@@ -36,13 +36,6 @@ for san in undefined address; do
meson -D b_sanitize=${san} -D b_lundef=false -D werror=true ..
ninja
${TEST_CMD}
sudo ninja install
cd ..
done
# Autotools build
CC=gcc
autoreconf -i
./configure
make
${TEST_CMD}
sudo make install
+15 -2
View File
@@ -2,7 +2,6 @@
set -e
sudo ln -svf $(which python3) /usr/bin/python3
sudo python3 -m pip install pytest meson
wget https://github.com/ninja-build/ninja/releases/download/v1.7.2/ninja-linux.zip
unzip ninja-linux.zip
@@ -13,9 +12,23 @@ valgrind --version
ninja --version
meson --version
# Install fuse
wget https://github.com/libfuse/libfuse/archive/master.zip
unzip master.zip
cd libfuse-master
mkdir build
cd build
export CC=gcc-6
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/
ls -d1 /usr/local/lib/*-linux-gnu | sudo tee /etc/ld.so.conf.d/usrlocal.conf
sudo ldconfig
# Setup ssh
ssh-keygen -b 768 -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"
+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"