Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fc63c64602 | |||
| 1e6e067fdf | |||
| 44a56f0cac | |||
| 653de120c8 | |||
| 63300fd564 | |||
| 45710c81d7 | |||
| 161ece55a3 | |||
| db149d1d87 | |||
| 861d308a03 | |||
| 319f0dde18 | |||
| 82766d1093 | |||
| 6f6491cd13 | |||
| 03a693d295 | |||
| 55eb1f00e6 | |||
| 37b6969067 | |||
| ff6194b5c7 | |||
| af31551e18 | |||
| 82e899fb21 | |||
| 34146444ce | |||
| 6cc86fc0bd | |||
| 73e4cf79b0 | |||
| a202e2dcc8 | |||
| c711bf738f | |||
| 493081e8f6 | |||
| 3033dbc22e | |||
| 5f4619bac3 | |||
| b66ecb9c3a | |||
| 9ac5829b89 | |||
| 9cfeaf5120 | |||
| 56d6030f9c | |||
| 152617e53f | |||
| 0908f570dc | |||
| 5bc9ff371d | |||
| 4379773a9e | |||
| 6056149d16 | |||
| 53a14aeaf1 | |||
| 8999210c93 | |||
| 5ed2cf8a8b | |||
| 9f2eb04371 | |||
| 6c301e043b | |||
| 6d7ce1607a | |||
| 5659d0d2ea | |||
| fe3ce10ff8 | |||
| 132dd88755 | |||
| af1f3c6235 | |||
| 8cc069f666 | |||
| d7cb098f1d | |||
| c7d8500634 | |||
| 2896aed825 | |||
| 110163d8d1 | |||
| d93f137c8f |
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -21,6 +21,7 @@ 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>
|
||||
@@ -30,4 +31,5 @@ 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>
|
||||
|
||||
@@ -1,3 +1,37 @@
|
||||
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)
|
||||
------------------------
|
||||
|
||||
+7
-19
@@ -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' \
|
||||
-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
|
||||
|
||||
@@ -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
@@ -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.
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -1,4 +1,4 @@
|
||||
AC_INIT(sshfs, 2.9)
|
||||
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
@@ -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
-2
@@ -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
|
||||
|
||||
@@ -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__ */
|
||||
@@ -0,0 +1 @@
|
||||
__pycache__/
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -0,0 +1,2 @@
|
||||
[pytest]
|
||||
addopts = --verbose --assert=rewrite --tb=native -x -r a
|
||||
Executable
+383
@@ -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)
|
||||
Executable
+48
@@ -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
|
||||
Executable
+35
@@ -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
@@ -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 = []
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user