Compare commits

...

8 Commits

Author SHA1 Message Date
Miklos Szeredi 6b4415ada4 Released 2.5 2014-01-14 15:23:31 +01:00
Alexander Neumann 6a2d06e36f Implement asynchronous readdir
Accessing directories with many (several thousand) files over sshfs is
slow, because most SFTP server implementations only send a fixed amount
of entries per READDIR command (e.g. OpenSSH SFTP: 100 entries). This
patch implements sending several READDIR commands in parallel, in order
to speed up directory listing in these cases.

An option (sync_readdir) is also added so that users can easily switch
on the old behaviour.

The performance improvement is astonishing. Accessing a directory with
30k files in from a remote server that has a RTT of 15ms via OpenSSH
SFTP:

Synchronous readdir:
 $ ./sshfs -o sync_readdir host:/tmp /mnt/temp
 $ time "ls" -1 /mnt/temp/test | wc -l
 30000
 "ls" -1 /mnt/temp/test  0.07s user 0.01s system 1% cpu 6.928 total

Asynchronous readdir:
 $ ./sshfs host:/tmp /mnt/temp
 $ time "ls" -1 /mnt/temp/test | wc -l
 30000
 "ls" -1 /mnt/temp/test  0.07s user 0.01s system 12% cpu 0.605 total

Accessing a directory with 100k files shows even more dramatic
improvement:

Synchronous readdir:
 $ ./sshfs -o sync_readdir host:/tmp /mnt/temp
 $ time "ls" -1 /mnt/temp/test2 | wc -l
 100000
 "ls" -1 /mnt/temp/test2  0.67s user 1.22s system 0% cpu 3:31.56 total

Asynchronous readdir:
 $ ./sshfs host:/tmp /mnt/temp
 $ time "ls" -1 /mnt/temp/test2 | wc -l
 100000
 "ls" -1 /mnt/temp/test2  0.20s user 0.03s system 14% cpu 1.631 total

This can easily be reproduced by creating a directory on a server and
touching a lot of files in it:
 $ mkdir /tmp/test
 $ cd /tmp/test
 $ for i in $(seq 1 30000); do touch $i; done

Signed-off-by: Alexander Neumann <alexander@bumpern.de>
2014-01-08 16:34:52 +01:00
Miklos Szeredi 334e9e6a27 Add -o disable_hardlink option (debian bug #670926)
Reported by Louis-David Mitterrand
2014-01-08 12:31:18 +01:00
Miklos Szeredi 91c1f2ba9e Map SSH2_FX_FAILURE to ENOTEMPTY for rmdir
Reported by Ross Lagerwall
2014-01-07 18:52:51 +01:00
Miklos Szeredi c1d62f2032 fix a typo in man page 2013-04-11 17:58:21 +02:00
Alan Jenkins 61524cd1e8 fix missing newline in manual page 2013-02-18 14:36:51 +01:00
Miklos Szeredi 9f4ba56b52 Add FIXME for deadlock if $PATH contains mountpoint.
Reported by Joachim Kopp
2013-02-05 12:33:36 +01:00
Miklos Szeredi ce6753c88f When checking root directory use LSTAT not STAT
This prevents I/O error being returned after a successful mount if a symlink is
mounted.

Reported by Bart Friederichs
2012-05-14 16:18:22 +02:00
4 changed files with 172 additions and 14 deletions
+24
View File
@@ -1,3 +1,27 @@
2014-01-14 Miklos Szeredi <miklos@szeredi.hu>
* Released 2.5
2014-01-08 Miklos Szeredi <miklos@szeredi.hu>
* Add -o disable_hardlink option (debian bug #670926). Reported
by Louis-David Mitterrand
* Optimize readdir by sending multiple requests in parallel. Add
-o sync_readdir to restore old behavior. Patch by Alexander
Neumann
2014-01-07 Miklos Szeredi <miklos@szeredi.hu>
* Map SSH2_FX_FAILURE to ENOTEMPTY for rmdir. Reported by Ross
Lagerwall
2012-05-14 Miklos Szeredi <miklos@szeredi.hu>
* When checking root directory use LSTAT not STAT. This prevents
I/O error being returned after a successful mount if a symlink is
mounted. Reported by Bart Friederichs
2012-03-08 Miklos Szeredi <miklos@szeredi.hu>
* Released 2.4
+1 -1
View File
@@ -1,4 +1,4 @@
AC_INIT(sshfs-fuse, 2.4)
AC_INIT(sshfs-fuse, 2.5)
AM_INIT_AUTOMAKE
AM_CONFIG_HEADER(config.h)
+10 -1
View File
@@ -58,6 +58,9 @@ synchronous writes
\fB\-o\fR no_readahead
synchronous reads (no speculative readahead)
.TP
\fB\-o\fR sync_readdir
synchronous readdir
.TP
\fB\-o\fR sshfs_debug
print some debugging information
.TP
@@ -138,9 +141,15 @@ path to sftp server or subsystem (default: sftp)
.TP
\fB\-o\fR directport=PORT
directly connect to PORT bypassing ssh
.TP
\fB\-o\fR slave
communicate over stdin and stdout bypassing network
.TP
\fB\-o\fR disable_hardlink
link(2) will return with errno set to ENOSYS. Hard links don't currently work
perfectly on sshfs, and this confuses some programs. If that happens try
disabling hard links with this option.
.TP
\fB\-o\fR transform_symlinks
transform absolute symlinks to relative
.TP
@@ -256,7 +265,7 @@ perform reads synchronously
\fB\-o\fR subdir=DIR
prepend this directory to all paths (mandatory)
.TP
\fB\-o\fR [no]rellinksa
\fB\-o\fR [no]rellinks
transform absolute symlinks to relative
.TP
[iconv]
+137 -12
View File
@@ -121,6 +121,10 @@
#define SSHNODELAY_SO "sshnodelay.so"
/* Asynchronous readdir parameters */
#define READDIR_START 2
#define READDIR_MAX 32
struct buffer {
uint8_t *p;
size_t len;
@@ -139,6 +143,7 @@ struct request {
unsigned int want_reply;
sem_t ready;
uint8_t reply_type;
uint32_t id;
int replied;
int error;
struct buffer reply;
@@ -203,6 +208,7 @@ struct sshfs {
int detect_uid;
int idmap;
int nomap;
int disable_hardlink;
char *uid_file;
char *gid_file;
GHashTable *uid_map;
@@ -214,6 +220,7 @@ struct sshfs {
unsigned ssh_ver;
int sync_write;
int sync_read;
int sync_readdir;
int debug;
int foreground;
int reconnect;
@@ -350,6 +357,7 @@ static struct fuse_opt sshfs_opts[] = {
SSHFS_OPT("nomap=error", nomap, NOMAP_ERROR),
SSHFS_OPT("sshfs_sync", sync_write, 1),
SSHFS_OPT("no_readahead", sync_read, 1),
SSHFS_OPT("sync_readdir", sync_readdir, 1),
SSHFS_OPT("sshfs_debug", debug, 1),
SSHFS_OPT("reconnect", reconnect, 1),
SSHFS_OPT("transform_symlinks", transform_symlinks, 1),
@@ -358,6 +366,7 @@ static struct fuse_opt sshfs_opts[] = {
SSHFS_OPT("password_stdin", password_stdin, 1),
SSHFS_OPT("delay_connect", delay_connect, 1),
SSHFS_OPT("slave", slave, 1),
SSHFS_OPT("disable_hardlink", disable_hardlink, 1),
FUSE_OPT_KEY("-p ", KEY_PORT),
FUSE_OPT_KEY("-C", KEY_COMPRESS),
@@ -1620,7 +1629,7 @@ static int sftp_check_root(const char *base_path)
buf_init(&buf, 0);
buf_add_string(&buf, remote_dir);
buf_to_iov(&buf, &iov[0]);
if (sftp_send_iov(SSH_FXP_STAT, id, iov, 1) == -1)
if (sftp_send_iov(SSH_FXP_LSTAT, id, iov, 1) == -1)
goto out;
buf_clear(&buf);
if (sftp_read(&type, &buf) == -1)
@@ -1790,6 +1799,13 @@ static int sftp_request_wait(struct request *req, uint8_t type,
err = -EIO;
break;
case SSH_FX_FAILURE:
if (type == SSH_FXP_RMDIR)
err = -ENOTEMPTY;
else
err = -EPERM;
break;
default:
err = -sftp_error_to_errno(serr);
}
@@ -1827,6 +1843,7 @@ static int sftp_request_send(uint8_t type, struct iovec *iov, size_t count,
if (begin_func)
begin_func(req);
id = sftp_get_id();
req->id = id;
err = start_processing_thread();
if (err) {
pthread_mutex_unlock(&sshfs.lock);
@@ -2014,6 +2031,113 @@ static int sshfs_readlink(const char *path, char *linkbuf, size_t size)
return err;
}
static int sftp_readdir_send(struct request **req, struct buffer *handle)
{
struct iovec iov;
buf_to_iov(handle, &iov);
return sftp_request_send(SSH_FXP_READDIR, &iov, 1, NULL, NULL,
SSH_FXP_NAME, NULL, req);
}
static int sshfs_req_pending(struct request *req)
{
if (g_hash_table_lookup(sshfs.reqtab, GUINT_TO_POINTER(req->id)))
return 1;
else
return 0;
}
static int sftp_readdir_async(struct buffer *handle, fuse_cache_dirh_t h,
fuse_cache_dirfil_t filler)
{
int err = 0;
int outstanding = 0;
int max = READDIR_START;
GList *list = NULL;
int done = 0;
while (!done || outstanding) {
struct request *req;
struct buffer name;
int tmperr;
while (!done && outstanding < max) {
tmperr = sftp_readdir_send(&req, handle);
if (tmperr && !done) {
err = tmperr;
done = 1;
break;
}
list = g_list_append(list, req);
outstanding++;
}
if (outstanding) {
GList *first;
/* wait for response to next request */
first = g_list_first(list);
req = first->data;
list = g_list_delete_link(list, first);
outstanding--;
if (done) {
pthread_mutex_lock(&sshfs.lock);
if (sshfs_req_pending(req))
req->want_reply = 0;
pthread_mutex_unlock(&sshfs.lock);
if (!req->want_reply)
continue;
}
tmperr = sftp_request_wait(req, SSH_FXP_READDIR,
SSH_FXP_NAME, &name);
if (tmperr && !done) {
err = tmperr;
if (err == MY_EOF)
err = 0;
done = 1;
}
if (!done) {
err = buf_get_entries(&name, h, filler);
buf_free(&name);
/* increase number of outstanding requests */
if (max < READDIR_MAX)
max++;
if (err)
done = 1;
}
}
}
assert(list == NULL);
return err;
}
static int sftp_readdir_sync(struct buffer *handle, fuse_cache_dirh_t h,
fuse_cache_dirfil_t filler)
{
int err;
do {
struct buffer name;
err = sftp_request(SSH_FXP_READDIR, handle, SSH_FXP_NAME, &name);
if (!err) {
err = buf_get_entries(&name, h, filler);
buf_free(&name);
}
} while (!err);
if (err == MY_EOF)
err = 0;
return err;
}
static int sshfs_getdir(const char *path, fuse_cache_dirh_t h,
fuse_cache_dirfil_t filler)
{
@@ -2026,16 +2150,11 @@ static int sshfs_getdir(const char *path, fuse_cache_dirh_t h,
if (!err) {
int err2;
buf_finish(&handle);
do {
struct buffer name;
err = sftp_request(SSH_FXP_READDIR, &handle, SSH_FXP_NAME, &name);
if (!err) {
err = buf_get_entries(&name, h, filler);
buf_free(&name);
}
} while (!err);
if (err == MY_EOF)
err = 0;
if (sshfs.sync_readdir)
err = sftp_readdir_sync(&handle, h, filler);
else
err = sftp_readdir_async(&handle, h, filler);
err2 = sftp_request(SSH_FXP_CLOSE, &handle, 0, NULL);
if (!err)
@@ -2192,7 +2311,7 @@ static int sshfs_link(const char *from, const char *to)
{
int err = -ENOSYS;
if (sshfs.ext_hardlink) {
if (sshfs.ext_hardlink && !sshfs.disable_hardlink) {
struct buffer buf;
buf_init(&buf, 0);
@@ -3162,6 +3281,7 @@ static void usage(const char *progname)
" -o delay_connect delay connection to server\n"
" -o sshfs_sync synchronous writes\n"
" -o no_readahead synchronous reads (no speculative readahead)\n"
" -o sync_readdir synchronous readdir\n"
" -o sshfs_debug print some debugging information\n"
" -o cache=BOOL enable caching {yes,no} (default: yes)\n"
" -o cache_timeout=N sets timeout for caches in seconds (default: 20)\n"
@@ -3190,6 +3310,7 @@ static void usage(const char *progname)
" -o sftp_server=SERV path to sftp server or subsystem (default: sftp)\n"
" -o directport=PORT directly connect to PORT bypassing ssh\n"
" -o slave communicate over stdin and stdout bypassing network\n"
" -o disable_hardlink link(2) will return with errno set to ENOSYS\n"
" -o transform_symlinks transform absolute symlinks to relative\n"
" -o follow_symlinks follow symlinks on the server\n"
" -o no_check_root don't check for existence of 'dir' on server\n"
@@ -3863,6 +3984,10 @@ int main(int argc, char *argv[])
exit(1);
}
/*
* FIXME: trim $PATH so it doesn't contain anything inside the
* mountpoint, which would deadlock.
*/
res = ssh_connect();
if (res == -1) {
fuse_unmount(mountpoint, ch);