1 /* 2 * Copyright (c) 2014, Facebook, Inc. 3 * All rights reserved. 4 * 5 * This source code is licensed under the Boost-style license found in the 6 * LICENSE file in the root directory of this source tree. An additional grant 7 * of patent rights can be found in the PATENTS file in the same directory. 8 * 9 */ 10 module dfuse.fuse; 11 12 /* reexport stat_t */ 13 public import core.sys.posix.fcntl; 14 15 import std.algorithm; 16 import std.array; 17 import std.conv; 18 import std.stdio; 19 import std..string; 20 import errno = core.stdc.errno; 21 import core.stdc..string; 22 23 import c.fuse.fuse; 24 25 import core.thread : thread_attachThis, thread_detachThis; 26 import core.sys.posix.pthread; 27 28 /** 29 * libfuse is handling the thread creation and we cannot hook into it. However 30 * we need to make the GC aware of the threads. So for any call to a handler 31 * we check if the current thread is attached and attach it if necessary. 32 */ 33 private int threadAttached = false; 34 private pthread_cleanup cleanup; 35 36 extern(C) void detach(void* ptr) nothrow 37 { 38 import std.exception; 39 collectException(thread_detachThis()); 40 } 41 42 private void attach() 43 { 44 if (!threadAttached) 45 { 46 thread_attachThis(); 47 cleanup.push(&detach, cast(void*) null); 48 threadAttached = true; 49 } 50 } 51 52 /** 53 * A template to wrap C function calls and support exceptions to indicate 54 * errors. 55 * 56 * The templates passes the Operations object to the lambda as the first 57 * arguemnt. 58 */ 59 private auto call(alias fn)() 60 { 61 attach(); 62 auto t = cast(Operations*) fuse_get_context().private_data; 63 try 64 { 65 return fn(*t); 66 } 67 catch (FuseException fe) 68 { 69 /* errno is used to indicate an error to libfuse */ 70 errno.errno = fe.errno; 71 return -fe.errno; 72 } 73 catch (Exception e) 74 { 75 (*t).exception(e); 76 return -errno.EIO; 77 } 78 } 79 80 /* C calling convention compatible function to hand into libfuse which wrap 81 * the call to our Operations object. 82 * 83 * Note that we convert our * char pointer to an array using the 84 * ptr[0..len] syntax. 85 */ 86 extern(System) 87 { 88 private int dfuse_access(const char* path, int mode) 89 { 90 return call!( 91 (Operations t) 92 { 93 if(t.access(path[0..path.strlen], mode)) 94 { 95 return 0; 96 } 97 return -1; 98 })(); 99 } 100 101 private int dfuse_getattr(const char* path, stat_t* st) 102 { 103 return call!( 104 (Operations t) 105 { 106 t.getattr(path[0..path.strlen], *st); 107 return 0; 108 })(); 109 } 110 111 private int dfuse_readdir(const char* path, void* buf, 112 fuse_fill_dir_t filler, off_t offset, fuse_file_info* fi) 113 { 114 return call!( 115 (Operations t) 116 { 117 foreach(file; t.readdir(path[0..path.strlen])) 118 { 119 filler(buf, cast(char*) toStringz(file), null, 0); 120 } 121 return 0; 122 })(); 123 } 124 125 private int dfuse_readlink(const char* path, char* buf, size_t size) 126 { 127 return call!( 128 (Operations t) 129 { 130 auto length = t.readlink(path[0..path.strlen], 131 (cast(ubyte*)buf)[0..size]); 132 /* Null-terminate the string and copy it over to the buffer. */ 133 assert(length <= size); 134 buf[length] = '\0'; 135 136 return 0; 137 })(); 138 } 139 140 private int dfuse_read(const char* path, char* buf, size_t size, 141 off_t offset, fuse_file_info* fi) 142 { 143 /* Ensure at compile time that off_t and size_t fit into an ulong. */ 144 static assert(ulong.max >= size_t.max); 145 static assert(ulong.max >= off_t.max); 146 147 return call!( 148 (Operations t) 149 { 150 auto bbuf = cast(ubyte*) buf; 151 return cast(int) t.read(path[0..path.strlen], bbuf[0..size], 152 to!ulong(offset)); 153 })(); 154 } 155 156 private int dfuse_write(const char* path, char* data, size_t size, 157 off_t offset, fuse_file_info* fi) 158 { 159 static assert(ulong.max >= size_t.max); 160 static assert(ulong.max >= off_t.max); 161 162 return call!( 163 (Operations t) 164 { 165 auto bdata = cast(ubyte*) data; 166 return t.write(path[0..path.strlen], bdata[0..size], 167 to!ulong(offset)); 168 })(); 169 } 170 171 private int dfuse_truncate(const char* path, off_t length) 172 { 173 static assert(ulong.max >= off_t.max); 174 return call!( 175 (Operations t) 176 { 177 t.truncate(path[0..path.strlen], to!ulong(length)); 178 return 0; 179 })(); 180 } 181 182 private void* dfuse_init(fuse_conn_info* conn) 183 { 184 attach(); 185 auto t = cast(Operations*) fuse_get_context().private_data; 186 (*t).initialize(); 187 return t; 188 } 189 190 private void dfuse_destroy(void* data) 191 { 192 /* this is an ugly hack at the moment. We need to somehow detach all 193 threads from the runtime because after fuse_main finishes the pthreads 194 are joined. We circumvent that problem by just exiting while our 195 threads still run. */ 196 import std.c.process; 197 exit(0); 198 } 199 } /* extern(C) */ 200 201 export class FuseException : Exception 202 { 203 public int errno; 204 this(int errno, string file = __FILE__, size_t line = __LINE__, 205 Throwable next = null) 206 { 207 super("Fuse Exception", file, line, next); 208 this.errno = errno; 209 } 210 } 211 212 /** 213 * An object oriented wrapper around fuse_operations. 214 */ 215 export class Operations 216 { 217 /** 218 * Runs on filesystem creation 219 */ 220 void initialize() 221 { 222 } 223 224 /** 225 * Called to get a stat(2) structure for a path. 226 */ 227 void getattr(const(char)[] path, ref stat_t stat) 228 { 229 throw new FuseException(errno.EOPNOTSUPP); 230 } 231 232 /** 233 * Read path into the provided buffer beginning at offset. 234 * 235 * Params: 236 * path = The path to the file to read. 237 * buf = The buffer to read the data into. 238 * offset = An offset to start reading at. 239 * Returns: The amount of bytes read. 240 */ 241 ulong read(const(char)[] path, ubyte[] buf, ulong offset) 242 { 243 throw new FuseException(errno.EOPNOTSUPP); 244 } 245 246 /** 247 * Write the given data to the file. 248 * 249 * Params: 250 * path = The path to the file to write. 251 * buf = A read-only buffer containing the data to write. 252 * offset = An offset to start writing at. 253 * Returns: The amount of bytes written. 254 */ 255 int write(const(char)[] path, in ubyte[] data, ulong offset) 256 { 257 throw new FuseException(errno.EOPNOTSUPP); 258 } 259 260 /** 261 * Truncate a file to the given length. 262 * Params: 263 * path = The path to the file to trunate. 264 * length = Truncate file to this given length. 265 */ 266 void truncate(const(char)[] path, ulong length) 267 { 268 throw new FuseException(errno.EOPNOTSUPP); 269 } 270 271 /** 272 * Returns a list of files and directory names in the given folder. Note 273 * that you have to return . and .. 274 * 275 * Params: 276 * path = The path to the directory. 277 * Returns: An array of filenames. 278 */ 279 string[] readdir(const(char)[] path) 280 { 281 throw new FuseException(errno.EOPNOTSUPP); 282 } 283 284 /** 285 * Reads the link identified by path into the given buffer. 286 * 287 * Params: 288 * path = The path to the directory. 289 */ 290 ulong readlink(const(char)[] path, ubyte[] buf) 291 { 292 throw new FuseException(errno.EOPNOTSUPP); 293 } 294 295 /** 296 * Determine if the user has access to the given path. 297 * 298 * Params: 299 * path = The path to check. 300 * mode = An flag indicating what to check for. See access(2) for 301 * supported modes. 302 * Returns: True on success otherwise false. 303 */ 304 bool access(const(char)[] path, int mode) 305 { 306 throw new FuseException(errno.EOPNOTSUPP); 307 } 308 309 void exception(Exception e) 310 { 311 } 312 } 313 314 /** 315 * A wrapper around fuse_main() 316 */ 317 export class Fuse 318 { 319 private: 320 bool foreground; 321 bool threaded; 322 string fsname; 323 324 public: 325 this(string fsname) 326 { 327 this(fsname, false, true); 328 } 329 330 this(string fsname, bool foreground, bool threaded) 331 { 332 this.fsname = fsname; 333 this.foreground = foreground; 334 this.threaded = threaded; 335 } 336 337 void mount(Operations ops, const string mountpoint, string[] mountopts) 338 { 339 string [] args = [this.fsname]; 340 341 args ~= mountpoint; 342 343 if(mountopts.length > 0) 344 { 345 args ~= format("-o%s", mountopts.join(",")); 346 } 347 348 if(this.foreground) 349 { 350 args ~= "-f"; 351 } 352 353 if(!this.threaded) 354 { 355 args ~= "-s"; 356 } 357 358 debug writefln("fuse arguments s=(%s)", args); 359 360 fuse_operations fops; 361 362 fops.init = &dfuse_init; 363 fops.access = &dfuse_access; 364 fops.getattr = &dfuse_getattr; 365 fops.readdir = &dfuse_readdir; 366 fops.read = &dfuse_read; 367 fops.write = &dfuse_write; 368 fops.truncate = &dfuse_truncate; 369 fops.readlink = &dfuse_readlink; 370 fops.destroy = &dfuse_destroy; 371 372 /* Create c-style arguments from a string[] array. */ 373 auto cargs = array(map!(a => toStringz(a))(args)); 374 int length = cast(int) cargs.length; 375 static if(length.max < cargs.length.max) 376 { 377 /* This is an unsafe cast that we need to do for C compat. 378 Enforce unlike assert will be checked in opt-builds as well. */ 379 import std.exception : enforce; 380 enforce(length >= 0); 381 enforce(length == cargs.length); 382 } 383 384 fuse_main(length, cast(char**) cargs.ptr, &fops, &ops); 385 } 386 }