1024 lines
30 KiB
C
1024 lines
30 KiB
C
/*
|
|
FTP file system
|
|
Copyright (C) 2006 Robson Braga Araujo <robsonbraga@gmail.com>
|
|
|
|
This program can be distributed under the terms of the GNU GPL.
|
|
See the file COPYING.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <stdint.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <netinet/in.h>
|
|
#include <fuse.h>
|
|
#include <fuse_opt.h>
|
|
#include <glib.h>
|
|
|
|
#include "ftpfs-ls.h"
|
|
#include "cache.h"
|
|
#include "ftpfs.h"
|
|
|
|
#define CURLFTPFS_BAD_NOBODY 0x070f02
|
|
#define CURLFTPFS_BAD_SSL 0x070f03
|
|
|
|
struct ftpfs ftpfs;
|
|
static char error_buf[CURL_ERROR_SIZE];
|
|
|
|
struct buffer {
|
|
uint8_t* p;
|
|
size_t len;
|
|
size_t size;
|
|
};
|
|
|
|
static void usage(const char* progname);
|
|
static char* get_dir_path(const char* path, int strip);
|
|
|
|
static inline void buf_init(struct buffer* buf, size_t size)
|
|
{
|
|
if (size) {
|
|
buf->p = (uint8_t*) malloc(size);
|
|
if (!buf->p) {
|
|
fprintf(stderr, "ftpfs: memory allocation failed\n");
|
|
exit(1);
|
|
}
|
|
} else
|
|
buf->p = NULL;
|
|
buf->len = 0;
|
|
buf->size = size;
|
|
}
|
|
|
|
static inline void buf_free(struct buffer* buf)
|
|
{
|
|
free(buf->p);
|
|
}
|
|
|
|
static inline void buf_finish(struct buffer *buf)
|
|
{
|
|
buf->len = buf->size;
|
|
}
|
|
|
|
|
|
static inline void buf_clear(struct buffer *buf)
|
|
{
|
|
buf_free(buf);
|
|
buf_init(buf, 0);
|
|
}
|
|
|
|
static void buf_resize(struct buffer *buf, size_t len)
|
|
{
|
|
buf->size = (buf->len + len + 63) & ~31;
|
|
buf->p = (uint8_t *) realloc(buf->p, buf->size);
|
|
if (!buf->p) {
|
|
fprintf(stderr, "ftpfs: memory allocation failed\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static inline void buf_check_add(struct buffer *buf, size_t len)
|
|
{
|
|
if (buf->len + len > buf->size)
|
|
buf_resize(buf, len);
|
|
}
|
|
|
|
#define _buf_add_mem(b, d, l) \
|
|
buf_check_add(b, l); \
|
|
memcpy(b->p + b->len, d, l); \
|
|
b->len += l;
|
|
|
|
|
|
static inline void buf_add_mem(struct buffer *buf, const void *data,
|
|
size_t len)
|
|
{
|
|
_buf_add_mem(buf, data, len);
|
|
}
|
|
|
|
static inline void buf_add_buf(struct buffer *buf, const struct buffer *bufa)
|
|
{
|
|
_buf_add_mem(buf, bufa->p, bufa->len);
|
|
}
|
|
|
|
static inline void buf_add_uint8(struct buffer *buf, uint8_t val)
|
|
{
|
|
_buf_add_mem(buf, &val, 1);
|
|
}
|
|
|
|
static inline void buf_add_uint32(struct buffer *buf, uint32_t val)
|
|
{
|
|
uint32_t nval = htonl(val);
|
|
_buf_add_mem(buf, &nval, 4);
|
|
}
|
|
|
|
static inline void buf_add_uint64(struct buffer *buf, uint64_t val)
|
|
{
|
|
buf_add_uint32(buf, val >> 32);
|
|
buf_add_uint32(buf, val & 0xffffffff);
|
|
}
|
|
|
|
static inline void buf_add_data(struct buffer *buf, const struct buffer *data)
|
|
{
|
|
buf_add_uint32(buf, data->len);
|
|
buf_add_mem(buf, data->p, data->len);
|
|
}
|
|
|
|
static inline void buf_add_string(struct buffer *buf, const char *str)
|
|
{
|
|
struct buffer data;
|
|
data.p = (uint8_t *) str;
|
|
data.len = strlen(str);
|
|
buf_add_data(buf, &data);
|
|
}
|
|
|
|
static int buf_check_get(struct buffer *buf, size_t len)
|
|
{
|
|
if (buf->len + len > buf->size) {
|
|
fprintf(stderr, "buffer too short\n");
|
|
return -1;
|
|
} else
|
|
return 0;
|
|
}
|
|
|
|
static inline int buf_get_mem(struct buffer *buf, void *data, size_t len)
|
|
{
|
|
if (buf_check_get(buf, len) == -1)
|
|
return -1;
|
|
memcpy(data, buf->p + buf->len, len);
|
|
buf->len += len;
|
|
return 0;
|
|
}
|
|
|
|
static inline int buf_get_uint8(struct buffer *buf, uint8_t *val)
|
|
{
|
|
return buf_get_mem(buf, val, 1);
|
|
}
|
|
|
|
static inline int buf_get_uint32(struct buffer *buf, uint32_t *val)
|
|
{
|
|
uint32_t nval;
|
|
if (buf_get_mem(buf, &nval, 4) == -1)
|
|
return -1;
|
|
*val = ntohl(nval);
|
|
return 0;
|
|
}
|
|
|
|
static inline int buf_get_uint64(struct buffer *buf, uint64_t *val)
|
|
{
|
|
uint32_t val1;
|
|
uint32_t val2;
|
|
if (buf_get_uint32(buf, &val1) == -1 || buf_get_uint32(buf, &val2) == -1)
|
|
return -1;
|
|
*val = ((uint64_t) val1 << 32) + val2;
|
|
return 0;
|
|
}
|
|
|
|
static inline int buf_get_data(struct buffer *buf, struct buffer *data)
|
|
{
|
|
uint32_t len;
|
|
if (buf_get_uint32(buf, &len) == -1 || len > buf->size - buf->len)
|
|
return -1;
|
|
buf_init(data, len + 1);
|
|
data->size = len;
|
|
if (buf_get_mem(buf, data->p, data->size) == -1) {
|
|
buf_free(data);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline int buf_get_string(struct buffer *buf, char **str)
|
|
{
|
|
struct buffer data;
|
|
if (buf_get_data(buf, &data) == -1)
|
|
return -1;
|
|
data.p[data.size] = '\0';
|
|
*str = (char *) data.p;
|
|
return 0;
|
|
}
|
|
|
|
struct ftpfs_file {
|
|
struct buffer buf;
|
|
int dirty;
|
|
int copied;
|
|
};
|
|
|
|
enum {
|
|
KEY_HELP,
|
|
KEY_VERBOSE,
|
|
KEY_VERSION,
|
|
};
|
|
|
|
#define FTPFS_OPT(t, p, v) { t, offsetof(struct ftpfs, p), v }
|
|
|
|
static struct fuse_opt ftpfs_opts[] = {
|
|
FTPFS_OPT("ftpfs_debug", debug, 1),
|
|
FTPFS_OPT("transform_symlinks", transform_symlinks, 1),
|
|
FTPFS_OPT("disable_epsv", disable_epsv, 1),
|
|
FTPFS_OPT("skip_pasv_ip", skip_pasv_ip, 1),
|
|
FTPFS_OPT("ftp_port=%s", ftp_port, 0),
|
|
FTPFS_OPT("disable_eprt", disable_eprt, 1),
|
|
FTPFS_OPT("tcp_nodelay", tcp_nodelay, 1),
|
|
FTPFS_OPT("connect_timeout=%u", connect_timeout, 0),
|
|
FTPFS_OPT("ssl", use_ssl, CURLFTPSSL_ALL),
|
|
FTPFS_OPT("ssl_control", use_ssl, CURLFTPSSL_CONTROL),
|
|
FTPFS_OPT("ssl_try", use_ssl, CURLFTPSSL_TRY),
|
|
FTPFS_OPT("no_verify_hostname", no_verify_hostname, 1),
|
|
FTPFS_OPT("cert=%s", cert, 0),
|
|
FTPFS_OPT("cert_type=%s", cert_type, 0),
|
|
FTPFS_OPT("key=%s", key, 0),
|
|
FTPFS_OPT("key_type=%s", key_type, 0),
|
|
FTPFS_OPT("pass=%s", key_password, 0),
|
|
FTPFS_OPT("engine=%s", engine, 0),
|
|
FTPFS_OPT("cacert=%s", cacert, 0),
|
|
FTPFS_OPT("capath=%s", capath, 0),
|
|
FTPFS_OPT("ciphers=%s", ciphers, 0),
|
|
FTPFS_OPT("interface=%s", interface, 0),
|
|
FTPFS_OPT("krb4=%s", krb4, 0),
|
|
FTPFS_OPT("proxy=%s", proxy, 0),
|
|
FTPFS_OPT("proxytunnel", proxytunnel, 1),
|
|
FTPFS_OPT("proxy_anyauth", proxyanyauth, 1),
|
|
FTPFS_OPT("proxy_basic", proxybasic, 1),
|
|
FTPFS_OPT("proxy_digest", proxydigest, 1),
|
|
FTPFS_OPT("proxy_ntlm", proxyntlm, 1),
|
|
FTPFS_OPT("user=%s", user, 0),
|
|
FTPFS_OPT("proxy_user=%s", proxy_user, 0),
|
|
FTPFS_OPT("tlsv1", ssl_version, CURL_SSLVERSION_TLSv1),
|
|
FTPFS_OPT("sslv3", ssl_version, CURL_SSLVERSION_SSLv3),
|
|
FTPFS_OPT("ipv4", ip_version, CURL_IPRESOLVE_V4),
|
|
FTPFS_OPT("ipv6", ip_version, CURL_IPRESOLVE_V6),
|
|
|
|
FUSE_OPT_KEY("-h", KEY_HELP),
|
|
FUSE_OPT_KEY("--help", KEY_HELP),
|
|
FUSE_OPT_KEY("-v", KEY_VERBOSE),
|
|
FUSE_OPT_KEY("--verbose", KEY_VERBOSE),
|
|
FUSE_OPT_KEY("-V", KEY_VERSION),
|
|
FUSE_OPT_KEY("--version", KEY_VERSION),
|
|
FUSE_OPT_END
|
|
};
|
|
|
|
static size_t write_data(void *ptr, size_t size, size_t nmemb, void *data) {
|
|
struct ftpfs_file* fh = (struct ftpfs_file*)data;
|
|
if (fh == NULL) return 0;
|
|
unsigned to_copy = size * nmemb;
|
|
if (to_copy > fh->buf.len - fh->copied) {
|
|
to_copy = fh->buf.len - fh->copied;
|
|
}
|
|
DEBUG("write_data: %d\n", to_copy);
|
|
memcpy(ptr, fh->buf.p + fh->copied, to_copy);
|
|
fh->copied += to_copy;
|
|
return to_copy;
|
|
}
|
|
|
|
static size_t read_data(void *ptr, size_t size, size_t nmemb, void *data) {
|
|
struct buffer* buf = (struct buffer*)data;
|
|
if (buf == NULL) return size * nmemb;
|
|
buf_add_mem(buf, ptr, size * nmemb);
|
|
DEBUG("read_data: %d\n", size * nmemb);
|
|
return size * nmemb;
|
|
}
|
|
|
|
#define curl_easy_setopt_or_die(handle, option, ...) \
|
|
do {\
|
|
CURLcode res = curl_easy_setopt(handle, option, __VA_ARGS__);\
|
|
if (res != CURLE_OK) {\
|
|
fprintf(stderr, "Error setting curl: %s\n", error_buf);\
|
|
exit(1);\
|
|
}\
|
|
}while(0);
|
|
|
|
static int ftpfs_getdir(const char* path, fuse_cache_dirh_t h,
|
|
fuse_cache_dirfil_t filler) {
|
|
CURLcode curl_res;
|
|
char* dir_path = get_dir_path(path, 0);
|
|
|
|
DEBUG("ftpfs_getdir: %s\n", dir_path);
|
|
struct buffer buf;
|
|
buf_init(&buf, 0);
|
|
|
|
pthread_mutex_lock(&ftpfs.lock);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, dir_path);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_WRITEDATA, &buf);
|
|
curl_res = curl_easy_perform(ftpfs.connection);
|
|
pthread_mutex_unlock(&ftpfs.lock);
|
|
|
|
if (curl_res != 0) {
|
|
DEBUG("%s\n", error_buf);
|
|
}
|
|
buf_add_mem(&buf, "\0", 1);
|
|
|
|
parse_dir(buf.p, dir_path + strlen(ftpfs.host) - 1, NULL, NULL, NULL, 0, h, filler);
|
|
|
|
free(dir_path);
|
|
buf_free(&buf);
|
|
return 0;
|
|
}
|
|
|
|
static char* get_dir_path(const char* path, int strip) {
|
|
char *ret;
|
|
const char *lastdir;
|
|
|
|
++path;
|
|
|
|
if (strip) {
|
|
lastdir = strrchr(path, '/');
|
|
if (lastdir == NULL) lastdir = path;
|
|
} else {
|
|
lastdir = path + strlen(path);
|
|
}
|
|
|
|
ret = g_strdup_printf("%s%.*s%s", ftpfs.host, lastdir - path, path,
|
|
lastdir - path ? "/" : "");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ftpfs_getattr(const char* path, struct stat* sbuf) {
|
|
int err;
|
|
CURLcode curl_res;
|
|
char* dir_path = get_dir_path(path, 1);
|
|
|
|
DEBUG("dir_path: %s %s\n", path, dir_path);
|
|
struct buffer buf;
|
|
buf_init(&buf, 0);
|
|
|
|
pthread_mutex_lock(&ftpfs.lock);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, dir_path);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_WRITEDATA, &buf);
|
|
curl_res = curl_easy_perform(ftpfs.connection);
|
|
pthread_mutex_unlock(&ftpfs.lock);
|
|
|
|
if (curl_res != 0) {
|
|
DEBUG("%s\n", error_buf);
|
|
}
|
|
buf_add_mem(&buf, "\0", 1);
|
|
|
|
char* name = strrchr(path, '/');
|
|
++name;
|
|
err = parse_dir(buf.p, dir_path + strlen(ftpfs.host) - 1, name, sbuf, NULL, 0, NULL, NULL);
|
|
|
|
free(dir_path);
|
|
buf_free(&buf);
|
|
if (err) return -ENOENT;
|
|
return 0;
|
|
}
|
|
|
|
static int ftpfs_open(const char* path, struct fuse_file_info* fi) {
|
|
DEBUG("%d\n", fi->flags & O_ACCMODE);
|
|
if ((fi->flags & O_ACCMODE) == O_RDONLY) {
|
|
DEBUG("opening %s O_RDONLY\n", path);
|
|
} else if ((fi->flags & O_ACCMODE) == O_WRONLY) {
|
|
DEBUG("opening %s O_WRONLY\n", path);
|
|
} else if ((fi->flags & O_ACCMODE) == O_RDWR) {
|
|
DEBUG("opening %s O_RDWR\n", path);
|
|
}
|
|
|
|
char *full_path = g_strdup_printf("%s%s", ftpfs.host, path + 1);
|
|
|
|
DEBUG("full_path: %s\n", full_path);
|
|
struct buffer buf;
|
|
int err = 0;
|
|
buf_init(&buf, 0);
|
|
|
|
pthread_mutex_lock(&ftpfs.lock);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, full_path);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_WRITEDATA, &buf);
|
|
CURLcode curl_res = curl_easy_perform(ftpfs.connection);
|
|
if (curl_res != 0) {
|
|
err = -EACCES;
|
|
buf_free(&buf);
|
|
} else {
|
|
struct ftpfs_file* fh = (struct ftpfs_file*)
|
|
malloc(sizeof(struct ftpfs_file));
|
|
fh->buf = buf;
|
|
fh->dirty = 0;
|
|
fh->copied = 0;
|
|
fi->fh = (unsigned long) fh;
|
|
}
|
|
pthread_mutex_unlock(&ftpfs.lock);
|
|
|
|
free(full_path);
|
|
return err;
|
|
}
|
|
|
|
static int ftpfs_read(const char* path, char* rbuf, size_t size, off_t offset,
|
|
struct fuse_file_info* fi) {
|
|
(void) path;
|
|
struct ftpfs_file* fh = (struct ftpfs_file*) (uintptr_t) fi->fh;
|
|
if (offset >= fh->buf.len) return 0;
|
|
if (size > fh->buf.len - offset) {
|
|
size = fh->buf.len - offset;
|
|
}
|
|
memcpy(rbuf, fh->buf.p + offset, size);
|
|
|
|
return size;
|
|
}
|
|
|
|
static int ftpfs_mknod(const char* path, mode_t mode, dev_t rdev) {
|
|
(void) rdev;
|
|
|
|
int err = 0;
|
|
|
|
if ((mode & S_IFMT) != S_IFREG)
|
|
return -EPERM;
|
|
|
|
char *full_path = g_strdup_printf("%s%s", ftpfs.host, path + 1);
|
|
|
|
pthread_mutex_lock(&ftpfs.lock);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, full_path);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_INFILESIZE, 0);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_UPLOAD, 1);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_READDATA, NULL);
|
|
CURLcode curl_res = curl_easy_perform(ftpfs.connection);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_UPLOAD, 0);
|
|
pthread_mutex_unlock(&ftpfs.lock);
|
|
|
|
if (curl_res != 0) {
|
|
err = -EPERM;
|
|
}
|
|
|
|
free(full_path);
|
|
return err;
|
|
}
|
|
|
|
static int ftpfs_chmod(const char* path, mode_t mode) {
|
|
(void) path;
|
|
(void) mode;
|
|
return 0;
|
|
}
|
|
|
|
static int ftpfs_chown(const char* path, uid_t uid, gid_t gid) {
|
|
(void) path;
|
|
(void) uid;
|
|
(void) gid;
|
|
return 0;
|
|
}
|
|
|
|
static int ftpfs_truncate(const char* path, off_t offset) {
|
|
DEBUG("ftpfs_truncate: %lld\n", offset);
|
|
if (offset == 0) return ftpfs_mknod(path, S_IFREG, 0);
|
|
return 0;
|
|
}
|
|
|
|
static int ftpfs_utime(const char* path, struct utimbuf* time) {
|
|
(void) path;
|
|
(void) time;
|
|
return 0;
|
|
}
|
|
|
|
static int ftpfs_rmdir(const char* path) {
|
|
int err = 0;
|
|
struct curl_slist* header = NULL;
|
|
char* full_path = get_dir_path(path, 1);
|
|
char* cmd = g_strdup_printf("RMD %s", strrchr(path, '/') + 1);
|
|
struct buffer buf;
|
|
buf_init(&buf, 0);
|
|
|
|
DEBUG("%s\n", full_path);
|
|
DEBUG("%s\n", cmd);
|
|
|
|
header = curl_slist_append(header, cmd);
|
|
|
|
pthread_mutex_lock(&ftpfs.lock);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_POSTQUOTE, header);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, full_path);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_WRITEDATA, &buf);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_NOBODY, ftpfs.safe_nobody);
|
|
CURLcode curl_res = curl_easy_perform(ftpfs.connection);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_POSTQUOTE, NULL);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_NOBODY, 0);
|
|
pthread_mutex_unlock(&ftpfs.lock);
|
|
|
|
if (curl_res != 0) {
|
|
err = -EPERM;
|
|
}
|
|
|
|
buf_free(&buf);
|
|
curl_slist_free_all(header);
|
|
free(full_path);
|
|
free(cmd);
|
|
return err;
|
|
}
|
|
|
|
static int ftpfs_mkdir(const char* path, mode_t mode) {
|
|
(void) mode;
|
|
int err = 0;
|
|
struct curl_slist* header = NULL;
|
|
char* full_path = get_dir_path(path, 1);
|
|
char* cmd = g_strdup_printf("MKD %s", strrchr(path, '/') + 1);
|
|
struct buffer buf;
|
|
buf_init(&buf, 0);
|
|
|
|
header = curl_slist_append(header, cmd);
|
|
|
|
pthread_mutex_lock(&ftpfs.lock);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_POSTQUOTE, header);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, full_path);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_WRITEDATA, &buf);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_NOBODY, ftpfs.safe_nobody);
|
|
CURLcode curl_res = curl_easy_perform(ftpfs.connection);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_POSTQUOTE, NULL);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_NOBODY, 0);
|
|
pthread_mutex_unlock(&ftpfs.lock);
|
|
|
|
if (curl_res != 0) {
|
|
err = -EPERM;
|
|
}
|
|
|
|
buf_free(&buf);
|
|
curl_slist_free_all(header);
|
|
free(full_path);
|
|
free(cmd);
|
|
return err;
|
|
}
|
|
|
|
static int ftpfs_unlink(const char* path) {
|
|
int err = 0;
|
|
struct curl_slist* header = NULL;
|
|
char* full_path = get_dir_path(path, 1);
|
|
char* cmd = g_strdup_printf("DELE %s", strrchr(path, '/') + 1);
|
|
struct buffer buf;
|
|
buf_init(&buf, 0);
|
|
|
|
header = curl_slist_append(header, cmd);
|
|
|
|
pthread_mutex_lock(&ftpfs.lock);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_POSTQUOTE, header);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, full_path);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_WRITEDATA, &buf);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_NOBODY, ftpfs.safe_nobody);
|
|
CURLcode curl_res = curl_easy_perform(ftpfs.connection);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_POSTQUOTE, NULL);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_NOBODY, 0);
|
|
pthread_mutex_unlock(&ftpfs.lock);
|
|
|
|
if (curl_res != 0) {
|
|
err = -EPERM;
|
|
}
|
|
|
|
buf_free(&buf);
|
|
curl_slist_free_all(header);
|
|
free(full_path);
|
|
free(cmd);
|
|
return err;
|
|
}
|
|
|
|
static int ftpfs_write(const char *path, const char *wbuf, size_t size,
|
|
off_t offset, struct fuse_file_info *fi) {
|
|
(void) path;
|
|
struct ftpfs_file* fh = (struct ftpfs_file*) (uintptr_t) fi->fh;
|
|
DEBUG("ftpfs_write: %d %lld\n", size, offset);
|
|
if (offset + size > fh->buf.size) {
|
|
buf_resize(&fh->buf, offset + size);
|
|
}
|
|
while (fh->buf.len < offset + size) {
|
|
buf_add_mem(&fh->buf, "\0", 1);
|
|
}
|
|
memcpy(fh->buf.p + offset, wbuf, size);
|
|
fh->dirty = 1;
|
|
|
|
return size;
|
|
}
|
|
|
|
static int ftpfs_flush(const char *path, struct fuse_file_info *fi) {
|
|
struct ftpfs_file* fh = (struct ftpfs_file*) (uintptr_t) fi->fh;
|
|
if (!fh->dirty) return 0;
|
|
|
|
int err = 0;
|
|
DEBUG("ftpfs_flush: %d\n", fh->buf.len);
|
|
char* full_path = g_strdup_printf("%s%s", ftpfs.host, path + 1);
|
|
|
|
pthread_mutex_lock(&ftpfs.lock);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, full_path);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_INFILESIZE, fh->buf.len);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_UPLOAD, 1);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_READDATA, fh);
|
|
CURLcode curl_res = curl_easy_perform(ftpfs.connection);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_UPLOAD, 0);
|
|
pthread_mutex_unlock(&ftpfs.lock);
|
|
|
|
if (curl_res != 0) {
|
|
err = -EPERM;
|
|
}
|
|
|
|
fh->dirty = 0;
|
|
|
|
free(full_path);
|
|
return err;
|
|
}
|
|
|
|
static int ftpfs_fsync(const char *path, int isdatasync,
|
|
struct fuse_file_info *fi) {
|
|
(void) isdatasync;
|
|
return ftpfs_flush(path, fi);
|
|
}
|
|
|
|
static int ftpfs_release(const char* path, struct fuse_file_info* fi) {
|
|
struct ftpfs_file* fh = (struct ftpfs_file*) (uintptr_t) fi->fh;
|
|
ftpfs_flush(path, fi);
|
|
buf_free(&fh->buf);
|
|
free(fh);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int ftpfs_rename(const char* from, const char* to) {
|
|
int err = 0;
|
|
char* rnfr = g_strdup_printf("RNFR %s", from + 1);
|
|
char* rnto = g_strdup_printf("RNTO %s", to + 1);
|
|
struct buffer buf;
|
|
buf_init(&buf, 0);
|
|
struct curl_slist* header = NULL;
|
|
header = curl_slist_append(header, rnfr);
|
|
header = curl_slist_append(header, rnto);
|
|
|
|
pthread_mutex_lock(&ftpfs.lock);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_POSTQUOTE, header);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, ftpfs.host);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_WRITEDATA, &buf);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_NOBODY, ftpfs.safe_nobody);
|
|
CURLcode curl_res = curl_easy_perform(ftpfs.connection);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_POSTQUOTE, NULL);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_NOBODY, 0);
|
|
pthread_mutex_unlock(&ftpfs.lock);
|
|
|
|
if (curl_res != 0) {
|
|
err = -EPERM;
|
|
}
|
|
|
|
buf_free(&buf);
|
|
curl_slist_free_all(header);
|
|
free(rnfr);
|
|
free(rnto);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int ftpfs_readlink(const char *path, char *linkbuf, size_t size) {
|
|
int err;
|
|
CURLcode curl_res;
|
|
char* dir_path = get_dir_path(path, 1);
|
|
|
|
DEBUG("dir_path: %s %s\n", path, dir_path);
|
|
struct buffer buf;
|
|
buf_init(&buf, 0);
|
|
|
|
pthread_mutex_lock(&ftpfs.lock);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, dir_path);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_WRITEDATA, &buf);
|
|
curl_res = curl_easy_perform(ftpfs.connection);
|
|
pthread_mutex_unlock(&ftpfs.lock);
|
|
|
|
if (curl_res != 0) {
|
|
DEBUG("%s\n", error_buf);
|
|
}
|
|
buf_add_mem(&buf, "\0", 1);
|
|
|
|
char* name = strrchr(path, '/');
|
|
++name;
|
|
err = parse_dir(buf.p, dir_path + strlen(ftpfs.host) - 1, name, NULL, linkbuf, size, NULL, NULL);
|
|
|
|
free(dir_path);
|
|
buf_free(&buf);
|
|
if (err) return -ENOENT;
|
|
return 0;
|
|
}
|
|
|
|
#if FUSE_VERSION >= 25
|
|
static int ftpfs_statfs(const char *path, struct statvfs *buf)
|
|
{
|
|
(void) path;
|
|
|
|
buf->f_namemax = 255;
|
|
buf->f_bsize = ftpfs.blksize;
|
|
buf->f_frsize = 512;
|
|
buf->f_blocks = 999999999 * 2;
|
|
buf->f_bfree = 999999999 * 2;
|
|
buf->f_bavail = 999999999 * 2;
|
|
buf->f_files = 999999999;
|
|
buf->f_ffree = 999999999;
|
|
return 0;
|
|
}
|
|
#else
|
|
static int ftpfs_statfs(const char *path, struct statfs *buf)
|
|
{
|
|
(void) path;
|
|
|
|
buf->f_namelen = 255;
|
|
buf->f_bsize = ftpfs.blksize;
|
|
buf->f_blocks = 999999999 * 2;
|
|
buf->f_bfree = 999999999 * 2;
|
|
buf->f_bavail = 999999999 * 2;
|
|
buf->f_files = 999999999;
|
|
buf->f_ffree = 999999999;
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static struct fuse_cache_operations ftpfs_oper = {
|
|
.oper = {
|
|
#ifdef SSHFS_USE_INIT
|
|
// .init = ftpfs_init,
|
|
#endif
|
|
.getattr = ftpfs_getattr,
|
|
.readlink = ftpfs_readlink,
|
|
.mknod = ftpfs_mknod,
|
|
.mkdir = ftpfs_mkdir,
|
|
// .symlink = ftpfs_symlink,
|
|
.unlink = ftpfs_unlink,
|
|
.rmdir = ftpfs_rmdir,
|
|
.rename = ftpfs_rename,
|
|
.chmod = ftpfs_chmod,
|
|
.chown = ftpfs_chown,
|
|
.truncate = ftpfs_truncate,
|
|
.utime = ftpfs_utime,
|
|
.open = ftpfs_open,
|
|
.flush = ftpfs_flush,
|
|
.fsync = ftpfs_fsync,
|
|
.release = ftpfs_release,
|
|
.read = ftpfs_read,
|
|
.write = ftpfs_write,
|
|
.statfs = ftpfs_statfs,
|
|
#if FUSE_VERSION >= 25
|
|
// .create = ftpfs_create,
|
|
// .ftruncate = ftpfs_ftruncate,
|
|
// .fgetattr = ftpfs_fgetattr,
|
|
#endif
|
|
},
|
|
.cache_getdir = ftpfs_getdir,
|
|
};
|
|
|
|
static int ftpfs_opt_proc(void* data, const char* arg, int key,
|
|
struct fuse_args* outargs) {
|
|
(void) data;
|
|
(void) outargs;
|
|
|
|
switch (key) {
|
|
case FUSE_OPT_KEY_OPT:
|
|
return 1;
|
|
case FUSE_OPT_KEY_NONOPT:
|
|
if (!ftpfs.host) {
|
|
const char* prefix = "";
|
|
if (strncmp(arg, "ftp://", 6)) {
|
|
prefix = "ftp://";
|
|
}
|
|
ftpfs.host = g_strdup_printf("%s%s%s", prefix, arg,
|
|
arg[strlen(arg)-1] == '/' ? "" : "/");
|
|
return 0;
|
|
} else if (!ftpfs.mountpoint)
|
|
ftpfs.mountpoint = strdup(arg);
|
|
return 1;
|
|
case KEY_HELP:
|
|
usage(outargs->argv[0]);
|
|
fuse_opt_add_arg(outargs, "-ho");
|
|
fuse_main(outargs->argc, outargs->argv, &ftpfs_oper.oper);
|
|
exit(1);
|
|
case KEY_VERBOSE:
|
|
ftpfs.verbose = 1;
|
|
return 0;
|
|
case KEY_VERSION:
|
|
fprintf(stderr, "curlftpfs %s libcurl/%s fuse/%u.%u\n",
|
|
VERSION,
|
|
ftpfs.curl_version->version,
|
|
FUSE_MAJOR_VERSION,
|
|
FUSE_MINOR_VERSION);
|
|
exit(1);
|
|
default:
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static void usage(const char* progname) {
|
|
fprintf(stderr,
|
|
"usage: %s <ftphost> <mountpoint>\n"
|
|
"\n"
|
|
"CurlFtpFS options:\n"
|
|
" -o opt,[opt...] ftp options\n"
|
|
" -v --verbose make libcurl print verbose debug\n"
|
|
" -h --help print help\n"
|
|
" -V --version print version\n"
|
|
"\n"
|
|
"FTP options:\n"
|
|
" ftpfs_debug print some debugging information\n"
|
|
" transform_symlinks prepend mountpoint to absolute symlink targets\n"
|
|
" disable_epsv use PASV, without trying EPSV first\n"
|
|
" skip_pasv_ip skip the IP address for PASV\n"
|
|
" ftp_port=STR use PORT with address instead of PASV\n"
|
|
" disable_eprt use PORT, without trying EPRT first\n"
|
|
" tcp_nodelay use the TCP_NODELAY option\n"
|
|
" connect_timeout=N maximum time allowed for connection in seconds\n"
|
|
" ssl enable SSL/TLS for both control and data connections\n"
|
|
" ssl_control enable SSL/TLS only for control connection\n"
|
|
" ssl_try try SSL/TLS first but connect anyway\n"
|
|
" no_verify_hostname does not verify the hostname (SSL)\n"
|
|
" cert=STR client certificate file (SSL)\n"
|
|
" cert_type=STR certificate file type (DER/PEM/ENG) (SSL)\n"
|
|
" key=STR private key file name (SSL)\n"
|
|
" key_type=STR private key file type (DER/PEM/ENG) (SSL)\n"
|
|
" pass=STR pass phrase for the private key (SSL)\n"
|
|
" engine=STR crypto engine to use (SSL)\n"
|
|
" cacert=STR file with CA certificates to verify the peer (SSL)\n"
|
|
" capath=STR CA directory to verify peer against (SSL)\n"
|
|
" ciphers=STR SSL ciphers to use (SSL)\n"
|
|
" interface=STR specify network interface/address to use\n"
|
|
" krb4=STR enable krb4 with specified security level\n"
|
|
" proxy=STR use host:port HTTP proxy\n"
|
|
" proxytunnel operate through a HTTP proxy tunnel (using CONNECT)\n"
|
|
" proxy_anyauth pick \"any\" proxy authentication method\n"
|
|
" proxy_basic use Basic authentication on the proxy\n"
|
|
" proxy_digest use Digest authentication on the proxy\n"
|
|
" proxy_ntlm use NTLM authentication on the proxy\n"
|
|
" user=STR set server user and password\n"
|
|
" proxy_user=STR set proxy user and password\n"
|
|
" tlsv1 use TLSv1 (SSL)\n"
|
|
" sslv3 use SSLv3 (SSL)\n"
|
|
" ipv4 resolve name to IPv4 address\n"
|
|
" ipv6 resolve name to IPv6 address\n"
|
|
"\n", progname);
|
|
}
|
|
|
|
static void set_common_curl_stuff() {
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_WRITEFUNCTION, read_data);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_READFUNCTION, write_data);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_ERRORBUFFER, error_buf);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, ftpfs.host);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
|
|
|
|
if (ftpfs.verbose) {
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_VERBOSE, TRUE);
|
|
}
|
|
|
|
if (ftpfs.disable_epsv) {
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_FTP_USE_EPSV, FALSE);
|
|
}
|
|
|
|
if (ftpfs.skip_pasv_ip) {
|
|
#ifdef CURLOPT_FTP_SKIP_PASV_IP
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_FTP_SKIP_PASV_IP, TRUE);
|
|
#endif
|
|
}
|
|
|
|
if (ftpfs.ftp_port) {
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_FTPPORT, ftpfs.ftp_port);
|
|
}
|
|
|
|
if (ftpfs.disable_eprt) {
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_FTP_USE_EPRT, FALSE);
|
|
}
|
|
|
|
if (ftpfs.tcp_nodelay) {
|
|
#ifdef CURLOPT_TCP_NODELAY
|
|
/* CURLOPT_TCP_NODELAY is not defined in older versions */
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_TCP_NODELAY, 1);
|
|
#endif
|
|
}
|
|
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_CONNECTTIMEOUT, ftpfs.connect_timeout);
|
|
|
|
/* CURLFTPSSL_CONTROL and CURLFTPSSL_ALL should make the connection fail if
|
|
* the server doesn't support SSL but libcurl only honors this beginning
|
|
* with version 7.15.4 */
|
|
if (ftpfs.use_ssl > CURLFTPSSL_TRY &&
|
|
ftpfs.curl_version->version_num <= CURLFTPFS_BAD_SSL) {
|
|
fprintf(stderr,
|
|
"WARNING: you are using libcurl %s.\n"
|
|
"This version of libcurl does not respect the mandatory SSL flag.\n"
|
|
"It will try to send the user and password even if the server doesn't support\n"
|
|
"SSL. Please upgrade to libcurl version 7.15.4 or higher.\n"
|
|
"You can abort the connection now by pressing ctrl+c.\n",
|
|
ftpfs.curl_version->version);
|
|
int i;
|
|
const int time_to_wait = 10;
|
|
for (i = 0; i < time_to_wait; i++) {
|
|
fprintf(stderr, "%d.. ", time_to_wait - i);
|
|
sleep(1);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
}
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_FTP_SSL, ftpfs.use_ssl);
|
|
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_SSLCERT, ftpfs.cert);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_SSLCERTTYPE, ftpfs.cert_type);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_SSLKEY, ftpfs.key);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_SSLKEYTYPE, ftpfs.key_type);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_SSLKEYPASSWD, ftpfs.key_password);
|
|
|
|
if (ftpfs.engine) {
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_SSLENGINE, ftpfs.engine);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_SSLENGINE_DEFAULT, 1);
|
|
}
|
|
|
|
if (ftpfs.cacert || ftpfs.capath) {
|
|
if (ftpfs.cacert) {
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_CAINFO, ftpfs.cacert);
|
|
}
|
|
if (ftpfs.capath) {
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_CAPATH, ftpfs.capath);
|
|
}
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_SSL_VERIFYPEER, TRUE);
|
|
}
|
|
|
|
if (ftpfs.ciphers) {
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_SSL_CIPHER_LIST, ftpfs.ciphers);
|
|
}
|
|
|
|
if (ftpfs.no_verify_hostname) {
|
|
/* The default is 2 which verifies even the host string. This sets to 1
|
|
* which means verify the host but not the string. */
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_SSL_VERIFYHOST, 1);
|
|
}
|
|
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_INTERFACE, ftpfs.interface);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_KRB4LEVEL, ftpfs.krb4);
|
|
|
|
if (ftpfs.proxy) {
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_PROXY, ftpfs.proxy);
|
|
/* Connection to FTP servers only make sense with a tunnel proxy */
|
|
}
|
|
if (ftpfs.proxy || ftpfs.proxytunnel) {
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_HTTPPROXYTUNNEL, TRUE);
|
|
}
|
|
|
|
if (ftpfs.proxyanyauth) {
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
|
|
} else if (ftpfs.proxyntlm) {
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_PROXYAUTH, CURLAUTH_NTLM);
|
|
} else if (ftpfs.proxydigest) {
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_PROXYAUTH, CURLAUTH_DIGEST);
|
|
} else if (ftpfs.proxybasic) {
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
|
|
}
|
|
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_USERPWD, ftpfs.user);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_PROXYUSERPWD, ftpfs.proxy_user);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_SSLVERSION, ftpfs.ssl_version);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_IPRESOLVE, ftpfs.ip_version);
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
int res;
|
|
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
|
|
CURLcode curl_res;
|
|
|
|
memset(&ftpfs, 0, sizeof(ftpfs));
|
|
|
|
ftpfs.curl_version = curl_version_info(CURLVERSION_NOW);
|
|
ftpfs.safe_nobody = ftpfs.curl_version->version_num > CURLFTPFS_BAD_NOBODY;
|
|
|
|
ftpfs.blksize = 4096;
|
|
|
|
if (fuse_opt_parse(&args, &ftpfs, ftpfs_opts, ftpfs_opt_proc) == -1)
|
|
exit(1);
|
|
|
|
if (!ftpfs.host) {
|
|
fprintf(stderr, "missing host\n");
|
|
fprintf(stderr, "see `%s -h' for usage\n", argv[0]);
|
|
exit(1);
|
|
}
|
|
|
|
ftpfs.connection = curl_easy_init();
|
|
if (ftpfs.connection == NULL) {
|
|
fprintf(stderr, "Error initializing libcurl\n");
|
|
exit(1);
|
|
}
|
|
|
|
res = cache_parse_options(&args);
|
|
if (res == -1)
|
|
exit(1);
|
|
|
|
if (ftpfs.transform_symlinks && !ftpfs.mountpoint) {
|
|
fprintf(stderr, "cannot transform symlinks: no mountpoint given\n");
|
|
exit(1);
|
|
}
|
|
if (!ftpfs.transform_symlinks)
|
|
ftpfs.symlink_prefix_len = 0;
|
|
else if (realpath(ftpfs.mountpoint, ftpfs.symlink_prefix) != NULL)
|
|
ftpfs.symlink_prefix_len = strlen(ftpfs.symlink_prefix);
|
|
else {
|
|
perror("unable to normalize mount path");
|
|
exit(1);
|
|
}
|
|
|
|
set_common_curl_stuff();
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_WRITEDATA, NULL);
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_NOBODY, ftpfs.safe_nobody);
|
|
curl_res = curl_easy_perform(ftpfs.connection);
|
|
if (curl_res != 0) {
|
|
fprintf(stderr, "Error connecting to ftp: %s\n", error_buf);
|
|
exit(1);
|
|
}
|
|
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_NOBODY, 0);
|
|
|
|
pthread_mutex_init(&ftpfs.lock, NULL);
|
|
|
|
res = fuse_main(args.argc, args.argv, cache_init(&ftpfs_oper));
|
|
|
|
curl_easy_cleanup(ftpfs.connection);
|
|
|
|
return res;
|
|
}
|