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 fused.fuse; 11 12 /* reexport stat_t */ 13 public import core.sys.posix.fcntl; 14 public import core.sys.posix.utime; 15 16 import std.algorithm; 17 import std.array; 18 import std.conv; 19 import std.stdio; 20 import std.string; 21 import std.process; 22 import errno = core.stdc.errno; 23 import core.stdc.string; 24 import core.sys.posix.signal; 25 26 import c.fuse.fuse; 27 28 import core.thread : thread_attachThis, thread_detachThis; 29 import core.sys.posix.pthread; 30 31 /** 32 * libfuse is handling the thread creation and we cannot hook into it. However 33 * we need to make the GC aware of the threads. So for any call to a handler 34 * we check if the current thread is attached and attach it if necessary. 35 */ 36 private int threadAttached = false; 37 private pthread_cleanup cleanup; 38 39 extern(C) void detach(void* ptr) nothrow 40 { 41 import std.exception; 42 collectException(thread_detachThis()); 43 } 44 45 private void attach() 46 { 47 if (!threadAttached) 48 { 49 thread_attachThis(); 50 cleanup.push(&detach, cast(void*) null); 51 threadAttached = true; 52 } 53 } 54 55 /** 56 * A template to wrap C function calls and support exceptions to indicate 57 * errors. 58 * 59 * The templates passes the Operations object to the lambda as the first 60 * arguemnt. 61 */ 62 private auto call(alias fn)() 63 { 64 attach(); 65 auto t = cast(Operations*) fuse_get_context().private_data; 66 try 67 { 68 return fn(*t); 69 } 70 catch (FuseException fe) 71 { 72 /* errno is used to indicate an error to libfuse */ 73 errno.errno = fe.errno; 74 return -fe.errno; 75 } 76 catch (Exception e) 77 { 78 (*t).exception(e); 79 return -errno.EIO; 80 } 81 } 82 83 /* C calling convention compatible function to hand into libfuse which wrap 84 * the call to our Operations object. 85 * 86 * Note that we convert our * char pointer to an array using the 87 * ptr[0..len] syntax. 88 */ 89 extern(System) 90 { 91 private int dfuse_access(const char* path, int mode) 92 { 93 return call!( 94 (Operations t) 95 { 96 if(t.access(path[0..path.strlen], mode)) 97 { 98 return 0; 99 } 100 return -1; 101 })(); 102 } 103 104 private int dfuse_getattr(const char* path, stat_t* st) 105 { 106 return call!( 107 (Operations t) 108 { 109 t.getattr(path[0..path.strlen], *st); 110 return 0; 111 })(); 112 } 113 114 private int dfuse_readdir(const char* path, void* buf, 115 fuse_fill_dir_t filler, off_t offset, fuse_file_info* fi) 116 { 117 return call!( 118 (Operations t) 119 { 120 foreach(file; t.readdir(path[0..path.strlen])) 121 { 122 filler(buf, cast(char*) toStringz(file), null, 0); 123 } 124 return 0; 125 })(); 126 } 127 128 private int dfuse_readlink(const char* path, char* buf, size_t size) 129 { 130 return call!( 131 (Operations t) 132 { 133 auto length = t.readlink(path[0..path.strlen], 134 (cast(ubyte*)buf)[0..size]); 135 /* Null-terminate the string and copy it over to the buffer. */ 136 assert(length <= size); 137 buf[length] = '\0'; 138 139 return 0; 140 })(); 141 } 142 143 private int dfuse_open(const char* path, fuse_file_info* fi) 144 { 145 return call!( 146 (Operations t) 147 { 148 t.open(path[0..path.strlen]); 149 return 0; 150 })(); 151 } 152 153 private int dfuse_release(const char* path, fuse_file_info* fi) 154 { 155 return call!( 156 (Operations t) 157 { 158 t.release(path[0..path.strlen]); 159 return 0; 160 })(); 161 } 162 163 private int dfuse_read(const char* path, char* buf, size_t size, 164 off_t offset, fuse_file_info* fi) 165 { 166 /* Ensure at compile time that off_t and size_t fit into an ulong. */ 167 static assert(ulong.max >= size_t.max); 168 static assert(ulong.max >= off_t.max); 169 170 return call!( 171 (Operations t) 172 { 173 auto bbuf = cast(ubyte*) buf; 174 return cast(int) t.read(path[0..path.strlen], bbuf[0..size], 175 to!ulong(offset)); 176 })(); 177 } 178 179 private int dfuse_write(const char* path, char* data, size_t size, 180 off_t offset, fuse_file_info* fi) 181 { 182 static assert(ulong.max >= size_t.max); 183 static assert(ulong.max >= off_t.max); 184 185 return call!( 186 (Operations t) 187 { 188 auto bdata = cast(ubyte*) data; 189 return t.write(path[0..path.strlen], bdata[0..size], 190 to!ulong(offset)); 191 })(); 192 } 193 194 private int dfuse_truncate(const char* path, off_t length) 195 { 196 static assert(ulong.max >= off_t.max); 197 return call!( 198 (Operations t) 199 { 200 t.truncate(path[0..path.strlen], to!ulong(length)); 201 return 0; 202 })(); 203 } 204 205 private int dfuse_mknod(const char* path, mode_t mod, dev_t dev) 206 { 207 static assert(ulong.max >= dev_t.max); 208 static assert(uint.max >= mode_t.max); 209 return call!( 210 (Operations t) 211 { 212 t.mknod(path[0..path.strlen], mod, dev); 213 return 0; 214 })(); 215 } 216 217 private int dfuse_unlink(const char* path) 218 { 219 return call!( 220 (Operations t) 221 { 222 t.unlink(path[0..path.strlen]); 223 return 0; 224 })(); 225 } 226 227 private int dfuse_mkdir(const char * path, mode_t mode) 228 { 229 static assert(uint.max >= mode_t.max); 230 return call!( 231 (Operations t) 232 { 233 t.mkdir(path[0..path.strlen], mode.to!uint); 234 return 0; 235 })(); 236 } 237 private int dfuse_rmdir(const char * path) 238 { 239 return call!( 240 (Operations t) 241 { 242 t.rmdir(path[0..path.strlen]); 243 return 0; 244 })(); 245 } 246 247 private int dfuse_rename(const char* orig, const char* dest) { 248 return call!( 249 (Operations t) 250 { 251 t.rename(orig[0..orig.strlen], dest[0..dest.strlen]); 252 return 0; 253 })(); 254 } 255 256 private int dfuse_chmod(const char* path, mode_t mode) { 257 return call!( 258 (Operations t) 259 { 260 t.chmod(path[0 .. path.strlen], mode); 261 return 0; 262 } 263 )(); 264 } 265 266 private int dfuse_utime(const char* path, utimbuf* time) { 267 return call!( 268 (Operations t) 269 { 270 t.utime(path[0 .. path.strlen], time); 271 return 0; 272 } 273 ); 274 } 275 276 private int dfuse_symlink(const char* target, char* link) { 277 return call!( 278 (Operations t) 279 { 280 t.symlink(target[0 .. target.strlen], link[0 .. link.strlen]); 281 return 0; 282 } 283 ); 284 } 285 286 private int dfuse_chown(const char* path, uid_t uid, gid_t gid) { 287 return call!( 288 (Operations t) 289 { 290 t.chown(path[0 .. path.strlen], uid, gid); 291 return 0; 292 } 293 ); 294 } 295 296 private void* dfuse_init(fuse_conn_info* conn) 297 { 298 attach(); 299 auto t = cast(Operations*) fuse_get_context().private_data; 300 (*t).initialize(); 301 return t; 302 } 303 304 private void dfuse_destroy(void* data) 305 { 306 /* this is an ugly hack at the moment. We need to somehow detach all 307 threads from the runtime because after fuse_main finishes the pthreads 308 are joined. We circumvent that problem by just exiting while our 309 threads still run. */ 310 import core.stdc.stdlib : exit; 311 exit(0); 312 } 313 } /* extern(C) */ 314 315 export class FuseException : Exception 316 { 317 public int errno; 318 this(int errno, string file = __FILE__, size_t line = __LINE__, 319 Throwable next = null) 320 { 321 super("Fuse Exception", file, line, next); 322 this.errno = errno; 323 } 324 } 325 326 /** 327 * An object oriented wrapper around fuse_operations. 328 */ 329 export class Operations 330 { 331 /** 332 * Runs on filesystem creation 333 */ 334 void initialize() 335 { 336 } 337 338 /** 339 * Called to get a stat(2) structure for a path. 340 */ 341 void getattr(const(char)[] path, ref stat_t stat) 342 { 343 throw new FuseException(errno.EOPNOTSUPP); 344 } 345 346 /** 347 * Read path into the provided buffer beginning at offset. 348 * 349 * Params: 350 * path = The path to the file to read. 351 * buf = The buffer to read the data into. 352 * offset = An offset to start reading at. 353 * Returns: The amount of bytes read. 354 */ 355 ulong read(const(char)[] path, ubyte[] buf, ulong offset) 356 { 357 throw new FuseException(errno.EOPNOTSUPP); 358 } 359 360 /** 361 * Write the given data to the file. 362 * 363 * Params: 364 * path = The path to the file to write. 365 * buf = A read-only buffer containing the data to write. 366 * offset = An offset to start writing at. 367 * Returns: The amount of bytes written. 368 */ 369 int write(const(char)[] path, in ubyte[] data, ulong offset) 370 { 371 throw new FuseException(errno.EOPNOTSUPP); 372 } 373 374 /** 375 * Truncate a file to the given length. 376 * Params: 377 * path = The path to the file to trunate. 378 * length = Truncate file to this given length. 379 */ 380 void truncate(const(char)[] path, ulong length) 381 { 382 throw new FuseException(errno.EOPNOTSUPP); 383 } 384 385 /** 386 * Returns a list of files and directory names in the given folder. Note 387 * that you have to return . and .. 388 * 389 * Params: 390 * path = The path to the directory. 391 * Returns: An array of filenames. 392 */ 393 string[] readdir(const(char)[] path) 394 { 395 throw new FuseException(errno.EOPNOTSUPP); 396 } 397 398 /** 399 * Reads the link identified by path into the given buffer. 400 * 401 * Params: 402 * path = The path to the directory. 403 */ 404 size_t readlink(const(char)[] path, ubyte[] buf) 405 { 406 throw new FuseException(errno.EOPNOTSUPP); 407 } 408 409 /** 410 * Determine if the user has access to the given path. 411 * 412 * Params: 413 * path = The path to check. 414 * mode = An flag indicating what to check for. See access(2) for 415 * supported modes. 416 * Returns: True on success otherwise false. 417 */ 418 bool access(const(char)[] path, int mode) 419 { 420 throw new FuseException(errno.EOPNOTSUPP); 421 } 422 423 /** 424 * Changes the mode of a path. 425 * 426 * Params: 427 * path = The path to check. 428 * mode = The mode to set. 429 */ 430 void chmod(const(char)[] path, mode_t mode) 431 { 432 throw new FuseException(errno.EOPNOTSUPP); 433 } 434 435 /** 436 * Sets access and modification time. 437 * 438 * Params: 439 * path = The patch to modify. 440 * time = The time to set. 441 */ 442 void utime(const(char)[] path, utimbuf* time) 443 { 444 throw new FuseException(errno.EOPNOTSUPP); 445 } 446 447 /** 448 * Creates a symlink. 449 * 450 * Params: 451 * path = The path to create. 452 * target = The target of the link. 453 */ 454 void symlink(const(char)[] target, const(char)[] path) 455 { 456 throw new FuseException(errno.EOPNOTSUPP); 457 } 458 459 /** 460 * Changes ownership of a file. 461 * 462 * Params: 463 * path = Path to the file. 464 * uid = New user ID. 465 * gid = New group ID. 466 */ 467 void chown(const(char)[] path, uid_t uid, gid_t gid) 468 { 469 throw new FuseException(errno.EOPNOTSUPP); 470 } 471 472 void mknod(const(char)[] path, int mod, ulong dev) 473 { 474 throw new FuseException(errno.EOPNOTSUPP); 475 } 476 477 void unlink(const(char)[] path) 478 { 479 throw new FuseException(errno.EOPNOTSUPP); 480 } 481 482 void mkdir(const(char)[] path, uint mode) 483 { 484 throw new FuseException(errno.EOPNOTSUPP); 485 } 486 487 void rmdir(const(char)[] path) 488 { 489 throw new FuseException(errno.EOPNOTSUPP); 490 } 491 492 void rename(const(char)[] orig, const(char)[] dest) 493 { 494 throw new FuseException(errno.EOPNOTSUPP); 495 } 496 497 void open(const(char)[] path) 498 { 499 throw new FuseException(errno.EOPNOTSUPP); 500 } 501 502 void release(const(char)[] path) 503 { 504 throw new FuseException(errno.EOPNOTSUPP); 505 } 506 507 void exception(Exception e) 508 { 509 } 510 } 511 512 /** 513 * A wrapper around fuse_main() 514 */ 515 export class Fuse 516 { 517 private: 518 bool foreground; 519 bool threaded; 520 string fsname; 521 int pid; 522 523 public: 524 this(string fsname) 525 { 526 this(fsname, false, true); 527 } 528 529 this(string fsname, bool foreground, bool threaded) 530 { 531 this.fsname = fsname; 532 this.foreground = foreground; 533 this.threaded = threaded; 534 } 535 536 void mount(Operations ops, const string mountpoint, string[] mountopts) 537 { 538 string [] args = [this.fsname]; 539 540 args ~= mountpoint; 541 542 if(mountopts.length > 0) 543 { 544 args ~= format("-o%s", mountopts.join(",")); 545 } 546 547 if(this.foreground) 548 { 549 args ~= "-f"; 550 } 551 552 if(!this.threaded) 553 { 554 args ~= "-s"; 555 } 556 557 debug writefln("fuse arguments s=(%s)", args); 558 559 fuse_operations fops; 560 561 fops.init = &dfuse_init; 562 fops.access = &dfuse_access; 563 fops.getattr = &dfuse_getattr; 564 fops.readdir = &dfuse_readdir; 565 fops.open = &dfuse_open; 566 fops.release = &dfuse_release; 567 fops.read = &dfuse_read; 568 fops.write = &dfuse_write; 569 fops.truncate = &dfuse_truncate; 570 fops.readlink = &dfuse_readlink; 571 fops.destroy = &dfuse_destroy; 572 fops.mknod = &dfuse_mknod; 573 fops.unlink = &dfuse_unlink; 574 fops.mkdir = &dfuse_mkdir; 575 fops.rmdir = &dfuse_rmdir; 576 fops.rename = &dfuse_rename; 577 fops.chmod = &dfuse_chmod; 578 fops.utime = &dfuse_utime; 579 fops.symlink = &dfuse_symlink; 580 fops.chown = &dfuse_chown; 581 582 /* Create c-style arguments from a string[] array. */ 583 auto cargs = array(map!(a => toStringz(a))(args)); 584 int length = cast(int) cargs.length; 585 static if(length.max < cargs.length.max) 586 { 587 /* This is an unsafe cast that we need to do for C compat. 588 Enforce unlike assert will be checked in opt-builds as well. */ 589 import std.exception : enforce; 590 enforce(length >= 0); 591 enforce(length == cargs.length); 592 } 593 594 this.pid = thisProcessID(); 595 fuse_main(length, cast(char**) cargs.ptr, &fops, &ops); 596 } 597 598 void exit() 599 { 600 kill(this.pid, SIGINT); 601 } 602 }