Files
hubicfuse/cloudfuse.c
T

655 lines
16 KiB
C

#define FUSE_USE_VERSION 26
#include <fuse.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <pthread.h>
#include <pwd.h>
#include <fcntl.h>
#include <signal.h>
#include <stdint.h>
#include <stddef.h>
#include "cloudfsapi.h"
#include "config.h"
static int cache_timeout;
static char *temp_dir;
typedef struct dir_cache
{
char *path;
dir_entry *entries;
time_t cached;
struct dir_cache *next, *prev;
} dir_cache;
static dir_cache *dcache;
static pthread_mutex_t dmut;
typedef struct
{
int fd;
int flags;
} openfile;
static void dir_for(const char *path, char *dir)
{
strncpy(dir, path, MAX_PATH_SIZE);
char *slash = strrchr(dir, '/');
if (slash)
*slash = '\0';
}
static dir_cache *new_cache(const char *path)
{
dir_cache *cw = (dir_cache *)calloc(sizeof(dir_cache), 1);
cw->path = strdup(path);
cw->prev = NULL;
cw->entries = NULL;
cw->cached = time(NULL);
if (dcache)
dcache->prev = cw;
cw->next = dcache;
return (dcache = cw);
}
static int caching_list_directory(const char *path, dir_entry **list)
{
pthread_mutex_lock(&dmut);
if (!strcmp(path, "/"))
path = "";
dir_cache *cw;
for (cw = dcache; cw; cw = cw->next)
if (!strcmp(cw->path, path))
break;
if (!cw)
{
if (!cloudfs_list_directory(path, list))
return 0;
cw = new_cache(path);
}
else if (cache_timeout > 0 && (time(NULL) - cw->cached > cache_timeout))
{
if (!cloudfs_list_directory(path, list))
return 0;
cloudfs_free_dir_list(cw->entries);
cw->cached = time(NULL);
}
else
*list = cw->entries;
cw->entries = *list;
pthread_mutex_unlock(&dmut);
return 1;
}
static void update_dir_cache(const char *path, off_t size, int isdir, int islink)
{
pthread_mutex_lock(&dmut);
dir_cache *cw;
dir_entry *de;
char dir[MAX_PATH_SIZE];
dir_for(path, dir);
for (cw = dcache; cw; cw = cw->next)
{
if (!strcmp(cw->path, dir))
{
for (de = cw->entries; de; de = de->next)
{
if (!strcmp(de->full_name, path))
{
de->size = size;
pthread_mutex_unlock(&dmut);
return;
}
}
de = (dir_entry *)malloc(sizeof(dir_entry));
de->size = size;
de->isdir = isdir;
de->islink = islink;
de->name = strdup(&path[strlen(cw->path)+1]);
de->full_name = strdup(path);
if (isdir)
{
de->content_type = strdup("application/link");
}
if(islink)
{
de->content_type = strdup("application/directory");
}
else
{
de->content_type = strdup("application/octet-stream");
}
de->last_modified = time(NULL);
de->next = cw->entries;
cw->entries = de;
if (isdir)
new_cache(path);
break;
}
}
pthread_mutex_unlock(&dmut);
}
static void dir_decache(const char *path)
{
dir_cache *cw;
pthread_mutex_lock(&dmut);
dir_entry *de, *tmpde;
char dir[MAX_PATH_SIZE];
dir_for(path, dir);
for (cw = dcache; cw; cw = cw->next)
{
if (!strcmp(cw->path, path))
{
if (cw == dcache)
dcache = cw->next;
if (cw->prev)
cw->prev->next = cw->next;
if (cw->next)
cw->next->prev = cw->prev;
cloudfs_free_dir_list(cw->entries);
free(cw->path);
free(cw);
}
else if (cw->entries && !strcmp(dir, cw->path))
{
if (!strcmp(cw->entries->full_name, path))
{
de = cw->entries;
cw->entries = de->next;
de->next = NULL;
cloudfs_free_dir_list(de);
}
else for (de = cw->entries; de->next; de = de->next)
{
if (!strcmp(de->next->full_name, path))
{
tmpde = de->next;
de->next = de->next->next;
tmpde->next = NULL;
cloudfs_free_dir_list(tmpde);
break;
}
}
}
}
pthread_mutex_unlock(&dmut);
}
static dir_entry *path_info(const char *path)
{
char dir[MAX_PATH_SIZE];
dir_for(path, dir);
dir_entry *tmp;
if (!caching_list_directory(dir, &tmp))
return NULL;
for (; tmp; tmp = tmp->next)
{
if (!strcmp(tmp->full_name, path))
return tmp;
}
return NULL;
}
static int cfs_getattr(const char *path, struct stat *stbuf)
{
stbuf->st_uid = geteuid();
stbuf->st_gid = getegid();
if (!strcmp(path, "/"))
{
stbuf->st_mode = S_IFDIR | 0755;
stbuf->st_nlink = 2;
return 0;
}
dir_entry *de = path_info(path);
if (!de)
return -ENOENT;
stbuf->st_ctime = stbuf->st_mtime = de->last_modified;
if (de->isdir)
{
stbuf->st_size = 0;
stbuf->st_mode = S_IFDIR | 0755;
stbuf->st_nlink = 2;
}
else if (de->islink)
{
stbuf->st_size = 1;
stbuf->st_mode = S_IFLNK | 0755;
stbuf->st_nlink = 1;
stbuf->st_size = de->size;
/* calc. blocks as if 4K blocksize filesystem; stat uses units of 512B */
stbuf->st_blocks = ((4095 + de->size) / 4096) * 8;
}
else
{
stbuf->st_size = de->size;
/* calc. blocks as if 4K blocksize filesystem; stat uses units of 512B */
stbuf->st_blocks = ((4095 + de->size) / 4096) * 8;
stbuf->st_mode = S_IFREG | 0666;
stbuf->st_nlink = 1;
}
return 0;
}
static int cfs_fgetattr(const char *path, struct stat *stbuf, struct fuse_file_info *info)
{
openfile *of = (openfile *)(uintptr_t)info->fh;
if (of)
{
stbuf->st_size = cloudfs_file_size(of->fd);
stbuf->st_mode = S_IFREG | 0666;
stbuf->st_nlink = 1;
return 0;
}
return -ENOENT;
}
static int cfs_readdir(const char *path, void *buf, fuse_fill_dir_t filldir, off_t offset, struct fuse_file_info *info)
{
dir_entry *de;
if (!caching_list_directory(path, &de))
return -ENOLINK;
filldir(buf, ".", NULL, 0);
filldir(buf, "..", NULL, 0);
for (; de; de = de->next)
filldir(buf, de->name, NULL, 0);
return 0;
}
static int cfs_mkdir(const char *path, mode_t mode)
{
if (cloudfs_create_directory(path))
{
update_dir_cache(path, 0, 1, 0);
return 0;
}
return -ENOENT;
}
static int cfs_create(const char *path, mode_t mode, struct fuse_file_info *info)
{
FILE *temp_file = tmpfile();
openfile *of = (openfile *)malloc(sizeof(openfile));
of->fd = dup(fileno(temp_file));
fclose(temp_file);
of->flags = info->flags;
info->fh = (uintptr_t)of;
update_dir_cache(path, 0, 0, 0);
info->direct_io = 1;
return 0;
}
static int cfs_open(const char *path, struct fuse_file_info *info)
{
FILE *temp_file;
dir_entry *de = path_info(path);
if (*temp_dir)
{
char tmp_path[PATH_MAX];
strncpy(tmp_path, path, PATH_MAX);
char *pch;
while((pch = strchr(tmp_path, '/'))) {
*pch = '.';
}
char file_path[PATH_MAX];
snprintf(file_path, PATH_MAX, "%s/.cloudfuse%ld-%s", temp_dir,
(long)getpid(), tmp_path);
if(access(file_path, F_OK) != -1)
{
// file exists
temp_file = fopen(file_path, "r");
}
else if (!(info->flags & O_WRONLY))
{
// we need to lock on the filename another process could open the file
// while we are writing to it and then only read part of the file
// duplicate the directory caching datastructure to make the code easier
// to understand.
// each file in the cache needs:
// filename, is_writing, last_closed, is_removing
// the first time a file is opened a new entry is created in the cache
// setting the filename and is_writing to true. This check needs to be
// wrapped with a lock.
//
// each time a file is closed we set the last_closed for the file to now
// and we check the cache for files whose last
// closed is greater than cache_timeout, then start a new thread rming
// that file.
// TODO: just to prevent this craziness for now
temp_file = fopen(file_path, "w+b");
if (!cloudfs_object_write_fp(path, temp_file))
{
fclose(temp_file);
return -ENOENT;
}
}
}
else
{
temp_file = tmpfile();
if (!(info->flags & O_WRONLY))
{
if (!cloudfs_object_write_fp(path, temp_file))
{
fclose(temp_file);
return -ENOENT;
}
}
}
update_dir_cache(path, (de ? de->size : 0), 0, 0);
openfile *of = (openfile *)malloc(sizeof(openfile));
of->fd = dup(fileno(temp_file));
fclose(temp_file);
of->flags = info->flags;
info->fh = (uintptr_t)of;
info->direct_io = 1;
return 0;
}
static int cfs_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *info)
{
return pread(((openfile *)(uintptr_t)info->fh)->fd, buf, size, offset);
}
static int cfs_flush(const char *path, struct fuse_file_info *info)
{
openfile *of = (openfile *)(uintptr_t)info->fh;
if (of)
{
update_dir_cache(path, cloudfs_file_size(of->fd), 0, 0);
if (of->flags & O_RDWR || of->flags & O_WRONLY)
{
FILE *fp = fdopen(dup(of->fd), "r");
rewind(fp);
if (!cloudfs_object_read_fp(path, fp))
{
fclose(fp);
return -ENOENT;
}
fclose(fp);
}
}
return 0;
}
static int cfs_release(const char *path, struct fuse_file_info *info)
{
close(((openfile *)(uintptr_t)info->fh)->fd);
return 0;
}
static int cfs_rmdir(const char *path)
{
int success = cloudfs_delete_object(path);
if (success == -1)
return -ENOTEMPTY;
if (success)
{
dir_decache(path);
return 0;
}
return -ENOENT;
}
static int cfs_ftruncate(const char *path, off_t size, struct fuse_file_info *info)
{
openfile *of = (openfile *)(uintptr_t)info->fh;
if (ftruncate(of->fd, size))
return -errno;
lseek(of->fd, 0, SEEK_SET);
update_dir_cache(path, size, 0, 0);
return 0;
}
static int cfs_write(const char *path, const char *buf, size_t length, off_t offset, struct fuse_file_info *info)
{
update_dir_cache(path, offset + length, 0, 0);
return pwrite(((openfile *)(uintptr_t)info->fh)->fd, buf, length, offset);
}
static int cfs_unlink(const char *path)
{
int success = cloudfs_delete_object(path);
if (success == -1)
return -EACCES;
if (success)
{
dir_decache(path);
return 0;
}
return -ENOENT;
}
static int cfs_fsync(const char *path, int idunno, struct fuse_file_info *info)
{
return 0;
}
static int cfs_truncate(const char *path, off_t size)
{
cloudfs_object_truncate(path, size);
return 0;
}
static int cfs_statfs(const char *path, struct statvfs *stat)
{
if (cloudfs_statfs(path, stat)){
return 0;
}
else
return -EIO;
}
static int cfs_chown(const char *path, uid_t uid, gid_t gid)
{
return 0;
}
static int cfs_chmod(const char *path, mode_t mode)
{
return 0;
}
static int cfs_rename(const char *src, const char *dst)
{
dir_entry *src_de = path_info(src);
if (!src_de)
return -ENOENT;
if (src_de->isdir)
return -EISDIR;
if (cloudfs_copy_object(src, dst))
{
/* FIXME this isn't quite right as doesn't preserve last modified */
update_dir_cache(dst, src_de->size, 0, 0);
return cfs_unlink(src);
}
return -EIO;
}
static int cfs_symlink(const char *src, const char *dst)
{
if(cloudfs_create_symlink(src, dst))
{
update_dir_cache(dst, 1, 0, 1);
return 0;
}
return -EIO;
}
static int cfs_readlink(const char* path, char* buf, size_t size)
{
FILE *temp_file = tmpfile();
int ret = 0;
if (!cloudfs_object_write_fp(path, temp_file))
{
ret = -ENOENT;
}
if (!pread(fileno(temp_file), buf, size, 0))
{
ret = -ENOENT;
}
fclose(temp_file);
return ret;
}
static void *cfs_init(struct fuse_conn_info *conn)
{
signal(SIGPIPE, SIG_IGN);
return NULL;
}
char *get_home_dir()
{
char *home;
if ((home = getenv("HOME")) && !access(home, R_OK))
return home;
struct passwd *pwd = getpwuid(geteuid());
if ((home = pwd->pw_dir) && !access(home, R_OK))
return home;
return "~";
}
FuseOptions options = {
.cache_timeout = "600",
.verify_ssl = "true",
.segment_size = "1073741824",
.segment_above = "2147483648",
.storage_url = "",
.container = "",
//.temp_dir = "/tmp/",
.temp_dir = "",
.client_id = "",
.client_secret = "",
.refresh_token = ""
};
int parse_option(void *data, const char *arg, int key, struct fuse_args *outargs)
{
if (sscanf(arg, " cache_timeout = %[^\r\n ]", options.cache_timeout) ||
sscanf(arg, " verify_ssl = %[^\r\n ]", options.verify_ssl) ||
sscanf(arg, " segment_above = %[^\r\n ]", options.segment_above) ||
sscanf(arg, " segment_size = %[^\r\n ]", options.segment_size) ||
sscanf(arg, " storage_url = %[^\r\n ]", options.storage_url) ||
sscanf(arg, " container = %[^\r\n ]", options.container) ||
sscanf(arg, " temp_dir = %[^\r\n ]", options.temp_dir) ||
sscanf(arg, " client_id = %[^\r\n ]", options.client_id) ||
sscanf(arg, " client_secret = %[^\r\n ]", options.client_secret) ||
sscanf(arg, " refresh_token = %[^\r\n ]", options.refresh_token))
return 0;
if (!strcmp(arg, "-f") || !strcmp(arg, "-d") || !strcmp(arg, "debug"))
cloudfs_debug(1);
return 1;
}
int main(int argc, char **argv)
{
char settings_filename[MAX_PATH_SIZE] = "";
FILE *settings;
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
snprintf(settings_filename, sizeof(settings_filename), "%s/.hubicfuse", get_home_dir());
if ((settings = fopen(settings_filename, "r")))
{
char line[OPTION_SIZE];
while (fgets(line, sizeof(line), settings))
parse_option(NULL, line, -1, &args);
fclose(settings);
}
fuse_opt_parse(&args, &options, NULL, parse_option);
cache_timeout = atoi(options.cache_timeout);
segment_size = atoll(options.segment_size);
segment_above = atoll(options.segment_above);
// this is ok since main is on the stack during the entire execution
override_storage_url = options.storage_url;
public_container = options.container;
temp_dir = options.temp_dir;
if (!*options.client_id || !*options.client_secret || !*options.refresh_token)
{
fprintf(stderr, "Unable to determine client_id, client_secret or refresh_token.\n\n");
fprintf(stderr, "These can be set either as mount options or in "
"a file named %s\n\n", settings_filename);
fprintf(stderr, " client_id=[App's id]\n");
fprintf(stderr, " client_secret=[App's secret]\n");
fprintf(stderr, " refresh_token=[Get it running hubic_token]\n");
fprintf(stderr, "The following settings are optional:\n\n");
fprintf(stderr, " cache_timeout=[Seconds for directory caching, default 600]\n");
fprintf(stderr, " verify_ssl=[False to disable SSL cert verification]\n");
fprintf(stderr, " segment_size=[Size to use when creating DLOs, default 1073741824]\n");
fprintf(stderr, " segment_above=[File size at which to use segments, defult 2147483648]\n");
fprintf(stderr, " storage_url=[Storage URL for other tenant to view container]\n");
fprintf(stderr, " container=[Public container to view of tenant specified by storage_url]\n");
fprintf(stderr, " temp_dir=[Directory to store temp files]\n");
return 1;
}
cloudfs_init();
cloudfs_verify_ssl(!strcasecmp(options.verify_ssl, "true"));
cloudfs_set_credentials(options.client_id, options.client_secret, options.refresh_token);
if (!cloudfs_connect())
{
fprintf(stderr, "Failed to authenticate.\n");
return 1;
}
#ifndef HAVE_OPENSSL
#warning Compiling without libssl, will run single-threaded.
fuse_opt_add_arg(&args, "-s");
#endif
struct fuse_operations cfs_oper = {
.readdir = cfs_readdir,
.mkdir = cfs_mkdir,
.read = cfs_read,
.create = cfs_create,
.open = cfs_open,
.fgetattr = cfs_fgetattr,
.getattr = cfs_getattr,
.flush = cfs_flush,
.release = cfs_release,
.rmdir = cfs_rmdir,
.ftruncate = cfs_ftruncate,
.truncate = cfs_truncate,
.write = cfs_write,
.unlink = cfs_unlink,
.fsync = cfs_fsync,
.statfs = cfs_statfs,
.chmod = cfs_chmod,
.chown = cfs_chown,
.rename = cfs_rename,
.symlink = cfs_symlink,
.readlink = cfs_readlink,
.init = cfs_init,
};
pthread_mutex_init(&dmut, NULL);
return fuse_main(args.argc, args.argv, &cfs_oper, &options);
}