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 int dfuse_mknod(const char* path, mode_t mod, dev_t dev) 183 { 184 static assert(ulong.max >= dev_t.max); 185 static assert(uint.max >= mode_t.max); 186 return call!( 187 (Operations t) 188 { 189 t.mknod(path[0..path.strlen], mod, dev); 190 return 0; 191 })(); 192 } 193 194 private int dfuse_unlink(const char* path) 195 { 196 return call!( 197 (Operations t) 198 { 199 t.unlink(path[0..path.strlen]); 200 return 0; 201 })(); 202 } 203 204 private int dfuse_mkdir(const char * path, mode_t mode) 205 { 206 static assert(uint.max >= mode_t.max); 207 return call!( 208 (Operations t) 209 { 210 t.mkdir(path[0..path.strlen], mode.to!uint); 211 return 0; 212 })(); 213 } 214 private int dfuse_rmdir(const char * path) 215 { 216 return call!( 217 (Operations t) 218 { 219 t.rmdir(path[0..path.strlen]); 220 return 0; 221 })(); 222 } 223 224 private int dfuse_rename(const char* orig, const char* dest) { 225 return call!( 226 (Operations t) 227 { 228 t.rename(orig[0..orig.strlen], dest[0..dest.strlen]); 229 return 0; 230 })(); 231 } 232 233 private void* dfuse_init(fuse_conn_info* conn) 234 { 235 attach(); 236 auto t = cast(Operations*) fuse_get_context().private_data; 237 (*t).initialize(); 238 return t; 239 } 240 241 private void dfuse_destroy(void* data) 242 { 243 /* this is an ugly hack at the moment. We need to somehow detach all 244 threads from the runtime because after fuse_main finishes the pthreads 245 are joined. We circumvent that problem by just exiting while our 246 threads still run. */ 247 import core.stdc.stdlib; 248 exit(0); 249 } 250 } /* extern(C) */ 251 252 export class FuseException : Exception 253 { 254 public int errno; 255 this(int errno, string file = __FILE__, size_t line = __LINE__, 256 Throwable next = null) 257 { 258 super("Fuse Exception", file, line, next); 259 this.errno = errno; 260 } 261 } 262 263 /** 264 * An object oriented wrapper around fuse_operations. 265 */ 266 export class Operations 267 { 268 /** 269 * Runs on filesystem creation 270 */ 271 void initialize() 272 { 273 } 274 275 /** 276 * Called to get a stat(2) structure for a path. 277 */ 278 void getattr(const(char)[] path, ref stat_t stat) 279 { 280 throw new FuseException(errno.EOPNOTSUPP); 281 } 282 283 /** 284 * Read path into the provided buffer beginning at offset. 285 * 286 * Params: 287 * path = The path to the file to read. 288 * buf = The buffer to read the data into. 289 * offset = An offset to start reading at. 290 * Returns: The amount of bytes read. 291 */ 292 ulong read(const(char)[] path, ubyte[] buf, ulong offset) 293 { 294 throw new FuseException(errno.EOPNOTSUPP); 295 } 296 297 /** 298 * Write the given data to the file. 299 * 300 * Params: 301 * path = The path to the file to write. 302 * buf = A read-only buffer containing the data to write. 303 * offset = An offset to start writing at. 304 * Returns: The amount of bytes written. 305 */ 306 int write(const(char)[] path, in ubyte[] data, ulong offset) 307 { 308 throw new FuseException(errno.EOPNOTSUPP); 309 } 310 311 /** 312 * Truncate a file to the given length. 313 * Params: 314 * path = The path to the file to trunate. 315 * length = Truncate file to this given length. 316 */ 317 void truncate(const(char)[] path, ulong length) 318 { 319 throw new FuseException(errno.EOPNOTSUPP); 320 } 321 322 /** 323 * Returns a list of files and directory names in the given folder. Note 324 * that you have to return . and .. 325 * 326 * Params: 327 * path = The path to the directory. 328 * Returns: An array of filenames. 329 */ 330 string[] readdir(const(char)[] path) 331 { 332 throw new FuseException(errno.EOPNOTSUPP); 333 } 334 335 /** 336 * Reads the link identified by path into the given buffer. 337 * 338 * Params: 339 * path = The path to the directory. 340 */ 341 size_t readlink(const(char)[] path, ubyte[] buf) 342 { 343 throw new FuseException(errno.EOPNOTSUPP); 344 } 345 346 /** 347 * Determine if the user has access to the given path. 348 * 349 * Params: 350 * path = The path to check. 351 * mode = An flag indicating what to check for. See access(2) for 352 * supported modes. 353 * Returns: True on success otherwise false. 354 */ 355 bool access(const(char)[] path, int mode) 356 { 357 throw new FuseException(errno.EOPNOTSUPP); 358 } 359 360 void mknod(const(char)[] path, int mod, ulong dev) 361 { 362 throw new FuseException(errno.EOPNOTSUPP); 363 } 364 365 void unlink(const(char)[] path) 366 { 367 throw new FuseException(errno.EOPNOTSUPP); 368 } 369 370 void mkdir(const(char)[] path, uint mode) 371 { 372 throw new FuseException(errno.EOPNOTSUPP); 373 } 374 375 void rmdir(const(char)[] path) 376 { 377 throw new FuseException(errno.EOPNOTSUPP); 378 } 379 380 void rename(const(char)[] orig, const(char)[] dest) 381 { 382 throw new FuseException(errno.EOPNOTSUPP); 383 } 384 385 void exception(Exception e) 386 { 387 } 388 } 389 390 /** 391 * A wrapper around fuse_main() 392 */ 393 export class Fuse 394 { 395 private: 396 bool foreground; 397 bool threaded; 398 string fsname; 399 400 public: 401 this(string fsname) 402 { 403 this(fsname, false, true); 404 } 405 406 this(string fsname, bool foreground, bool threaded) 407 { 408 this.fsname = fsname; 409 this.foreground = foreground; 410 this.threaded = threaded; 411 } 412 413 void mount(Operations ops, const string mountpoint, string[] mountopts) 414 { 415 string [] args = [this.fsname]; 416 417 args ~= mountpoint; 418 419 if(mountopts.length > 0) 420 { 421 args ~= format("-o%s", mountopts.join(",")); 422 } 423 424 if(this.foreground) 425 { 426 args ~= "-f"; 427 } 428 429 if(!this.threaded) 430 { 431 args ~= "-s"; 432 } 433 434 debug writefln("fuse arguments s=(%s)", args); 435 436 fuse_operations fops; 437 438 fops.init = &dfuse_init; 439 fops.access = &dfuse_access; 440 fops.getattr = &dfuse_getattr; 441 fops.readdir = &dfuse_readdir; 442 fops.read = &dfuse_read; 443 fops.write = &dfuse_write; 444 fops.truncate = &dfuse_truncate; 445 fops.readlink = &dfuse_readlink; 446 fops.destroy = &dfuse_destroy; 447 fops.mknod = &dfuse_mknod; 448 fops.unlink = &dfuse_unlink; 449 fops.mkdir = &dfuse_mkdir; 450 fops.rmdir = &dfuse_rmdir; 451 fops.rename = &dfuse_rename; 452 453 /* Create c-style arguments from a string[] array. */ 454 auto cargs = array(map!(a => toStringz(a))(args)); 455 int length = cast(int) cargs.length; 456 static if(length.max < cargs.length.max) 457 { 458 /* This is an unsafe cast that we need to do for C compat. 459 Enforce unlike assert will be checked in opt-builds as well. */ 460 import std.exception : enforce; 461 enforce(length >= 0); 462 enforce(length == cargs.length); 463 } 464 465 fuse_main(length, cast(char**) cargs.ptr, &fops, &ops); 466 } 467 }