Compare commits

...

71 Commits

Author SHA1 Message Date
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
Nikolaus Rath 1dbda31786 Released 2.9 2017-04-17 13:13:38 -07:00
Nikolaus Rath 33667f2a56 Fixed up version numbers in ChangeLog 2017-04-17 13:09:35 -07:00
Nikolaus Rath f627055bc7 Merge pull request #23 from billziss-gh/master
Port SSHFS to Cygwin
2017-01-12 14:57:45 -08:00
Bill Zissimopoulos efeed71295 remove no longer useful check for fsp_fuse_opt_parse from configure.ac 2017-01-12 11:24:08 -08:00
Nikolaus Rath 96c6138978 Merge pull request #38 from BerserkerTroll/master
sshfs.1.in: remove duplicate "slave" option description
2016-12-30 11:07:57 -08:00
Berserker 1afaef8f46 sshfs.1.in: remove duplicate "slave" option description 2016-12-30 20:52:47 +03:00
Nikolaus Rath e5acfce8ed Merge pull request #27 from benesch/master
Remove call to g_slice_set_config()
2016-07-28 16:22:16 -07:00
Benjamin Fleischer e178150a72 Remove call to g_slice_set_config()
It is unclear why G_SLICE_CONFIG_ALWAYS_MALLOC has been set before but
doing so with the latest version of GLib (2.46.2) results in the
following warning:

GLib-CRITICAL **: g_slice_set_config: assertion 'sys_page_size == 0'
failed

This fixes issue osxfuse/sshfs#17
2016-07-28 12:25:37 -04:00
Nikolaus Rath c63acf3dd7 Merge pull request #24 from kuscsik/master
Makefile: fix path for sshfs.1
2016-07-01 15:09:32 -04:00
Zoltan Kuscsik e3cd445a4e Makefile: fix path for sshfs.1
Fix source path when build directory differs
from the source dir.
2016-07-01 09:30:31 +02:00
Nikolaus Rath 1167c11901 Merge pull request #20 from rianhunter/master
Require paths for truncate/fstat workarounds
2016-06-23 12:10:11 -04:00
Bill Zissimopoulos a175c2876a port sshfs to cygwin 2016-06-22 17:02:30 -07:00
Nikolaus Rath b2fa759358 Released 2.8 2016-06-22 11:38:53 -07:00
Nikolaus Rath 1756abe6fd Fixed release year of version 2.7 2016-06-22 11:37:40 -07:00
Rian Hunter f51d7b0c6a Require paths for truncate/fstat workarounds
Commit 74bfa3850a allowed sshfs to run
without requiring a non-NULL path argument for certain
operations. This was erroneous in the case of using certain
workarounds.
2016-06-06 00:12:42 -07:00
Nikolaus Rath df10d96ac2 Add extra quoting for increased compatibility.
Fixes: #15.
2016-06-05 14:18:49 -04:00
Nikolaus Rath a2dda962c4 Merge branch 'rianhunter-master' 2016-06-05 14:15:52 -04:00
Nikolaus Rath 504e26f938 Added ChangeLog entry for fsync extension 2016-06-05 14:15:36 -04:00
Rian Hunter 74bfa3850a Support the lack of paths when cache is off
Before FUSE 2.9, FUSE had to emulate unlink() if a file was still open
via renaming to a hidden file.  This was due to the requirement that a
valid "path" argument must be submitted for many FUSE operations. FUSE
2.9 introduced the flag_nullpath_ok and flag_nopath flags that allow a
FUSE file system to signal to FUSE that the "path" argument may be NULL
in certain operations.

sshfs doesn't require paths if the cache isn't used so communicate
that information to the FUSE layer.
2016-06-01 23:27:01 -07:00
Rian Hunter 0f6f33b1b4 Implement support for "fsync@openssh.com"
OpenSSH implements fsync() via the extension "fsync@openssh.com". This
change uses that extension when receiving a FUSE fsync request.
2016-06-01 22:34:36 -07:00
26 changed files with 1590 additions and 885 deletions
+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 -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,4 @@ sshfs.1
/.pc
/patches
/m4
.deps/
+19
View File
@@ -0,0 +1,19 @@
sudo: required
dist: trusty
language:
- c
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- valgrind
- clang
- gcc
- gcc-6
before_install:
- pip install pip==8.1.1 && pip3 install pip==8.1.1
install: test/travis-install.sh
script: test/travis-build.sh
+6
View File
@@ -16,9 +16,12 @@ Contributors (autogenerated list)
Alan Jenkins <alan.christopher.jenkins@gmail.com>
Alexander Neumann <alexander@bumpern.de>
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>
Jakub Jelen <jjelen@redhat.com>
Julio Merino <jmmv@google.com>
Julio Merino <jmmv@meroh.net>
Mike Kelly <mike@pair.com>
@@ -27,3 +30,6 @@ Miklos Szeredi <mszeredi@suse.cz>
Nikolaus Rath <Nikolaus@rath.org>
Percy Jahn <email@percyjahn.de>
Qais Patankar <qaisjp@gmail.com>
Rian Hunter <rian@alum.mit.edu>
tpoindessous <thomas@poindessous.com>
Zoltan Kuscsik <zoltan.kuscsik@linaro.org>
+47 -1
View File
@@ -1,4 +1,50 @@
Release 2.6 (2015-03-01)
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
+8 -20
View File
@@ -2,6 +2,8 @@
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
@@ -12,31 +14,17 @@ endif
sshfs_LDADD = $(SSHFS_LIBS)
sshfs_CFLAGS = $(SSHFS_CFLAGS)
sshfs_CPPFLAGS = -D_REENTRANT -DFUSE_USE_VERSION=26 -DLIBDIR=\"$(libdir)\" \
sshfs_CPPFLAGS = -D_REENTRANT -DFUSE_USE_VERSION=31 -DLIBDIR=\"$(libdir)\" \
-DIDMAP_DEFAULT="\"$(IDMAP_DEFAULT)\""
EXTRA_DIST = sshnodelay.c sshfs.1.in
CLEANFILES = sshnodelay.so sshfs.1 sshfs.1.tmp
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' \
<sshfs.1.in >sshfs.1.tmp || exit 1; \
-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
-72
View File
@@ -1,72 +0,0 @@
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
How to mount a filesystem
=========================
Once sshfs is installed (see next section) running it is very simple:
sshfs hostname: 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.
Also many ssh options can be specified (see the manual pages for
sftp(1) and ssh_config(5)), including the remote port number
(`-oport=PORT`)
To unmount the filesystem:
fusermount -u mountpoint
Installing
==========
First you need to download FUSE 2.2 or later from
http://github.com/libfuse/libfuse.
You also need to install the devel package for glib2.0. After
installing FUSE, compile sshfs the usual way:
./configure
make
make install (as root)
And you are ready to go.
If checking out from git for the first time also do `autoreconf -i`
before doing `./configure`.
+133
View File
@@ -0,0 +1,133 @@
SSHFS
=====
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.
How to use
----------
Once sshfs is installed (see next section) running it is very simple::
sshfs [user@]hostname:[directory] mountpoint
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).
Also many ssh options can be specified (see the manual pages for
*sftp(1)* and *ssh_config(5)*), including the remote port number
(``-oport=PORT``)
To unmount the filesystem::
fusermount -u mountpoint
On BSD and OS-X, to unmount the filesystem::
umount mountpoint
Installation
------------
First, download the latest SSHFS release from
https://github.com/libfuse/sshfs/releases. On Linux and BSD, you will
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
(temporary) build directory and run Meson::
$ md build; cd build
$ meson ..
Normally, the default build options will work fine. If you
nevertheless want to adjust them, you can do so with the *mesonconf*
command::
$ mesonconf # list options
$ mesonconf -D strip=true # set an option
To build, test and install SSHFS, you then use Ninja (running the
tests requires the `py.test`_ Python module)::
$ ninja
$ python3 -m pytest test/ # optional, but recommended
$ sudo ninja install
.. _libfuse: http://github.com/libfuse/libfuse
.. _OSXFUSE: https://osxfuse.github.io/
.. _Glib: https://developer.gnome.org/glib/stable/
.. _Meson: http://mesonbuild.com/
.. _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
------------
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.
Professional Support
--------------------
Professional support is available. Please contact Nikolaus Rath
<Nikolaus@rath.org> for details.
+173 -159
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,132 +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
}
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
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 },
@@ -581,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
};
@@ -595,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);
+3 -30
View File
@@ -1,4 +1,4 @@
AC_INIT(sshfs, 2.7)
AC_INIT(sshfs, 3.1.0)
AC_CANONICAL_TARGET
AM_INIT_AUTOMAKE([foreign subdir-objects])
AM_CONFIG_HEADER(config.h)
@@ -8,8 +8,6 @@ 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
@@ -18,33 +16,8 @@ case "$target_os" in
*) 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])
PKG_CHECK_MODULES([SSHFS], [fuse3 >= 3.1 glib-2.0 gthread-2.0])
have_fuse_opt_parse=no
oldlibs="$LIBS"
LIBS="$LIBS $SSHFS_LIBS"
@@ -67,5 +40,5 @@ case "$osname" in
esac
AC_SUBST(IDMAP_DEFAULT)
AC_CONFIG_FILES([Makefile])
AC_CONFIG_FILES([Makefile test/Makefile])
AC_OUTPUT
+64
View File
@@ -0,0 +1,64 @@
project('sshfs', 'c', version: '3.1.0',
meson_version: '>= 0.38',
default_options: [ 'buildtype=plain' ])
add_global_arguments('-D_REENTRANT', '-DHAVE_CONFIG_H', '-O2', '-g',
'-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
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'
cfg.set_quoted('IDMAP_DEFAULT', 'user')
sshfs_sources += [ 'compat/fuse_opt.c', 'compat/darwin_compat.c' ]
include_dirs += [ include_directories('compat') ]
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('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'))
# 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'))
subdir('test')
+2 -4
View File
@@ -7,7 +7,7 @@ SSHFS \- filesystem client based on ssh
\fBsshfs\fP [\fIuser\fP@]\fBhost\fP:[\fIdir\fP] \fBmountpoint\fP [\fIoptions\fP]
.SS unmounting
.TP
\fB__UNMOUNT_COMMAND__ mountpoint\fP
\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)
@@ -97,7 +97,7 @@ fix buffer fillup bug in server (default: on)
.RE
.TP
\fB\-o\fR idmap=TYPE
user/group ID mapping (default: __IDMAP_DEFAULT__)
user/group ID mapping (default: @IDMAP_DEFAULT@)
.RS 8
.TP
none
@@ -141,8 +141,6 @@ path to sftp server or subsystem (default: sftp)
.TP
\fB\-o\fR directport=PORT
directly connect to PORT bypassing ssh
\fB\-o\fR slave
communicate over stdin and stdout bypassing network
.TP
\fB\-o\fR slave
communicate over stdin and stdout bypassing network
+415 -524
View File
File diff suppressed because it is too large Load Diff
-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__/
+4
View File
@@ -0,0 +1,4 @@
## 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
+89
View File
@@ -0,0 +1,89 @@
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.mark.hookwrapper
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.yield_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)
+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', '-fPu', '--preserve=mode',
'@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)
+2
View File
@@ -0,0 +1,2 @@
[pytest]
addopts = --verbose --assert=rewrite --tb=native -x -r a
+383
View File
@@ -0,0 +1,383 @@
#!/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)
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]):
__ctr[0] += 1
return 'testfile_%d' % __ctr[0]
@pytest.mark.parametrize("debug", (False, True))
@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:
# capfd.register_output(r'^ unique: [0-9]+, error: -[0-9]+ .+$',
# count=0)
# Test if we can ssh into localhost without password
try:
res = subprocess.call(['ssh', '-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', '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', 'dcache_timeout=%d' % cache_timeout,
'-o', 'dir_cache=yes' ]
# FUSE Cache
cmdline += [ '-o', 'entry_timeout=0',
'-o', 'attr_timeout=0' ]
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(mnt_dir)
tst_readdir(src_dir, mnt_dir)
tst_open_read(src_dir, mnt_dir)
tst_open_write(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_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_link(mnt_dir)
tst_truncate_path(mnt_dir)
tst_truncate_fd(mnt_dir)
tst_open_unlink(mnt_dir)
except:
cleanup(mnt_dir)
raise
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_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)
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_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(mnt_dir):
os.statvfs(mnt_dir)
def tst_link(mnt_dir):
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)
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)
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]
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]
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_passthrough(src_dir, mnt_dir, cache_timeout):
name = name_generator()
src_name = pjoin(src_dir, name)
mnt_name = pjoin(src_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)
assert os.stat(src_name) == os.stat(mnt_name)
name = name_generator()
src_name = pjoin(src_dir, name)
mnt_name = pjoin(src_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)
assert os.stat(src_name) == os.stat(mnt_name)
+48
View File
@@ -0,0 +1,48 @@
#!/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 gcc-6 clang; do
mkdir build-${CC}; cd build-${CC}
if [ ${CC} == 'gcc-6' ]; then
build_opts='-D b_lundef=false'
else
build_opts=''
fi
meson -D werror=true ${build_opts} ../
ninja
TEST_WITH_VALGRIND=true ${TEST_CMD}
cd ..
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}
cd ..
done
# Autotools build
CC=gcc
autoreconf -i
./configure
make
${TEST_CMD}
sudo make install
+35
View File
@@ -0,0 +1,35 @@
#!/bin/sh
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
chmod 755 ninja
sudo chown root:root ninja
sudo mv -fv ninja /usr/local/bin
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"
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env python3
import subprocess
import pytest
import os
import stat
import time
from os.path import join as pjoin
basename = pjoin(os.path.dirname(__file__), '..')
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(mnt_dir):
subprocess.call(['fusermount', '-z', '-u', mnt_dir],
stdout=subprocess.DEVNULL,
stderr=subprocess.STDOUT)
def umount(mount_process, mnt_dir):
subprocess.check_call(['fusermount', '-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('file system process terminated with code %s' % (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()`.
'''
skip = lambda x: pytest.mark.skip(reason=x)
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('Unable to open /dev/fuse: %s' % 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;
}