387 lines
9.8 KiB
D
387 lines
9.8 KiB
D
/*
|
|
* Copyright (c) 2014, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the Boost-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*
|
|
*/
|
|
module dfuse.fuse;
|
|
|
|
/* reexport stat_t */
|
|
public import core.sys.posix.fcntl;
|
|
|
|
import std.algorithm;
|
|
import std.array;
|
|
import std.conv;
|
|
import std.stdio;
|
|
import std.string;
|
|
import errno = core.stdc.errno;
|
|
import core.stdc.string;
|
|
|
|
import c.fuse.fuse;
|
|
|
|
import core.thread : thread_attachThis, thread_detachThis;
|
|
import core.sys.posix.pthread;
|
|
|
|
/**
|
|
* libfuse is handling the thread creation and we cannot hook into it. However
|
|
* we need to make the GC aware of the threads. So for any call to a handler
|
|
* we check if the current thread is attached and attach it if necessary.
|
|
*/
|
|
private int threadAttached = false;
|
|
private pthread_cleanup cleanup;
|
|
|
|
extern(C) void detach(void* ptr) nothrow
|
|
{
|
|
import std.exception;
|
|
collectException(thread_detachThis());
|
|
}
|
|
|
|
private void attach()
|
|
{
|
|
if (!threadAttached)
|
|
{
|
|
thread_attachThis();
|
|
cleanup.push(&detach, cast(void*) null);
|
|
threadAttached = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A template to wrap C function calls and support exceptions to indicate
|
|
* errors.
|
|
*
|
|
* The templates passes the Operations object to the lambda as the first
|
|
* arguemnt.
|
|
*/
|
|
private auto call(alias fn)()
|
|
{
|
|
attach();
|
|
auto t = cast(Operations*) fuse_get_context().private_data;
|
|
try
|
|
{
|
|
return fn(*t);
|
|
}
|
|
catch (FuseException fe)
|
|
{
|
|
/* errno is used to indicate an error to libfuse */
|
|
errno.errno = fe.errno;
|
|
return -fe.errno;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
(*t).exception(e);
|
|
return -errno.EIO;
|
|
}
|
|
}
|
|
|
|
/* C calling convention compatible function to hand into libfuse which wrap
|
|
* the call to our Operations object.
|
|
*
|
|
* Note that we convert our * char pointer to an array using the
|
|
* ptr[0..len] syntax.
|
|
*/
|
|
extern(System)
|
|
{
|
|
private int dfuse_access(const char* path, int mode)
|
|
{
|
|
return call!(
|
|
(Operations t)
|
|
{
|
|
if(t.access(path[0..path.strlen], mode))
|
|
{
|
|
return 0;
|
|
}
|
|
return -1;
|
|
})();
|
|
}
|
|
|
|
private int dfuse_getattr(const char* path, stat_t* st)
|
|
{
|
|
return call!(
|
|
(Operations t)
|
|
{
|
|
t.getattr(path[0..path.strlen], *st);
|
|
return 0;
|
|
})();
|
|
}
|
|
|
|
private int dfuse_readdir(const char* path, void* buf,
|
|
fuse_fill_dir_t filler, off_t offset, fuse_file_info* fi)
|
|
{
|
|
return call!(
|
|
(Operations t)
|
|
{
|
|
foreach(file; t.readdir(path[0..path.strlen]))
|
|
{
|
|
filler(buf, cast(char*) toStringz(file), null, 0);
|
|
}
|
|
return 0;
|
|
})();
|
|
}
|
|
|
|
private int dfuse_readlink(const char* path, char* buf, size_t size)
|
|
{
|
|
return call!(
|
|
(Operations t)
|
|
{
|
|
auto length = t.readlink(path[0..path.strlen],
|
|
(cast(ubyte*)buf)[0..size]);
|
|
/* Null-terminate the string and copy it over to the buffer. */
|
|
assert(length <= size);
|
|
buf[length] = '\0';
|
|
|
|
return 0;
|
|
})();
|
|
}
|
|
|
|
private int dfuse_read(const char* path, char* buf, size_t size,
|
|
off_t offset, fuse_file_info* fi)
|
|
{
|
|
/* Ensure at compile time that off_t and size_t fit into an ulong. */
|
|
static assert(ulong.max >= size_t.max);
|
|
static assert(ulong.max >= off_t.max);
|
|
|
|
return call!(
|
|
(Operations t)
|
|
{
|
|
auto bbuf = cast(ubyte*) buf;
|
|
return cast(int) t.read(path[0..path.strlen], bbuf[0..size],
|
|
to!ulong(offset));
|
|
})();
|
|
}
|
|
|
|
private int dfuse_write(const char* path, char* data, size_t size,
|
|
off_t offset, fuse_file_info* fi)
|
|
{
|
|
static assert(ulong.max >= size_t.max);
|
|
static assert(ulong.max >= off_t.max);
|
|
|
|
return call!(
|
|
(Operations t)
|
|
{
|
|
auto bdata = cast(ubyte*) data;
|
|
return t.write(path[0..path.strlen], bdata[0..size],
|
|
to!ulong(offset));
|
|
})();
|
|
}
|
|
|
|
private int dfuse_truncate(const char* path, off_t length)
|
|
{
|
|
static assert(ulong.max >= off_t.max);
|
|
return call!(
|
|
(Operations t)
|
|
{
|
|
t.truncate(path[0..path.strlen], to!ulong(length));
|
|
return 0;
|
|
})();
|
|
}
|
|
|
|
private void* dfuse_init(fuse_conn_info* conn)
|
|
{
|
|
attach();
|
|
auto t = cast(Operations*) fuse_get_context().private_data;
|
|
(*t).initialize();
|
|
return t;
|
|
}
|
|
|
|
private void dfuse_destroy(void* data)
|
|
{
|
|
/* this is an ugly hack at the moment. We need to somehow detach all
|
|
threads from the runtime because after fuse_main finishes the pthreads
|
|
are joined. We circumvent that problem by just exiting while our
|
|
threads still run. */
|
|
import core.stdc.stdlib : exit;
|
|
exit(0);
|
|
}
|
|
} /* extern(C) */
|
|
|
|
export class FuseException : Exception
|
|
{
|
|
public int errno;
|
|
this(int errno, string file = __FILE__, size_t line = __LINE__,
|
|
Throwable next = null)
|
|
{
|
|
super("Fuse Exception", file, line, next);
|
|
this.errno = errno;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An object oriented wrapper around fuse_operations.
|
|
*/
|
|
export class Operations
|
|
{
|
|
/**
|
|
* Runs on filesystem creation
|
|
*/
|
|
void initialize()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Called to get a stat(2) structure for a path.
|
|
*/
|
|
void getattr(const(char)[] path, ref stat_t stat)
|
|
{
|
|
throw new FuseException(errno.EOPNOTSUPP);
|
|
}
|
|
|
|
/**
|
|
* Read path into the provided buffer beginning at offset.
|
|
*
|
|
* Params:
|
|
* path = The path to the file to read.
|
|
* buf = The buffer to read the data into.
|
|
* offset = An offset to start reading at.
|
|
* Returns: The amount of bytes read.
|
|
*/
|
|
ulong read(const(char)[] path, ubyte[] buf, ulong offset)
|
|
{
|
|
throw new FuseException(errno.EOPNOTSUPP);
|
|
}
|
|
|
|
/**
|
|
* Write the given data to the file.
|
|
*
|
|
* Params:
|
|
* path = The path to the file to write.
|
|
* buf = A read-only buffer containing the data to write.
|
|
* offset = An offset to start writing at.
|
|
* Returns: The amount of bytes written.
|
|
*/
|
|
int write(const(char)[] path, in ubyte[] data, ulong offset)
|
|
{
|
|
throw new FuseException(errno.EOPNOTSUPP);
|
|
}
|
|
|
|
/**
|
|
* Truncate a file to the given length.
|
|
* Params:
|
|
* path = The path to the file to trunate.
|
|
* length = Truncate file to this given length.
|
|
*/
|
|
void truncate(const(char)[] path, ulong length)
|
|
{
|
|
throw new FuseException(errno.EOPNOTSUPP);
|
|
}
|
|
|
|
/**
|
|
* Returns a list of files and directory names in the given folder. Note
|
|
* that you have to return . and ..
|
|
*
|
|
* Params:
|
|
* path = The path to the directory.
|
|
* Returns: An array of filenames.
|
|
*/
|
|
string[] readdir(const(char)[] path)
|
|
{
|
|
throw new FuseException(errno.EOPNOTSUPP);
|
|
}
|
|
|
|
/**
|
|
* Reads the link identified by path into the given buffer.
|
|
*
|
|
* Params:
|
|
* path = The path to the directory.
|
|
*/
|
|
ulong readlink(const(char)[] path, ubyte[] buf)
|
|
{
|
|
throw new FuseException(errno.EOPNOTSUPP);
|
|
}
|
|
|
|
/**
|
|
* Determine if the user has access to the given path.
|
|
*
|
|
* Params:
|
|
* path = The path to check.
|
|
* mode = An flag indicating what to check for. See access(2) for
|
|
* supported modes.
|
|
* Returns: True on success otherwise false.
|
|
*/
|
|
bool access(const(char)[] path, int mode)
|
|
{
|
|
throw new FuseException(errno.EOPNOTSUPP);
|
|
}
|
|
|
|
void exception(Exception e)
|
|
{
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A wrapper around fuse_main()
|
|
*/
|
|
export class Fuse
|
|
{
|
|
private:
|
|
bool foreground;
|
|
bool threaded;
|
|
string fsname;
|
|
|
|
public:
|
|
this(string fsname)
|
|
{
|
|
this(fsname, false, true);
|
|
}
|
|
|
|
this(string fsname, bool foreground, bool threaded)
|
|
{
|
|
this.fsname = fsname;
|
|
this.foreground = foreground;
|
|
this.threaded = threaded;
|
|
}
|
|
|
|
void mount(Operations ops, const string mountpoint, string[] mountopts)
|
|
{
|
|
string [] args = [this.fsname];
|
|
|
|
args ~= mountpoint;
|
|
|
|
if(mountopts.length > 0)
|
|
{
|
|
args ~= format("-o%s", mountopts.join(","));
|
|
}
|
|
|
|
if(this.foreground)
|
|
{
|
|
args ~= "-f";
|
|
}
|
|
|
|
if(!this.threaded)
|
|
{
|
|
args ~= "-s";
|
|
}
|
|
|
|
debug writefln("fuse arguments s=(%s)", args);
|
|
|
|
fuse_operations fops;
|
|
|
|
fops.init = &dfuse_init;
|
|
fops.access = &dfuse_access;
|
|
fops.getattr = &dfuse_getattr;
|
|
fops.readdir = &dfuse_readdir;
|
|
fops.read = &dfuse_read;
|
|
fops.write = &dfuse_write;
|
|
fops.truncate = &dfuse_truncate;
|
|
fops.readlink = &dfuse_readlink;
|
|
fops.destroy = &dfuse_destroy;
|
|
|
|
/* Create c-style arguments from a string[] array. */
|
|
auto cargs = array(map!(a => toStringz(a))(args));
|
|
int length = cast(int) cargs.length;
|
|
static if(length.max < cargs.length.max)
|
|
{
|
|
/* This is an unsafe cast that we need to do for C compat.
|
|
Enforce unlike assert will be checked in opt-builds as well. */
|
|
import std.exception : enforce;
|
|
enforce(length >= 0);
|
|
enforce(length == cargs.length);
|
|
}
|
|
|
|
fuse_main(length, cast(char**) cargs.ptr, &fops, &ops);
|
|
}
|
|
}
|