Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b3aac4e035 | |||
| 330c126684 | |||
| 3cdf0a547a | |||
| e233f82239 | |||
| 9693a8bf05 | |||
| 80748b39e4 | |||
| 8e303b9a01 | |||
| 838fed1a3a | |||
| 99acbfff7a | |||
| 2705253d2c | |||
| b318855315 | |||
| da058cf9da | |||
| a464d3ec2c | |||
| d5696036a5 | |||
| 551e32210b | |||
| 240b5f7bb5 | |||
| 39b1a10bdb | |||
| 386a7f67ab | |||
| cdc0da7956 | |||
| 733b3b1f4a | |||
| 88ab18b8f8 | |||
| 716bfbf697 | |||
| 7fb3a02ec5 | |||
| b3483b6bb9 | |||
| 6f3b9bfdd1 | |||
| c73761963b | |||
| 5dc1d26e70 |
+16
@@ -0,0 +1,16 @@
|
||||
.deps
|
||||
COPYING
|
||||
INSTALL
|
||||
Makefile
|
||||
Makefile.in
|
||||
aclocal.m4
|
||||
*.cache
|
||||
config.*
|
||||
configure
|
||||
compile
|
||||
depcomp
|
||||
install-sh
|
||||
missing
|
||||
sshfs
|
||||
stamp*
|
||||
.deps
|
||||
@@ -0,0 +1,56 @@
|
||||
2005-03-03 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Slightly optimize readahead. Still not clever enough to always
|
||||
keep the pipe filled.
|
||||
|
||||
* Add 'sshfs_debug' option
|
||||
|
||||
2005-02-17 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Parse 'max_read' mount option and if smaller than 65536 forward
|
||||
to FUSE
|
||||
|
||||
2005-02-16 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Added simple readahead (big performance gain in case of
|
||||
sequential read pattern). Can be disabled with '-o no_readahead'
|
||||
|
||||
2005-02-14 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Added asynchronous writeback (big performance gain) and made
|
||||
this the default. Can be disabled with '-o sshfs_sync'
|
||||
|
||||
2005-02-09 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Added option to start arbitary command instead of 'ssh'
|
||||
|
||||
* Re-added '-p PORT' as a convenience option, also '-C' works as
|
||||
in ssh.
|
||||
|
||||
2005-02-08 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Add caching of symlinks
|
||||
|
||||
* Add support for many ssh options to be passed to ssh
|
||||
|
||||
* Port number can now actually be specified with "-o port=PORT",
|
||||
bug spotted by Nadia Maksymiw
|
||||
|
||||
2005-02-07 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Separate attribute caching to a separate layer
|
||||
|
||||
* Add caching of directory contents
|
||||
|
||||
2005-02-03 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
Fix PKG_CONFIG_PATH setting in configure.ac (reported by Alpar
|
||||
Juttner)
|
||||
|
||||
2005-01-09 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Released 1.0
|
||||
|
||||
2004-12-04 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Started ChangeLog
|
||||
@@ -1,21 +0,0 @@
|
||||
CC = gcc
|
||||
|
||||
CFLAGS := -Wall -W -g
|
||||
LDLIBS := -lpthread -ldl -rdynamic
|
||||
|
||||
PKGCONFIG := env PKG_CONFIG_PATH=/usr/local/lib/pkgconfig pkg-config
|
||||
FUSEVER := $(shell $(PKGCONFIG) --modversion fuse 2> /dev/null)
|
||||
ifeq ($(FUSEVER),)
|
||||
LDLIBS += -lfuse
|
||||
else
|
||||
CFLAGS += $(shell $(PKGCONFIG) --cflags fuse)
|
||||
LDLIBS += $(shell $(PKGCONFIG) --libs fuse)
|
||||
endif
|
||||
|
||||
CPPFLAGS := -D_FILE_OFFSET_BITS=64
|
||||
#CPPFLAGS += -DDEBUG
|
||||
|
||||
sshfs: sshfs.o
|
||||
|
||||
clean:
|
||||
rm -f *.o sshfs
|
||||
@@ -0,0 +1,5 @@
|
||||
## Process this file with automake to produce Makefile.in
|
||||
|
||||
bin_PROGRAMS = sshfs
|
||||
sshfs_SOURCES = sshfs.c cache.c opts.c cache.h opts.h
|
||||
sshfs_CPPFLAGS = -DFUSE_USE_VERSION=22
|
||||
@@ -0,0 +1,19 @@
|
||||
What is new in 1.1
|
||||
------------------
|
||||
|
||||
* Performance improvements:
|
||||
|
||||
- directory content caching
|
||||
|
||||
- symlink caching
|
||||
|
||||
- asynchronous writeback
|
||||
|
||||
- readahead
|
||||
|
||||
* Fixed '-p' option
|
||||
|
||||
What is new in 1.0
|
||||
------------------
|
||||
|
||||
* Initial release
|
||||
@@ -0,0 +1,71 @@
|
||||
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
|
||||
|
||||
|
||||
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 moutpoint
|
||||
|
||||
|
||||
Installing
|
||||
==========
|
||||
|
||||
First you need to download FUSE 2.2 or later from:
|
||||
|
||||
http://fuse.sourceforge.net
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Bugs and feature requests
|
||||
=========================
|
||||
|
||||
Send bug reports to <miklos@szeredi.hu>.
|
||||
|
||||
Good luck!
|
||||
Miklos Szeredi
|
||||
@@ -0,0 +1,526 @@
|
||||
/*
|
||||
Caching file system proxy
|
||||
Copyright (C) 2004 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU GPL.
|
||||
See the file COPYING.
|
||||
*/
|
||||
|
||||
#include "cache.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <glib.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "opts.h"
|
||||
|
||||
#define DEFAULT_CACHE_TIMEOUT 20
|
||||
#define MAX_CACHE_SIZE 10000
|
||||
#define MIN_CACHE_CLEAN_INTERVAL 5
|
||||
#define CACHE_CLEAN_INTERVAL 60
|
||||
|
||||
static int cache_on = 1;
|
||||
static unsigned cache_stat_timeout = DEFAULT_CACHE_TIMEOUT;
|
||||
static unsigned cache_dir_timeout = DEFAULT_CACHE_TIMEOUT;
|
||||
static unsigned cache_link_timeout = DEFAULT_CACHE_TIMEOUT;
|
||||
|
||||
struct node {
|
||||
struct stat stat;
|
||||
time_t stat_valid;
|
||||
char **dir;
|
||||
time_t dir_valid;
|
||||
char *link;
|
||||
time_t link_valid;
|
||||
time_t valid;
|
||||
};
|
||||
|
||||
struct fuse_cache_dirhandle {
|
||||
const char *path;
|
||||
fuse_dirh_t h;
|
||||
fuse_dirfil_t filler;
|
||||
GPtrArray *dir;
|
||||
};
|
||||
|
||||
static struct fuse_cache_operations *next_oper;
|
||||
static GHashTable *cache;
|
||||
static pthread_mutex_t cache_lock;
|
||||
static time_t last_cleaned;
|
||||
|
||||
static void free_node(gpointer node_)
|
||||
{
|
||||
struct node *node = (struct node *) node_;
|
||||
g_strfreev(node->dir);
|
||||
g_free(node);
|
||||
}
|
||||
|
||||
static int cache_clean_entry(void *key_, struct node *node, time_t *now)
|
||||
{
|
||||
(void) key_;
|
||||
if (*now > node->valid)
|
||||
return TRUE;
|
||||
else
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void cache_clean(void)
|
||||
{
|
||||
time_t now = time(NULL);
|
||||
if (now > last_cleaned + MIN_CACHE_CLEAN_INTERVAL &&
|
||||
(g_hash_table_size(cache) > MAX_CACHE_SIZE ||
|
||||
now > last_cleaned + CACHE_CLEAN_INTERVAL)) {
|
||||
g_hash_table_foreach_remove(cache, (GHRFunc) cache_clean_entry, &now);
|
||||
last_cleaned = now;
|
||||
}
|
||||
}
|
||||
|
||||
static struct node *cache_lookup(const char *path)
|
||||
{
|
||||
return (struct node *) g_hash_table_lookup(cache, path);
|
||||
}
|
||||
|
||||
static void cache_purge(const char *path)
|
||||
{
|
||||
g_hash_table_remove(cache, path);
|
||||
}
|
||||
|
||||
static void cache_purge_parent(const char *path)
|
||||
{
|
||||
const char *s = strrchr(path, '/');
|
||||
if (s) {
|
||||
if (s == path)
|
||||
g_hash_table_remove(cache, "/");
|
||||
else {
|
||||
char *parent = g_strndup(path, s - path);
|
||||
cache_purge(parent);
|
||||
g_free(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void cache_invalidate(const char *path)
|
||||
{
|
||||
pthread_mutex_lock(&cache_lock);
|
||||
cache_purge(path);
|
||||
pthread_mutex_unlock(&cache_lock);
|
||||
}
|
||||
|
||||
static void cache_invalidate_dir(const char *path)
|
||||
{
|
||||
pthread_mutex_lock(&cache_lock);
|
||||
cache_purge(path);
|
||||
cache_purge_parent(path);
|
||||
pthread_mutex_unlock(&cache_lock);
|
||||
}
|
||||
|
||||
static void cache_do_rename(const char *from, const char *to)
|
||||
{
|
||||
pthread_mutex_lock(&cache_lock);
|
||||
cache_purge(from);
|
||||
cache_purge(to);
|
||||
cache_purge_parent(from);
|
||||
cache_purge_parent(to);
|
||||
pthread_mutex_unlock(&cache_lock);
|
||||
}
|
||||
|
||||
static struct node *cache_get(const char *path)
|
||||
{
|
||||
struct node *node = cache_lookup(path);
|
||||
if (node == NULL) {
|
||||
char *pathcopy = g_strdup(path);
|
||||
node = g_new0(struct node, 1);
|
||||
g_hash_table_insert(cache, pathcopy, node);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
static void cache_add_attr(const char *path, const struct stat *stbuf)
|
||||
{
|
||||
struct node *node;
|
||||
time_t now;
|
||||
|
||||
pthread_mutex_lock(&cache_lock);
|
||||
node = cache_get(path);
|
||||
now = time(NULL);
|
||||
node->stat = *stbuf;
|
||||
node->stat_valid = time(NULL) + cache_stat_timeout;
|
||||
if (node->stat_valid > node->valid)
|
||||
node->valid = node->stat_valid;
|
||||
cache_clean();
|
||||
pthread_mutex_unlock(&cache_lock);
|
||||
}
|
||||
|
||||
static void cache_add_dir(const char *path, char **dir)
|
||||
{
|
||||
struct node *node;
|
||||
time_t now;
|
||||
|
||||
pthread_mutex_lock(&cache_lock);
|
||||
node = cache_get(path);
|
||||
now = time(NULL);
|
||||
g_strfreev(node->dir);
|
||||
node->dir = dir;
|
||||
node->dir_valid = time(NULL) + cache_dir_timeout;
|
||||
if (node->dir_valid > node->valid)
|
||||
node->valid = node->dir_valid;
|
||||
cache_clean();
|
||||
pthread_mutex_unlock(&cache_lock);
|
||||
}
|
||||
|
||||
static size_t my_strnlen(const char *s, size_t maxsize)
|
||||
{
|
||||
const char *p;
|
||||
for (p = s; maxsize && *p; maxsize--, p++);
|
||||
return p - s;
|
||||
}
|
||||
|
||||
static void cache_add_link(const char *path, const char *link, size_t size)
|
||||
{
|
||||
struct node *node;
|
||||
time_t now;
|
||||
|
||||
pthread_mutex_lock(&cache_lock);
|
||||
node = cache_get(path);
|
||||
now = time(NULL);
|
||||
g_free(node->link);
|
||||
node->link = g_strndup(link, my_strnlen(link, size-1));
|
||||
node->link_valid = time(NULL) + cache_link_timeout;
|
||||
if (node->link_valid > node->valid)
|
||||
node->valid = node->link_valid;
|
||||
cache_clean();
|
||||
pthread_mutex_unlock(&cache_lock);
|
||||
}
|
||||
|
||||
static int cache_getattr(const char *path, struct stat *stbuf)
|
||||
{
|
||||
struct node *node;
|
||||
int err;
|
||||
|
||||
pthread_mutex_lock(&cache_lock);
|
||||
node = cache_lookup(path);
|
||||
if (node != NULL) {
|
||||
time_t now = time(NULL);
|
||||
if (node->stat_valid - now >= 0) {
|
||||
*stbuf = node->stat;
|
||||
pthread_mutex_unlock(&cache_lock);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&cache_lock);
|
||||
err = next_oper->oper.getattr(path, stbuf);
|
||||
if (!err)
|
||||
cache_add_attr(path, stbuf);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_readlink(const char *path, char *buf, size_t size)
|
||||
{
|
||||
struct node *node;
|
||||
int err;
|
||||
|
||||
pthread_mutex_lock(&cache_lock);
|
||||
node = cache_lookup(path);
|
||||
if (node != NULL) {
|
||||
time_t now = time(NULL);
|
||||
if (node->link_valid - now >= 0) {
|
||||
strncpy(buf, node->link, size-1);
|
||||
buf[size-1] = '\0';
|
||||
pthread_mutex_unlock(&cache_lock);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&cache_lock);
|
||||
err = next_oper->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)
|
||||
{
|
||||
int err = ch->filler(ch->h, name, 0, 0);
|
||||
if (!err) {
|
||||
g_ptr_array_add(ch->dir, g_strdup(name));
|
||||
char *fullpath = g_strdup_printf("%s/%s",
|
||||
!ch->path[1] ? "" : ch->path, name);
|
||||
cache_add_attr(fullpath, stbuf);
|
||||
g_free(fullpath);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_getdir(const char *path, fuse_dirh_t h, fuse_dirfil_t filler)
|
||||
{
|
||||
struct fuse_cache_dirhandle ch;
|
||||
int err;
|
||||
char **dir;
|
||||
struct node *node;
|
||||
|
||||
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);
|
||||
pthread_mutex_unlock(&cache_lock);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&cache_lock);
|
||||
|
||||
ch.path = path;
|
||||
ch.h = h;
|
||||
ch.filler = filler;
|
||||
ch.dir = g_ptr_array_new();
|
||||
err = next_oper->cache_getdir(path, &ch, cache_dirfill);
|
||||
g_ptr_array_add(ch.dir, NULL);
|
||||
dir = (char **) ch.dir->pdata;
|
||||
if (!err)
|
||||
cache_add_dir(path, dir);
|
||||
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 next_oper->cache_getdir(path, &ch, cache_unity_dirfill);
|
||||
}
|
||||
|
||||
static int cache_mknod(const char *path, mode_t mode, dev_t rdev)
|
||||
{
|
||||
int err = next_oper->oper.mknod(path, mode, rdev);
|
||||
if (!err)
|
||||
cache_invalidate_dir(path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_mkdir(const char *path, mode_t mode)
|
||||
{
|
||||
int err = next_oper->oper.mkdir(path, mode);
|
||||
if (!err)
|
||||
cache_invalidate_dir(path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_unlink(const char *path)
|
||||
{
|
||||
int err = next_oper->oper.unlink(path);
|
||||
if (!err)
|
||||
cache_invalidate_dir(path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_rmdir(const char *path)
|
||||
{
|
||||
int err = next_oper->oper.rmdir(path);
|
||||
if (!err)
|
||||
cache_invalidate_dir(path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_symlink(const char *from, const char *to)
|
||||
{
|
||||
int err = next_oper->oper.symlink(from, to);
|
||||
if (!err)
|
||||
cache_invalidate_dir(to);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_rename(const char *from, const char *to)
|
||||
{
|
||||
int err = next_oper->oper.rename(from, to);
|
||||
if (!err)
|
||||
cache_do_rename(from, to);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_link(const char *from, const char *to)
|
||||
{
|
||||
int err = next_oper->oper.link(from, to);
|
||||
if (!err) {
|
||||
cache_invalidate(from);
|
||||
cache_invalidate_dir(to);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_chmod(const char *path, mode_t mode)
|
||||
{
|
||||
int err = next_oper->oper.chmod(path, mode);
|
||||
if (!err)
|
||||
cache_invalidate(path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_chown(const char *path, uid_t uid, gid_t gid)
|
||||
{
|
||||
int err = next_oper->oper.chown(path, uid, gid);
|
||||
if (!err)
|
||||
cache_invalidate(path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_truncate(const char *path, off_t size)
|
||||
{
|
||||
int err = 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 = next_oper->oper.utime(path, buf);
|
||||
if (!err)
|
||||
cache_invalidate(path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_write(const char *path, const char *buf, size_t size,
|
||||
off_t offset, struct fuse_file_info *fi)
|
||||
{
|
||||
int res = next_oper->oper.write(path, buf, size, offset, fi);
|
||||
if (res >= 0)
|
||||
cache_invalidate(path);
|
||||
return res;
|
||||
}
|
||||
|
||||
static void cache_unity_fill(struct fuse_cache_operations *oper,
|
||||
struct fuse_operations *cache_oper)
|
||||
{
|
||||
cache_oper->getattr = oper->oper.getattr;
|
||||
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;
|
||||
}
|
||||
|
||||
struct fuse_operations *cache_init(struct fuse_cache_operations *oper)
|
||||
{
|
||||
static struct fuse_operations cache_oper;
|
||||
next_oper = oper;
|
||||
|
||||
cache_unity_fill(oper, &cache_oper);
|
||||
if (cache_on) {
|
||||
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;
|
||||
pthread_mutex_init(&cache_lock, NULL);
|
||||
cache = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
|
||||
free_node);
|
||||
if (cache == NULL) {
|
||||
fprintf(stderr, "failed to create cache\n");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return &cache_oper;
|
||||
}
|
||||
|
||||
enum {
|
||||
COPT_CACHE,
|
||||
COPT_TIMEOUT,
|
||||
COPT_STAT_TIMEOUT,
|
||||
COPT_DIR_TIMEOUT,
|
||||
COPT_LINK_TIMEOUT,
|
||||
COPT_LAST /* Last entry in this list! */
|
||||
};
|
||||
|
||||
static struct opt cache_opts[] = {
|
||||
[COPT_CACHE] = { .optname = "cache" },
|
||||
[COPT_TIMEOUT] = { .optname = "cache_timeout" },
|
||||
[COPT_STAT_TIMEOUT] = { .optname = "cache_stat_timeout" },
|
||||
[COPT_DIR_TIMEOUT] = { .optname = "cache_dir_timeout" },
|
||||
[COPT_LINK_TIMEOUT] = { .optname = "cache_link_timeout" },
|
||||
[COPT_LAST] = { .optname = NULL }
|
||||
};
|
||||
|
||||
static int get_timeout(int sel, unsigned *timeoutp)
|
||||
{
|
||||
struct opt *o = &cache_opts[sel];
|
||||
if (!o->present)
|
||||
return 0;
|
||||
if (opt_get_unsigned(o, timeoutp) == -1)
|
||||
return -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int cache_parse_options(int *argcp, char *argv[])
|
||||
{
|
||||
unsigned timeout;
|
||||
int res;
|
||||
process_options(argcp, argv, cache_opts, 1);
|
||||
if (cache_opts[COPT_CACHE].present) {
|
||||
char *val = cache_opts[COPT_CACHE].value;
|
||||
if (!val || !val[0] ||
|
||||
(strcmp(val, "yes") != 0 && strcmp(val, "no") != 0)) {
|
||||
fprintf(stderr, "Invalid or missing value for 'cache' option\n");
|
||||
return -1;
|
||||
}
|
||||
if (strcmp(val, "yes") == 0)
|
||||
cache_on = 1;
|
||||
else
|
||||
cache_on = 0;
|
||||
}
|
||||
if ((res = get_timeout(COPT_TIMEOUT, &timeout)) == -1)
|
||||
return -1;
|
||||
if (res == 1) {
|
||||
cache_stat_timeout = timeout;
|
||||
cache_dir_timeout = timeout;
|
||||
cache_link_timeout = timeout;
|
||||
}
|
||||
if(get_timeout(COPT_STAT_TIMEOUT, &cache_stat_timeout) == -1 ||
|
||||
get_timeout(COPT_DIR_TIMEOUT, &cache_dir_timeout) == -1 ||
|
||||
get_timeout(COPT_LINK_TIMEOUT, &cache_link_timeout) == -1)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
Caching file system proxy
|
||||
Copyright (C) 2004 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU GPL.
|
||||
See the file COPYING.
|
||||
*/
|
||||
|
||||
#include <fuse.h>
|
||||
|
||||
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);
|
||||
int cache_parse_options(int *argcp, char *argv[]);
|
||||
@@ -0,0 +1,12 @@
|
||||
AC_INIT(sshfs-fuse, 1.1)
|
||||
AM_INIT_AUTOMAKE
|
||||
AM_CONFIG_HEADER(config.h)
|
||||
|
||||
AC_PROG_CC
|
||||
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH
|
||||
PKG_CHECK_MODULES(SSHFS, [fuse >= 2.2 glib-2.0])
|
||||
CFLAGS="$CFLAGS -Wall -W -D_REENTRANT $SSHFS_CFLAGS"
|
||||
LIBS="$SSHFS_LIBS"
|
||||
|
||||
AC_CONFIG_FILES([Makefile])
|
||||
AC_OUTPUT
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
Mount option parsing
|
||||
Copyright (C) 2004 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU GPL.
|
||||
See the file COPYING.
|
||||
*/
|
||||
|
||||
#include "opts.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <glib.h>
|
||||
|
||||
static int process_option(char *arg, struct opt opts[], int case_sensitive)
|
||||
{
|
||||
int i;
|
||||
char *eq = strchr(arg, '=');
|
||||
if (eq)
|
||||
*eq = '\0';
|
||||
|
||||
for (i = 0; opts[i].optname != NULL; i++) {
|
||||
if (case_sensitive) {
|
||||
if (strcmp(opts[i].optname, arg) == 0)
|
||||
break;
|
||||
} else if (strcasecmp(opts[i].optname, arg) == 0)
|
||||
break;
|
||||
}
|
||||
if (opts[i].optname == NULL) {
|
||||
if (eq)
|
||||
*eq = '=';
|
||||
return 0;
|
||||
}
|
||||
opts[i].present = 1;
|
||||
if (eq) {
|
||||
if (opts[i].value)
|
||||
g_free(opts[i].value);
|
||||
opts[i].value = g_strdup(eq+1);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int process_option_group(char *arg, struct opt opts[],
|
||||
int case_sensitive)
|
||||
{
|
||||
int remove = 1;
|
||||
char *prevcomma = NULL;
|
||||
while (1) {
|
||||
int remove_one;
|
||||
char *comma = strchr(arg, ',');
|
||||
if (comma)
|
||||
*comma = '\0';
|
||||
remove_one = process_option(arg, opts, case_sensitive);
|
||||
if (remove_one) {
|
||||
if (comma)
|
||||
memmove(arg, comma + 1, strlen(comma + 1) + 1);
|
||||
} else {
|
||||
remove = 0;
|
||||
if (comma)
|
||||
arg = comma + 1;
|
||||
}
|
||||
if (!remove_one && prevcomma)
|
||||
*prevcomma = ',';
|
||||
if (!comma)
|
||||
break;
|
||||
prevcomma = comma;
|
||||
}
|
||||
return remove;
|
||||
}
|
||||
|
||||
void process_options(int *argcp, char *argv[], struct opt opts[],
|
||||
int case_sensitive)
|
||||
{
|
||||
int argctr;
|
||||
int newargctr;
|
||||
|
||||
for (argctr = 1, newargctr = 1; argctr < *argcp; argctr++) {
|
||||
char *arg = argv[argctr];
|
||||
int removed = 0;
|
||||
if (arg[0] == '-' && arg[1] == 'o') {
|
||||
if (arg[2])
|
||||
removed = process_option_group(arg+2, opts, case_sensitive);
|
||||
else {
|
||||
if (argctr + 1 < *argcp) {
|
||||
argctr++;
|
||||
arg = argv[argctr];
|
||||
removed = process_option_group(arg, opts, case_sensitive);
|
||||
if (removed)
|
||||
g_free(argv[argctr-1]);
|
||||
else if (argctr != newargctr)
|
||||
argv[newargctr++] = argv[argctr-1];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (removed)
|
||||
g_free(arg);
|
||||
else {
|
||||
if(argctr != newargctr)
|
||||
argv[newargctr] = arg;
|
||||
newargctr++;
|
||||
}
|
||||
}
|
||||
*argcp = newargctr;
|
||||
}
|
||||
|
||||
int opt_get_unsigned(const struct opt *o, unsigned *valp)
|
||||
{
|
||||
char *end;
|
||||
unsigned val;
|
||||
if (!o->value || !o->value[0]) {
|
||||
fprintf(stderr, "Missing value for '%s' option\n", o->optname);
|
||||
return -1;
|
||||
}
|
||||
val = strtoul(o->value, &end, 0);
|
||||
if (end[0]) {
|
||||
fprintf(stderr, "Invalid value for '%s' option\n", o->optname);
|
||||
return -1;
|
||||
}
|
||||
*valp = val;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
Mount option parsing
|
||||
Copyright (C) 2004 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU GPL.
|
||||
See the file COPYING.
|
||||
*/
|
||||
|
||||
struct opt {
|
||||
const char *optname;
|
||||
int present;
|
||||
char *value;
|
||||
};
|
||||
|
||||
void process_options(int *argcp, char *argv[], struct opt opts[],
|
||||
int case_sensitive);
|
||||
|
||||
int opt_get_unsigned(const struct opt *o, unsigned *valp);
|
||||
Reference in New Issue
Block a user