├── .gitignore ├── LICENSE ├── README.md ├── abstractions.cc ├── abstractions.h ├── binding.gyp ├── example.js ├── fuse-bindings.cc ├── index.js ├── package.json └── test ├── fixtures ├── mnt.js └── stat.js ├── links.js ├── misc.js ├── read.js └── write.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .nadconfig.mk 3 | build 4 | deps/ 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Mathias Buus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fuse-bindings 2 | 3 | # NOTICE 4 | 5 | # We published the successor module to this at https://github.com/fuse-friends/fuse-native 6 | 7 | Fully maintained fuse bindings for Node that aims to cover the entire FUSE api 8 | 9 | ``` 10 | npm install fuse-bindings 11 | ``` 12 | 13 | Compared to [fuse4js](https://github.com/bcle/fuse4js) these bindings cover almost the entire FUSE api (except for locking) and doesn't do 14 | any buffer copys in read/write. It also supports unmount and mouting of multiple fuse drives. 15 | 16 | ## Requirements 17 | 18 | You need to have FUSE installed (or Dokany on Windows) 19 | 20 | * On Linux/Ubuntu `sudo apt-get install libfuse-dev` 21 | * On OSX 22 | * if you use Brew, install [OSXFuse](http://osxfuse.github.com/) and `brew install pkg-config` 23 | * if you use MacPorts, `sudo port install osxfuse +devel` 24 | * On Windows install [Dokany](https://github.com/dokan-dev/dokany) 25 | 26 | ### Windows 27 | **WARNING**: Dokany is still not quite stable. It can cause BSODs. Be careful. 28 | 29 | ~~Using this on Windows is slightly more complicated. You need to install [Dokany](https://github.com/dokan-dev/dokany) (for `dokanfuse.lib`, `dokanctl.exe`, driver and service) **and** clone its repo (for the headers).~~ 30 | 31 | ~~Once the Dokany repo is cloned, you also need to set environment variable `DOKAN_INSTALL_DIR` to the path to `DokenLibrary` of your Dokany installaton, and `DOKAN_FUSE_INCLUDE` to the path to `*dokany repo*\dokan_fuse\include`.~~ 32 | 33 | **EDIT**: Dokany now includes needed headers and sets proper environment variables when installing! Just install Dokany and this module should install and work just fine! (Drop an issue otherwise) 34 | 35 | ## Usage 36 | 37 | Try creating an empty folder called `mnt` and run the below example 38 | 39 | ``` js 40 | var fuse = require('fuse-bindings') 41 | 42 | var mountPath = process.platform !== 'win32' ? './mnt' : 'M:\\' 43 | 44 | fuse.mount(mountPath, { 45 | readdir: function (path, cb) { 46 | console.log('readdir(%s)', path) 47 | if (path === '/') return cb(0, ['test']) 48 | cb(0) 49 | }, 50 | getattr: function (path, cb) { 51 | console.log('getattr(%s)', path) 52 | if (path === '/') { 53 | cb(0, { 54 | mtime: new Date(), 55 | atime: new Date(), 56 | ctime: new Date(), 57 | nlink: 1, 58 | size: 100, 59 | mode: 16877, 60 | uid: process.getuid ? process.getuid() : 0, 61 | gid: process.getgid ? process.getgid() : 0 62 | }) 63 | return 64 | } 65 | 66 | if (path === '/test') { 67 | cb(0, { 68 | mtime: new Date(), 69 | atime: new Date(), 70 | ctime: new Date(), 71 | nlink: 1, 72 | size: 12, 73 | mode: 33188, 74 | uid: process.getuid ? process.getuid() : 0, 75 | gid: process.getgid ? process.getgid() : 0 76 | }) 77 | return 78 | } 79 | 80 | cb(fuse.ENOENT) 81 | }, 82 | open: function (path, flags, cb) { 83 | console.log('open(%s, %d)', path, flags) 84 | cb(0, 42) // 42 is an fd 85 | }, 86 | read: function (path, fd, buf, len, pos, cb) { 87 | console.log('read(%s, %d, %d, %d)', path, fd, len, pos) 88 | var str = 'hello world\n'.slice(pos, pos + len) 89 | if (!str) return cb(0) 90 | buf.write(str) 91 | return cb(str.length) 92 | } 93 | }, function (err) { 94 | if (err) throw err 95 | console.log('filesystem mounted on ' + mountPath) 96 | }) 97 | 98 | process.on('SIGINT', function () { 99 | fuse.unmount(mountPath, function (err) { 100 | if (err) { 101 | console.log('filesystem at ' + mountPath + ' not unmounted', err) 102 | } else { 103 | console.log('filesystem at ' + mountPath + ' unmounted') 104 | } 105 | }) 106 | }) 107 | ``` 108 | 109 | ## See also 110 | 111 | [fs-fuse](https://github.com/piranna/fs-fuse) is a wrapper module build on top of `fuse-bindings` that allow you to export and mount any `fs`-like object as a FUSE filesystem. 112 | 113 | ## API 114 | 115 | #### `fuse.mount(mnt, ops, [cb])` 116 | 117 | Mount a new filesystem on `mnt`. 118 | Pass the FUSE operations you want to support as the `ops` argument. 119 | 120 | #### `fuse.unmount(mnt, [cb])` 121 | 122 | Unmount a filesystem 123 | 124 | #### `fuse.context()` 125 | 126 | Returns the current fuse context (pid, uid, gid). 127 | Must be called inside a fuse callback. 128 | 129 | ## Mount options 130 | 131 | #### `ops.options` 132 | 133 | Set [mount options](http://blog.woralelandia.com/2012/07/16/fuse-mount-options/) 134 | 135 | ``` js 136 | ops.options = ['direct_io'] // set the direct_io option 137 | ``` 138 | 139 | #### `ops.displayFolder` 140 | 141 | Set to `true` to make OSX display a folder icon and the folder name as the mount point in finder 142 | 143 | #### `ops.force` 144 | 145 | Set to `true` to force mount the filesystem (will do an unmount first) 146 | 147 | ## FUSE operations 148 | 149 | Most of the [FUSE api](http://fuse.sourceforge.net/doxygen/structfuse__operations.html) is supported. In general the callback for each op should be called with `cb(returnCode, [value])` where the return code is a number (`0` for OK and `< 0` for errors). See below for a list of POSIX error codes. 150 | 151 | #### `ops.init(cb)` 152 | 153 | Called on filesystem init. 154 | 155 | #### `ops.access(path, mode, cb)` 156 | 157 | Called before the filesystem accessed a file 158 | 159 | #### `ops.statfs(path, cb)` 160 | 161 | Called when the filesystem is being stat'ed. Accepts a fs stat object after the return code in the callback. 162 | 163 | ``` js 164 | ops.statfs = function (path, cb) { 165 | cb(0, { 166 | bsize: 1000000, 167 | frsize: 1000000, 168 | blocks: 1000000, 169 | bfree: 1000000, 170 | bavail: 1000000, 171 | files: 1000000, 172 | ffree: 1000000, 173 | favail: 1000000, 174 | fsid: 1000000, 175 | flag: 1000000, 176 | namemax: 1000000 177 | }) 178 | } 179 | ``` 180 | 181 | #### `ops.getattr(path, cb)` 182 | 183 | Called when a path is being stat'ed. Accepts a stat object (similar to the one returned in `fs.stat(path, cb)`) after the return code in the callback. 184 | 185 | ``` js 186 | ops.getattr = function (path, cb) { 187 | cb(0, { 188 | mtime: new Date(), 189 | atime: new Date(), 190 | ctime: new Date(), 191 | size: 100, 192 | mode: 16877, 193 | uid: process.getuid(), 194 | gid: process.getgid() 195 | }) 196 | } 197 | ``` 198 | 199 | #### `ops.fgetattr(path, fd, cb)` 200 | 201 | Same as above but is called when someone stats a file descriptor 202 | 203 | #### `ops.flush(path, fd, cb)` 204 | 205 | Called when a file descriptor is being flushed 206 | 207 | #### `ops.fsync(path, fd, datasync, cb)` 208 | 209 | Called when a file descriptor is being fsync'ed. 210 | 211 | #### `ops.fsyncdir(path, fd, datasync, cb)` 212 | 213 | Same as above but on a directory 214 | 215 | #### `ops.readdir(path, cb)` 216 | 217 | Called when a directory is being listed. Accepts an array of file/directory names after the return code in the callback 218 | 219 | ``` js 220 | ops.readdir = function (path, cb) { 221 | cb(0, ['file-1.txt', 'dir']) 222 | } 223 | ``` 224 | 225 | #### `ops.truncate(path, size, cb)` 226 | 227 | Called when a path is being truncated to a specific size 228 | 229 | #### `ops.ftruncate(path, fd, size, cb)` 230 | 231 | Same as above but on a file descriptor 232 | 233 | #### `ops.readlink(path, cb)` 234 | 235 | Called when a symlink is being resolved. Accepts a pathname (that the link should resolve to) after the return code in the callback 236 | 237 | ``` js 238 | ops.readlink = function (path, cb) { 239 | cb(null, 'file.txt') // make link point to file.txt 240 | } 241 | ``` 242 | 243 | #### `ops.chown(path, uid, gid, cb)` 244 | 245 | Called when ownership of a path is being changed 246 | 247 | #### `ops.chmod(path, mode, cb)` 248 | 249 | Called when the mode of a path is being changed 250 | 251 | #### `ops.mknod(path, mode, dev, cb)` 252 | 253 | Called when the a new device file is being made. 254 | 255 | #### `ops.setxattr(path, name, buffer, length, offset, flags, cb)` 256 | 257 | Called when extended attributes is being set (see the extended docs for your platform). 258 | Currently you can read the attribute value being set in `buffer` at `offset`. 259 | 260 | #### `ops.getxattr(path, name, buffer, length, offset, cb)` 261 | 262 | Called when extended attributes is being read. 263 | Currently you have to write the result to the provided `buffer` at `offset`. 264 | 265 | #### `ops.listxattr(path, buffer, length, cb)` 266 | 267 | Called when extended attributes of a path are being listed. 268 | `buffer` should be filled with the extended attribute names as *null-terminated* strings, one after the other, up to a total of `length` in length. (`ERANGE` should be passed to the callback if `length` is insufficient.) 269 | The size of buffer required to hold all the names should be passed to the callback either on success, or if the supplied `length` was zero. 270 | 271 | #### `ops.removexattr(path, name, cb)` 272 | 273 | Called when an extended attribute is being removed. 274 | 275 | #### `ops.open(path, flags, cb)` 276 | 277 | Called when a path is being opened. `flags` in a number containing the permissions being requested. Accepts a file descriptor after the return code in the callback. 278 | 279 | ``` js 280 | var toFlag = function(flags) { 281 | flags = flags & 3 282 | if (flags === 0) return 'r' 283 | if (flags === 1) return 'w' 284 | return 'r+' 285 | } 286 | 287 | ops.open = function (path, flags, cb) { 288 | var flag = toFlag(flags) // convert flags to a node style string 289 | ... 290 | cb(0, 42) // 42 is a file descriptor 291 | } 292 | ``` 293 | 294 | #### `ops.opendir(path, flags, cb)` 295 | 296 | Same as above but for directories 297 | 298 | #### `ops.read(path, fd, buffer, length, position, cb)` 299 | 300 | Called when contents of a file is being read. You should write the result of the read to the `buffer` and return the number of bytes written as the first argument in the callback. 301 | If no bytes were written (read is complete) return 0 in the callback. 302 | 303 | ``` js 304 | var data = new Buffer('hello world') 305 | 306 | ops.read = function (path, fd, buffer, length, position, cb) { 307 | if (position >= data.length) return cb(0) // done 308 | var part = data.slice(position, position + length) 309 | part.copy(buffer) // write the result of the read to the result buffer 310 | cb(part.length) // return the number of bytes read 311 | } 312 | ``` 313 | 314 | #### `ops.write(path, fd, buffer, length, position, cb)` 315 | 316 | Called when a file is being written to. You can get the data being written in `buffer` and you should return the number of bytes written in the callback as the first argument. 317 | 318 | ``` js 319 | ops.write = function (path, fd, buffer, length, position, cb) { 320 | console.log('writing', buffer.slice(0, length)) 321 | cb(length) // we handled all the data 322 | } 323 | ``` 324 | 325 | #### `ops.release(path, fd, cb)` 326 | 327 | Called when a file descriptor is being released. Happens when a read/write is done etc. 328 | 329 | #### `ops.releasedir(path, fd, cb)` 330 | 331 | Same as above but for directories 332 | 333 | #### `ops.create(path, mode, cb)` 334 | 335 | Called when a new file is being opened. 336 | 337 | #### `ops.utimens(path, atime, mtime, cb)` 338 | 339 | Called when the atime/mtime of a file is being changed. 340 | 341 | #### `ops.unlink(path, cb)` 342 | 343 | Called when a file is being unlinked. 344 | 345 | #### `ops.rename(src, dest, cb)` 346 | 347 | Called when a file is being renamed. 348 | 349 | #### `ops.link(src, dest, cb)` 350 | 351 | Called when a new link is created. 352 | 353 | #### `ops.symlink(src, dest, cb)` 354 | 355 | Called when a new symlink is created 356 | 357 | #### `ops.mkdir(path, mode, cb)` 358 | 359 | Called when a new directory is being created 360 | 361 | #### `ops.rmdir(path, cb)` 362 | 363 | Called when a directory is being removed 364 | 365 | #### `ops.destroy(cb)` 366 | 367 | Both `read` and `write` passes the underlying fuse buffer without copying them to be as fast as possible. 368 | 369 | ## Error codes 370 | 371 | The available error codes are exposes as well as properties. These include 372 | 373 | * `fuse.EPERM === -1` 374 | * `fuse.ENOENT === -2` 375 | * `fuse.ESRCH === -3` 376 | * `fuse.EINTR === -4` 377 | * `fuse.EIO === -5` 378 | * `fuse.ENXIO === -6` 379 | * `fuse.E2BIG === -7` 380 | * `fuse.ENOEXEC === -8` 381 | * `fuse.EBADF === -9` 382 | * `fuse.ECHILD === -10` 383 | * `fuse.EAGAIN === -11` 384 | * `fuse.ENOMEM === -12` 385 | * `fuse.EACCES === -13` 386 | * `fuse.EFAULT === -14` 387 | * `fuse.ENOTBLK === -15` 388 | * `fuse.EBUSY === -16` 389 | * `fuse.EEXIST === -17` 390 | * `fuse.EXDEV === -18` 391 | * `fuse.ENODEV === -19` 392 | * `fuse.ENOTDIR === -20` 393 | * `fuse.EISDIR === -21` 394 | * `fuse.EINVAL === -22` 395 | * `fuse.ENFILE === -23` 396 | * `fuse.EMFILE === -24` 397 | * `fuse.ENOTTY === -25` 398 | * `fuse.ETXTBSY === -26` 399 | * `fuse.EFBIG === -27` 400 | * `fuse.ENOSPC === -28` 401 | * `fuse.ESPIPE === -29` 402 | * `fuse.EROFS === -30` 403 | * `fuse.EMLINK === -31` 404 | * `fuse.EPIPE === -32` 405 | * `fuse.EDOM === -33` 406 | * `fuse.ERANGE === -34` 407 | * `fuse.EDEADLK === -35` 408 | * `fuse.ENAMETOOLONG === -36` 409 | * `fuse.ENOLCK === -37` 410 | * `fuse.ENOSYS === -38` 411 | * `fuse.ENOTEMPTY === -39` 412 | * `fuse.ELOOP === -40` 413 | * `fuse.EWOULDBLOCK === -11` 414 | * `fuse.ENOMSG === -42` 415 | * `fuse.EIDRM === -43` 416 | * `fuse.ECHRNG === -44` 417 | * `fuse.EL2NSYNC === -45` 418 | * `fuse.EL3HLT === -46` 419 | * `fuse.EL3RST === -47` 420 | * `fuse.ELNRNG === -48` 421 | * `fuse.EUNATCH === -49` 422 | * `fuse.ENOCSI === -50` 423 | * `fuse.EL2HLT === -51` 424 | * `fuse.EBADE === -52` 425 | * `fuse.EBADR === -53` 426 | * `fuse.EXFULL === -54` 427 | * `fuse.ENOANO === -55` 428 | * `fuse.EBADRQC === -56` 429 | * `fuse.EBADSLT === -57` 430 | * `fuse.EDEADLOCK === -35` 431 | * `fuse.EBFONT === -59` 432 | * `fuse.ENOSTR === -60` 433 | * `fuse.ENODATA === -61` 434 | * `fuse.ETIME === -62` 435 | * `fuse.ENOSR === -63` 436 | * `fuse.ENONET === -64` 437 | * `fuse.ENOPKG === -65` 438 | * `fuse.EREMOTE === -66` 439 | * `fuse.ENOLINK === -67` 440 | * `fuse.EADV === -68` 441 | * `fuse.ESRMNT === -69` 442 | * `fuse.ECOMM === -70` 443 | * `fuse.EPROTO === -71` 444 | * `fuse.EMULTIHOP === -72` 445 | * `fuse.EDOTDOT === -73` 446 | * `fuse.EBADMSG === -74` 447 | * `fuse.EOVERFLOW === -75` 448 | * `fuse.ENOTUNIQ === -76` 449 | * `fuse.EBADFD === -77` 450 | * `fuse.EREMCHG === -78` 451 | * `fuse.ELIBACC === -79` 452 | * `fuse.ELIBBAD === -80` 453 | * `fuse.ELIBSCN === -81` 454 | * `fuse.ELIBMAX === -82` 455 | * `fuse.ELIBEXEC === -83` 456 | * `fuse.EILSEQ === -84` 457 | * `fuse.ERESTART === -85` 458 | * `fuse.ESTRPIPE === -86` 459 | * `fuse.EUSERS === -87` 460 | * `fuse.ENOTSOCK === -88` 461 | * `fuse.EDESTADDRREQ === -89` 462 | * `fuse.EMSGSIZE === -90` 463 | * `fuse.EPROTOTYPE === -91` 464 | * `fuse.ENOPROTOOPT === -92` 465 | * `fuse.EPROTONOSUPPORT === -93` 466 | * `fuse.ESOCKTNOSUPPORT === -94` 467 | * `fuse.EOPNOTSUPP === -95` 468 | * `fuse.EPFNOSUPPORT === -96` 469 | * `fuse.EAFNOSUPPORT === -97` 470 | * `fuse.EADDRINUSE === -98` 471 | * `fuse.EADDRNOTAVAIL === -99` 472 | * `fuse.ENETDOWN === -100` 473 | * `fuse.ENETUNREACH === -101` 474 | * `fuse.ENETRESET === -102` 475 | * `fuse.ECONNABORTED === -103` 476 | * `fuse.ECONNRESET === -104` 477 | * `fuse.ENOBUFS === -105` 478 | * `fuse.EISCONN === -106` 479 | * `fuse.ENOTCONN === -107` 480 | * `fuse.ESHUTDOWN === -108` 481 | * `fuse.ETOOMANYREFS === -109` 482 | * `fuse.ETIMEDOUT === -110` 483 | * `fuse.ECONNREFUSED === -111` 484 | * `fuse.EHOSTDOWN === -112` 485 | * `fuse.EHOSTUNREACH === -113` 486 | * `fuse.EALREADY === -114` 487 | * `fuse.EINPROGRESS === -115` 488 | * `fuse.ESTALE === -116` 489 | * `fuse.EUCLEAN === -117` 490 | * `fuse.ENOTNAM === -118` 491 | * `fuse.ENAVAIL === -119` 492 | * `fuse.EISNAM === -120` 493 | * `fuse.EREMOTEIO === -121` 494 | * `fuse.EDQUOT === -122` 495 | * `fuse.ENOMEDIUM === -123` 496 | * `fuse.EMEDIUMTYPE === -124` 497 | 498 | ## License 499 | 500 | MIT 501 | -------------------------------------------------------------------------------- /abstractions.cc: -------------------------------------------------------------------------------- 1 | #include "abstractions.h" 2 | 3 | #ifndef _WIN32 4 | 5 | #include 6 | #include 7 | 8 | int execute_command_and_wait (char* argv[]) { 9 | // Fork our running process. 10 | pid_t cpid = vfork(); 11 | 12 | // Check if we are the observer or the new process. 13 | if (cpid > 0) { 14 | int status = 0; 15 | waitpid(cpid, &status, 0); 16 | return WIFEXITED(status) ? WEXITSTATUS(status) : -1; 17 | } else { 18 | // At this point we are on our child process. 19 | execvp(argv[0], argv); 20 | exit(1); 21 | 22 | // Something failed. 23 | return -1; 24 | } 25 | } 26 | 27 | #endif 28 | 29 | #ifdef __APPLE__ 30 | 31 | #include 32 | #include 33 | 34 | pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 35 | 36 | void thread_create (abstr_thread_t* thread, thread_fn fn, void* data) { 37 | pthread_attr_t attr; 38 | pthread_attr_init(&attr); 39 | pthread_create(thread, &attr, fn, data); 40 | } 41 | 42 | void thread_join (abstr_thread_t thread) { 43 | pthread_join(thread, NULL); 44 | } 45 | 46 | int fusermount (char *path) { 47 | char *argv[] = {(char *) "umount", path, NULL}; 48 | 49 | return execute_command_and_wait(argv); 50 | } 51 | 52 | #elif defined(_WIN32) 53 | 54 | HANDLE mutex = CreateMutex(NULL, false, NULL); 55 | 56 | void thread_create (HANDLE* thread, thread_fn fn, void* data) { 57 | *thread = CreateThread(NULL, 0, fn, data, 0, NULL); 58 | } 59 | 60 | void thread_join (HANDLE thread) { 61 | WaitForSingleObject(thread, INFINITE); 62 | } 63 | 64 | int fusermount (char *path) { 65 | char* dokanPath = getenv("DokanLibrary1"); 66 | char cmdLine[MAX_PATH]; 67 | 68 | if(dokanPath) { 69 | // Let's make sure there aren't no double slashes 70 | const char* dokanPathLast = dokanPath + strlen(dokanPath) - 1; 71 | 72 | const char* potentialEndSlash = 73 | (*dokanPathLast == '/' || *dokanPathLast == '\\') ? "" : "\\"; 74 | 75 | sprintf(cmdLine, "\"%s%sdokanctl.exe\" /u %s", dokanPath, potentialEndSlash, path); 76 | } 77 | else sprintf(cmdLine, "dokanctl.exe /u %s", path); 78 | 79 | STARTUPINFO info = {sizeof(info)}; 80 | PROCESS_INFORMATION procInfo; 81 | CreateProcess(NULL, cmdLine, NULL, NULL, false, CREATE_NO_WINDOW, NULL, NULL, &info, &procInfo); 82 | 83 | WaitForSingleObject(procInfo.hProcess, INFINITE); 84 | 85 | DWORD exitCode = -1; 86 | GetExitCodeProcess(procInfo.hProcess, &exitCode); 87 | 88 | CloseHandle(procInfo.hProcess); 89 | CloseHandle(procInfo.hThread); 90 | 91 | return exitCode; 92 | 93 | // dokanctl.exe requires admin permissions for some reason, so if node is not run as admin, 94 | // it'll fail to create the process for unmounting. The path will be unmounted once 95 | // the process is killed, however, so there's that! 96 | } 97 | 98 | #else 99 | 100 | #include 101 | #include 102 | 103 | pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 104 | 105 | void thread_create (abstr_thread_t* thread, thread_fn fn, void* data) { 106 | pthread_attr_t attr; 107 | pthread_attr_init(&attr); 108 | pthread_create(thread, &attr, fn, data); 109 | } 110 | 111 | void thread_join (abstr_thread_t thread) { 112 | pthread_join(thread, NULL); 113 | } 114 | 115 | int fusermount (char *path) { 116 | char *argv[] = {(char *) "fusermount", (char *) "-q", (char *) "-u", path, NULL}; 117 | 118 | return execute_command_and_wait(argv); 119 | } 120 | 121 | #endif 122 | -------------------------------------------------------------------------------- /abstractions.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define FUSE_USE_VERSION 29 4 | 5 | #ifdef __APPLE__ 6 | 7 | // OS X 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #define FUSE_OFF_T off_t 16 | 17 | typedef dispatch_semaphore_t bindings_sem_t; 18 | 19 | NAN_INLINE static int semaphore_init (dispatch_semaphore_t *sem) { 20 | *sem = dispatch_semaphore_create(0); 21 | return *sem == NULL ? -1 : 0; 22 | } 23 | 24 | NAN_INLINE static void semaphore_wait (dispatch_semaphore_t *sem) { 25 | dispatch_semaphore_wait(*sem, DISPATCH_TIME_FOREVER); 26 | } 27 | 28 | NAN_INLINE static void semaphore_signal (dispatch_semaphore_t *sem) { 29 | dispatch_semaphore_signal(*sem); 30 | } 31 | 32 | extern pthread_mutex_t mutex; 33 | 34 | NAN_INLINE static void mutex_lock (pthread_mutex_t *mutex) { 35 | pthread_mutex_lock(mutex); 36 | } 37 | 38 | NAN_INLINE static void mutex_unlock (pthread_mutex_t *mutex) { 39 | pthread_mutex_unlock(mutex); 40 | } 41 | 42 | typedef pthread_t abstr_thread_t; 43 | typedef void* thread_fn_rtn_t; 44 | 45 | #elif defined(_WIN32) 46 | 47 | #include 48 | #include 49 | #include 50 | 51 | typedef HANDLE bindings_sem_t; 52 | 53 | NAN_INLINE static int semaphore_init (HANDLE *sem) { 54 | *sem = CreateSemaphore(NULL, 0, 10, NULL); 55 | return *sem == NULL ? -1 : 0; 56 | } 57 | 58 | NAN_INLINE static void semaphore_wait (HANDLE *sem) { 59 | WaitForSingleObject(*sem, INFINITE); 60 | } 61 | 62 | NAN_INLINE static void semaphore_signal (HANDLE *sem) { 63 | ReleaseSemaphore(*sem, 1, NULL); 64 | } 65 | 66 | extern HANDLE mutex; 67 | 68 | NAN_INLINE static void mutex_lock (HANDLE *mutex) { 69 | WaitForSingleObject(*mutex, INFINITE); 70 | } 71 | 72 | NAN_INLINE static void mutex_unlock (HANDLE *mutex) { 73 | ReleaseMutex(*mutex); 74 | } 75 | 76 | typedef HANDLE abstr_thread_t; 77 | typedef DWORD thread_fn_rtn_t; 78 | 79 | #define fuse_session_remove_chan(x) 80 | #define stat _stati64 81 | 82 | #else 83 | 84 | // Linux and whatnot 85 | #include 86 | 87 | #include 88 | #include 89 | 90 | #define FUSE_OFF_T off_t 91 | 92 | typedef sem_t bindings_sem_t; 93 | 94 | NAN_INLINE static int semaphore_init (sem_t *sem) { 95 | return sem_init(sem, 0, 0); 96 | } 97 | 98 | NAN_INLINE static void semaphore_wait (sem_t *sem) { 99 | sem_wait(sem); 100 | } 101 | 102 | NAN_INLINE static void semaphore_signal (sem_t *sem) { 103 | sem_post(sem); 104 | } 105 | 106 | extern pthread_mutex_t mutex; 107 | 108 | NAN_INLINE static void mutex_lock (pthread_mutex_t *mutex) { 109 | pthread_mutex_lock(mutex); 110 | } 111 | 112 | NAN_INLINE static void mutex_unlock (pthread_mutex_t *mutex) { 113 | pthread_mutex_unlock(mutex); 114 | } 115 | 116 | typedef pthread_t abstr_thread_t; 117 | typedef void* thread_fn_rtn_t; 118 | 119 | #endif 120 | 121 | typedef thread_fn_rtn_t(*thread_fn)(void*); 122 | 123 | void thread_create (abstr_thread_t*, thread_fn, void*); 124 | void thread_join (abstr_thread_t); 125 | 126 | int fusermount (char*); 127 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [{ 3 | "target_name": "fuse_bindings", 4 | "sources": ["fuse-bindings.cc", "abstractions.cc"], 5 | "include_dirs": [ 6 | " 2 | 3 | #define FUSE_USE_VERSION 29 4 | 5 | #if defined(_WIN32) && _MSC_VER < 1900 6 | // Visual Studio 2015 adds struct timespec, 7 | // this #define will make Dokany define its 8 | // own struct timespec on earlier versions 9 | #define _CRT_NO_TIME_T 10 | #endif 11 | 12 | #include 13 | #include 14 | 15 | #ifndef _MSC_VER 16 | // Need to use FUSE_STAT when using Dokany with Visual Studio. 17 | // To keep things simple, when not using Visual Studio, 18 | // define FUSE_STAT to be "stat" so we can use FUSE_STAT in the code anyway. 19 | #define FUSE_STAT stat 20 | #endif 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "abstractions.h" 29 | 30 | using namespace v8; 31 | 32 | #define LOCAL_STRING(s) Nan::New(s).ToLocalChecked() 33 | #define LOOKUP_CALLBACK(map, name) map->Has(LOCAL_STRING(name)) ? new Nan::Callback(map->Get(LOCAL_STRING(name)).As()) : NULL 34 | 35 | enum bindings_ops_t { 36 | OP_INIT = 0, 37 | OP_ERROR, 38 | OP_ACCESS, 39 | OP_STATFS, 40 | OP_FGETATTR, 41 | OP_GETATTR, 42 | OP_FLUSH, 43 | OP_FSYNC, 44 | OP_FSYNCDIR, 45 | OP_READDIR, 46 | OP_TRUNCATE, 47 | OP_FTRUNCATE, 48 | OP_UTIMENS, 49 | OP_READLINK, 50 | OP_CHOWN, 51 | OP_CHMOD, 52 | OP_MKNOD, 53 | OP_SETXATTR, 54 | OP_GETXATTR, 55 | OP_LISTXATTR, 56 | OP_REMOVEXATTR, 57 | OP_OPEN, 58 | OP_OPENDIR, 59 | OP_READ, 60 | OP_WRITE, 61 | OP_RELEASE, 62 | OP_RELEASEDIR, 63 | OP_CREATE, 64 | OP_UNLINK, 65 | OP_RENAME, 66 | OP_LINK, 67 | OP_SYMLINK, 68 | OP_MKDIR, 69 | OP_RMDIR, 70 | OP_DESTROY 71 | }; 72 | 73 | static Nan::Persistent buffer_constructor; 74 | static Nan::Callback *callback_constructor; 75 | static struct FUSE_STAT empty_stat; 76 | 77 | struct bindings_t { 78 | int index; 79 | int gc; 80 | 81 | // fuse context 82 | int context_uid; 83 | int context_gid; 84 | int context_pid; 85 | 86 | // fuse data 87 | char mnt[1024]; 88 | char mntopts[1024]; 89 | abstr_thread_t thread; 90 | bindings_sem_t semaphore; 91 | bindings_sem_t semaphore_readdir; 92 | uv_async_t async; 93 | 94 | // methods 95 | Nan::Callback *ops_init; 96 | Nan::Callback *ops_error; 97 | Nan::Callback *ops_access; 98 | Nan::Callback *ops_statfs; 99 | Nan::Callback *ops_getattr; 100 | Nan::Callback *ops_fgetattr; 101 | Nan::Callback *ops_flush; 102 | Nan::Callback *ops_fsync; 103 | Nan::Callback *ops_fsyncdir; 104 | Nan::Callback *ops_readdir; 105 | Nan::Callback *ops_truncate; 106 | Nan::Callback *ops_ftruncate; 107 | Nan::Callback *ops_readlink; 108 | Nan::Callback *ops_chown; 109 | Nan::Callback *ops_chmod; 110 | Nan::Callback *ops_mknod; 111 | Nan::Callback *ops_setxattr; 112 | Nan::Callback *ops_getxattr; 113 | Nan::Callback *ops_listxattr; 114 | Nan::Callback *ops_removexattr; 115 | Nan::Callback *ops_open; 116 | Nan::Callback *ops_opendir; 117 | Nan::Callback *ops_read; 118 | Nan::Callback *ops_write; 119 | Nan::Callback *ops_release; 120 | Nan::Callback *ops_releasedir; 121 | Nan::Callback *ops_create; 122 | Nan::Callback *ops_utimens; 123 | Nan::Callback *ops_unlink; 124 | Nan::Callback *ops_rename; 125 | Nan::Callback *ops_link; 126 | Nan::Callback *ops_symlink; 127 | Nan::Callback *ops_mkdir; 128 | Nan::Callback *ops_rmdir; 129 | Nan::Callback *ops_destroy; 130 | 131 | Nan::Callback *callback; 132 | 133 | // method data 134 | bindings_ops_t op; 135 | fuse_fill_dir_t filler; // used in readdir 136 | struct fuse_file_info *info; 137 | char *path; 138 | char *name; 139 | FUSE_OFF_T offset; 140 | FUSE_OFF_T length; 141 | void *data; // various structs 142 | int mode; 143 | int dev; 144 | int uid; 145 | int gid; 146 | int result; 147 | }; 148 | 149 | static bindings_t *bindings_mounted[1024]; 150 | static int bindings_mounted_count = 0; 151 | static bindings_t *bindings_current = NULL; 152 | 153 | static bindings_t *bindings_find_mounted (char *path) { 154 | for (int i = 0; i < bindings_mounted_count; i++) { 155 | bindings_t *b = bindings_mounted[i]; 156 | if (b != NULL && !b->gc && !strcmp(b->mnt, path)) { 157 | return b; 158 | } 159 | } 160 | return NULL; 161 | } 162 | 163 | static int bindings_fusermount (char *path) { 164 | return fusermount(path); 165 | } 166 | 167 | static int bindings_unmount (char *path) { 168 | mutex_lock(&mutex); 169 | bindings_t *b = bindings_find_mounted(path); 170 | int result = bindings_fusermount(path); 171 | if (b != NULL && result == 0) b->gc = 1; 172 | mutex_unlock(&mutex); 173 | 174 | if (b != NULL && result == 0) thread_join(b->thread); 175 | 176 | return result; 177 | } 178 | 179 | #if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION && NODE_MODULE_VERSION < IOJS_3_0_MODULE_VERSION) 180 | NAN_INLINE v8::Local bindings_buffer (char *data, size_t length) { 181 | Local buf = Nan::New(buffer_constructor)->NewInstance(0, NULL); 182 | Local k = LOCAL_STRING("length"); 183 | Local v = Nan::New(length); 184 | buf->Set(k, v); 185 | buf->SetIndexedPropertiesToExternalArrayData((char *) data, kExternalUnsignedByteArray, length); 186 | return buf; 187 | } 188 | #else 189 | void noop (char *data, void *hint) {} 190 | NAN_INLINE v8::Local bindings_buffer (char *data, size_t length) { 191 | return Nan::NewBuffer(data, length, noop, NULL).ToLocalChecked(); 192 | } 193 | #endif 194 | 195 | NAN_INLINE static int bindings_call_ex (bindings_t *b, bool isreaddir) { 196 | uv_async_send(&(b->async)); 197 | if (isreaddir) { 198 | semaphore_wait(&(b->semaphore_readdir)); 199 | } else { 200 | semaphore_wait(&(b->semaphore)); 201 | } 202 | 203 | return b->result; 204 | } 205 | 206 | NAN_INLINE static int bindings_call (bindings_t *b) { 207 | return bindings_call_ex(b, false); 208 | } 209 | 210 | static bindings_t *bindings_get_context () { 211 | fuse_context *ctx = fuse_get_context(); 212 | bindings_t *b = (bindings_t *) ctx->private_data; 213 | b->context_pid = ctx->pid; 214 | b->context_uid = ctx->uid; 215 | b->context_gid = ctx->gid; 216 | return b; 217 | } 218 | 219 | static int bindings_mknod (const char *path, mode_t mode, dev_t dev) { 220 | bindings_t *b = bindings_get_context(); 221 | 222 | b->op = OP_MKNOD; 223 | b->path = (char *) path; 224 | b->mode = mode; 225 | b->dev = dev; 226 | 227 | return bindings_call(b); 228 | } 229 | 230 | static int bindings_truncate (const char *path, FUSE_OFF_T size) { 231 | bindings_t *b = bindings_get_context(); 232 | 233 | b->op = OP_TRUNCATE; 234 | b->path = (char *) path; 235 | b->length = size; 236 | 237 | return bindings_call(b); 238 | } 239 | 240 | static int bindings_ftruncate (const char *path, FUSE_OFF_T size, struct fuse_file_info *info) { 241 | bindings_t *b = bindings_get_context(); 242 | 243 | b->op = OP_FTRUNCATE; 244 | b->path = (char *) path; 245 | b->length = size; 246 | b->info = info; 247 | 248 | return bindings_call(b); 249 | } 250 | 251 | static int bindings_getattr (const char *path, struct FUSE_STAT *stat) { 252 | bindings_t *b = bindings_get_context(); 253 | 254 | b->op = OP_GETATTR; 255 | b->path = (char *) path; 256 | b->data = stat; 257 | 258 | return bindings_call(b); 259 | } 260 | 261 | static int bindings_fgetattr (const char *path, struct FUSE_STAT *stat, struct fuse_file_info *info) { 262 | bindings_t *b = bindings_get_context(); 263 | 264 | b->op = OP_FGETATTR; 265 | b->path = (char *) path; 266 | b->data = stat; 267 | b->info = info; 268 | 269 | return bindings_call(b); 270 | } 271 | 272 | static int bindings_flush (const char *path, struct fuse_file_info *info) { 273 | bindings_t *b = bindings_get_context(); 274 | 275 | b->op = OP_FLUSH; 276 | b->path = (char *) path; 277 | b->info = info; 278 | 279 | return bindings_call(b); 280 | } 281 | 282 | static int bindings_fsync (const char *path, int datasync, struct fuse_file_info *info) { 283 | bindings_t *b = bindings_get_context(); 284 | 285 | b->op = OP_FSYNC; 286 | b->path = (char *) path; 287 | b->mode = datasync; 288 | b->info = info; 289 | 290 | return bindings_call(b); 291 | } 292 | 293 | static int bindings_fsyncdir (const char *path, int datasync, struct fuse_file_info *info) { 294 | bindings_t *b = bindings_get_context(); 295 | 296 | b->op = OP_FSYNCDIR; 297 | b->path = (char *) path; 298 | b->mode = datasync; 299 | b->info = info; 300 | 301 | return bindings_call(b); 302 | } 303 | 304 | static int bindings_readdir (const char *path, void *buf, fuse_fill_dir_t filler, FUSE_OFF_T offset, struct fuse_file_info *info) { 305 | bindings_t *b = bindings_get_context(); 306 | 307 | b->op = OP_READDIR; 308 | b->path = (char *) path; 309 | b->data = buf; 310 | b->filler = filler; 311 | 312 | return bindings_call_ex(b, true); 313 | } 314 | 315 | static int bindings_readlink (const char *path, char *buf, size_t len) { 316 | bindings_t *b = bindings_get_context(); 317 | 318 | b->op = OP_READLINK; 319 | b->path = (char *) path; 320 | b->data = (void *) buf; 321 | b->length = len; 322 | 323 | return bindings_call(b); 324 | } 325 | 326 | static int bindings_chown (const char *path, uid_t uid, gid_t gid) { 327 | bindings_t *b = bindings_get_context(); 328 | 329 | b->op = OP_CHOWN; 330 | b->path = (char *) path; 331 | b->uid = uid; 332 | b->gid = gid; 333 | 334 | return bindings_call(b); 335 | } 336 | 337 | static int bindings_chmod (const char *path, mode_t mode) { 338 | bindings_t *b = bindings_get_context(); 339 | 340 | b->op = OP_CHMOD; 341 | b->path = (char *) path; 342 | b->mode = mode; 343 | 344 | return bindings_call(b); 345 | } 346 | 347 | #ifdef __APPLE__ 348 | static int bindings_setxattr (const char *path, const char *name, const char *value, size_t size, int flags, uint32_t position) { 349 | bindings_t *b = bindings_get_context(); 350 | 351 | b->op = OP_SETXATTR; 352 | b->path = (char *) path; 353 | b->name = (char *) name; 354 | b->data = (void *) value; 355 | b->length = size; 356 | b->offset = position; 357 | b->mode = flags; 358 | 359 | return bindings_call(b); 360 | } 361 | 362 | static int bindings_getxattr (const char *path, const char *name, char *value, size_t size, uint32_t position) { 363 | bindings_t *b = bindings_get_context(); 364 | 365 | b->op = OP_GETXATTR; 366 | b->path = (char *) path; 367 | b->name = (char *) name; 368 | b->data = (void *) value; 369 | b->length = size; 370 | b->offset = position; 371 | 372 | return bindings_call(b); 373 | } 374 | #else 375 | static int bindings_setxattr (const char *path, const char *name, const char *value, size_t size, int flags) { 376 | bindings_t *b = bindings_get_context(); 377 | 378 | b->op = OP_SETXATTR; 379 | b->path = (char *) path; 380 | b->name = (char *) name; 381 | b->data = (void *) value; 382 | b->length = size; 383 | b->offset = 0; 384 | b->mode = flags; 385 | 386 | return bindings_call(b); 387 | } 388 | 389 | static int bindings_getxattr (const char *path, const char *name, char *value, size_t size) { 390 | bindings_t *b = bindings_get_context(); 391 | 392 | b->op = OP_GETXATTR; 393 | b->path = (char *) path; 394 | b->name = (char *) name; 395 | b->data = (void *) value; 396 | b->length = size; 397 | b->offset = 0; 398 | 399 | return bindings_call(b); 400 | } 401 | #endif 402 | 403 | static int bindings_listxattr (const char *path, char *list, size_t size) { 404 | bindings_t *b = bindings_get_context(); 405 | 406 | b->op = OP_LISTXATTR; 407 | b->path = (char *) path; 408 | b->data = (void *) list; 409 | b->length = size; 410 | 411 | return bindings_call(b); 412 | } 413 | 414 | static int bindings_removexattr (const char *path, const char *name) { 415 | bindings_t *b = bindings_get_context(); 416 | 417 | b->op = OP_REMOVEXATTR; 418 | b->path = (char *) path; 419 | b->name = (char *) name; 420 | 421 | return bindings_call(b); 422 | } 423 | 424 | static int bindings_statfs (const char *path, struct statvfs *statfs) { 425 | bindings_t *b = bindings_get_context(); 426 | 427 | b->op = OP_STATFS; 428 | b->path = (char *) path; 429 | b->data = statfs; 430 | 431 | return bindings_call(b); 432 | } 433 | 434 | static int bindings_open (const char *path, struct fuse_file_info *info) { 435 | bindings_t *b = bindings_get_context(); 436 | 437 | b->op = OP_OPEN; 438 | b->path = (char *) path; 439 | b->mode = info->flags; 440 | b->info = info; 441 | 442 | return bindings_call(b); 443 | } 444 | 445 | static int bindings_opendir (const char *path, struct fuse_file_info *info) { 446 | bindings_t *b = bindings_get_context(); 447 | 448 | b->op = OP_OPENDIR; 449 | b->path = (char *) path; 450 | b->mode = info->flags; 451 | b->info = info; 452 | 453 | return bindings_call(b); 454 | } 455 | 456 | static int bindings_read (const char *path, char *buf, size_t len, FUSE_OFF_T offset, struct fuse_file_info *info) { 457 | bindings_t *b = bindings_get_context(); 458 | 459 | b->op = OP_READ; 460 | b->path = (char *) path; 461 | b->data = (void *) buf; 462 | b->offset = offset; 463 | b->length = len; 464 | b->info = info; 465 | 466 | return bindings_call(b); 467 | } 468 | 469 | static int bindings_write (const char *path, const char *buf, size_t len, FUSE_OFF_T offset, struct fuse_file_info * info) { 470 | bindings_t *b = bindings_get_context(); 471 | 472 | b->op = OP_WRITE; 473 | b->path = (char *) path; 474 | b->data = (void *) buf; 475 | b->offset = offset; 476 | b->length = len; 477 | b->info = info; 478 | 479 | return bindings_call(b); 480 | } 481 | 482 | static int bindings_release (const char *path, struct fuse_file_info *info) { 483 | bindings_t *b = bindings_get_context(); 484 | 485 | b->op = OP_RELEASE; 486 | b->path = (char *) path; 487 | b->info = info; 488 | 489 | return bindings_call(b); 490 | } 491 | 492 | static int bindings_releasedir (const char *path, struct fuse_file_info *info) { 493 | bindings_t *b = bindings_get_context(); 494 | 495 | b->op = OP_RELEASEDIR; 496 | b->path = (char *) path; 497 | b->info = info; 498 | 499 | return bindings_call(b); 500 | } 501 | 502 | static int bindings_access (const char *path, int mode) { 503 | bindings_t *b = bindings_get_context(); 504 | 505 | b->op = OP_ACCESS; 506 | b->path = (char *) path; 507 | b->mode = mode; 508 | 509 | return bindings_call(b); 510 | } 511 | 512 | static int bindings_create (const char *path, mode_t mode, struct fuse_file_info *info) { 513 | bindings_t *b = bindings_get_context(); 514 | 515 | b->op = OP_CREATE; 516 | b->path = (char *) path; 517 | b->mode = mode; 518 | b->info = info; 519 | 520 | return bindings_call(b); 521 | } 522 | 523 | static int bindings_utimens (const char *path, const struct timespec tv[2]) { 524 | bindings_t *b = bindings_get_context(); 525 | 526 | b->op = OP_UTIMENS; 527 | b->path = (char *) path; 528 | b->data = (void *) tv; 529 | 530 | return bindings_call(b); 531 | } 532 | 533 | static int bindings_unlink (const char *path) { 534 | bindings_t *b = bindings_get_context(); 535 | 536 | b->op = OP_UNLINK; 537 | b->path = (char *) path; 538 | 539 | return bindings_call(b); 540 | } 541 | 542 | static int bindings_rename (const char *src, const char *dest) { 543 | bindings_t *b = bindings_get_context(); 544 | 545 | b->op = OP_RENAME; 546 | b->path = (char *) src; 547 | b->data = (void *) dest; 548 | 549 | return bindings_call(b); 550 | } 551 | 552 | static int bindings_link (const char *path, const char *dest) { 553 | bindings_t *b = bindings_get_context(); 554 | 555 | b->op = OP_LINK; 556 | b->path = (char *) path; 557 | b->data = (void *) dest; 558 | 559 | return bindings_call(b); 560 | } 561 | 562 | static int bindings_symlink (const char *path, const char *dest) { 563 | bindings_t *b = bindings_get_context(); 564 | 565 | b->op = OP_SYMLINK; 566 | b->path = (char *) path; 567 | b->data = (void *) dest; 568 | 569 | return bindings_call(b); 570 | } 571 | 572 | static int bindings_mkdir (const char *path, mode_t mode) { 573 | bindings_t *b = bindings_get_context(); 574 | 575 | b->op = OP_MKDIR; 576 | b->path = (char *) path; 577 | b->mode = mode; 578 | 579 | return bindings_call(b); 580 | } 581 | 582 | static int bindings_rmdir (const char *path) { 583 | bindings_t *b = bindings_get_context(); 584 | 585 | b->op = OP_RMDIR; 586 | b->path = (char *) path; 587 | 588 | return bindings_call(b); 589 | } 590 | 591 | static void* bindings_init (struct fuse_conn_info *conn) { 592 | bindings_t *b = bindings_get_context(); 593 | 594 | b->op = OP_INIT; 595 | 596 | bindings_call(b); 597 | return b; 598 | } 599 | 600 | static void bindings_destroy (void *data) { 601 | bindings_t *b = bindings_get_context(); 602 | 603 | b->op = OP_DESTROY; 604 | 605 | bindings_call(b); 606 | } 607 | 608 | static void bindings_free (bindings_t *b) { 609 | if (b->ops_access != NULL) delete b->ops_access; 610 | if (b->ops_truncate != NULL) delete b->ops_truncate; 611 | if (b->ops_ftruncate != NULL) delete b->ops_ftruncate; 612 | if (b->ops_getattr != NULL) delete b->ops_getattr; 613 | if (b->ops_fgetattr != NULL) delete b->ops_fgetattr; 614 | if (b->ops_flush != NULL) delete b->ops_flush; 615 | if (b->ops_fsync != NULL) delete b->ops_fsync; 616 | if (b->ops_fsyncdir != NULL) delete b->ops_fsyncdir; 617 | if (b->ops_readdir != NULL) delete b->ops_readdir; 618 | if (b->ops_readlink != NULL) delete b->ops_readlink; 619 | if (b->ops_chown != NULL) delete b->ops_chown; 620 | if (b->ops_chmod != NULL) delete b->ops_chmod; 621 | if (b->ops_mknod != NULL) delete b->ops_mknod; 622 | if (b->ops_setxattr != NULL) delete b->ops_setxattr; 623 | if (b->ops_getxattr != NULL) delete b->ops_getxattr; 624 | if (b->ops_listxattr != NULL) delete b->ops_listxattr; 625 | if (b->ops_removexattr != NULL) delete b->ops_removexattr; 626 | if (b->ops_statfs != NULL) delete b->ops_statfs; 627 | if (b->ops_open != NULL) delete b->ops_open; 628 | if (b->ops_opendir != NULL) delete b->ops_opendir; 629 | if (b->ops_read != NULL) delete b->ops_read; 630 | if (b->ops_write != NULL) delete b->ops_write; 631 | if (b->ops_release != NULL) delete b->ops_release; 632 | if (b->ops_releasedir != NULL) delete b->ops_releasedir; 633 | if (b->ops_create != NULL) delete b->ops_create; 634 | if (b->ops_utimens != NULL) delete b->ops_utimens; 635 | if (b->ops_unlink != NULL) delete b->ops_unlink; 636 | if (b->ops_rename != NULL) delete b->ops_rename; 637 | if (b->ops_link != NULL) delete b->ops_link; 638 | if (b->ops_symlink != NULL) delete b->ops_symlink; 639 | if (b->ops_mkdir != NULL) delete b->ops_mkdir; 640 | if (b->ops_rmdir != NULL) delete b->ops_rmdir; 641 | if (b->ops_init != NULL) delete b->ops_init; 642 | if (b->ops_destroy != NULL) delete b->ops_destroy; 643 | if (b->callback != NULL) delete b->callback; 644 | 645 | bindings_mounted[b->index] = NULL; 646 | while (bindings_mounted_count > 0 && bindings_mounted[bindings_mounted_count - 1] == NULL) { 647 | bindings_mounted_count--; 648 | } 649 | 650 | free(b); 651 | } 652 | 653 | static void bindings_on_close (uv_handle_t *handle) { 654 | mutex_lock(&mutex); 655 | bindings_free((bindings_t *) handle->data); 656 | mutex_unlock(&mutex); 657 | } 658 | 659 | static thread_fn_rtn_t bindings_thread (void *data) { 660 | bindings_t *b = (bindings_t *) data; 661 | 662 | struct fuse_operations ops = { }; 663 | 664 | if (b->ops_access != NULL) ops.access = bindings_access; 665 | if (b->ops_truncate != NULL) ops.truncate = bindings_truncate; 666 | if (b->ops_ftruncate != NULL) ops.ftruncate = bindings_ftruncate; 667 | if (b->ops_getattr != NULL) ops.getattr = bindings_getattr; 668 | if (b->ops_fgetattr != NULL) ops.fgetattr = bindings_fgetattr; 669 | if (b->ops_flush != NULL) ops.flush = bindings_flush; 670 | if (b->ops_fsync != NULL) ops.fsync = bindings_fsync; 671 | if (b->ops_fsyncdir != NULL) ops.fsyncdir = bindings_fsyncdir; 672 | if (b->ops_readdir != NULL) ops.readdir = bindings_readdir; 673 | if (b->ops_readlink != NULL) ops.readlink = bindings_readlink; 674 | if (b->ops_chown != NULL) ops.chown = bindings_chown; 675 | if (b->ops_chmod != NULL) ops.chmod = bindings_chmod; 676 | if (b->ops_mknod != NULL) ops.mknod = bindings_mknod; 677 | if (b->ops_setxattr != NULL) ops.setxattr = bindings_setxattr; 678 | if (b->ops_getxattr != NULL) ops.getxattr = bindings_getxattr; 679 | if (b->ops_listxattr != NULL) ops.listxattr = bindings_listxattr; 680 | if (b->ops_removexattr != NULL) ops.removexattr = bindings_removexattr; 681 | if (b->ops_statfs != NULL) ops.statfs = bindings_statfs; 682 | if (b->ops_open != NULL) ops.open = bindings_open; 683 | if (b->ops_opendir != NULL) ops.opendir = bindings_opendir; 684 | if (b->ops_read != NULL) ops.read = bindings_read; 685 | if (b->ops_write != NULL) ops.write = bindings_write; 686 | if (b->ops_release != NULL) ops.release = bindings_release; 687 | if (b->ops_releasedir != NULL) ops.releasedir = bindings_releasedir; 688 | if (b->ops_create != NULL) ops.create = bindings_create; 689 | if (b->ops_utimens != NULL) ops.utimens = bindings_utimens; 690 | if (b->ops_unlink != NULL) ops.unlink = bindings_unlink; 691 | if (b->ops_rename != NULL) ops.rename = bindings_rename; 692 | if (b->ops_link != NULL) ops.link = bindings_link; 693 | if (b->ops_symlink != NULL) ops.symlink = bindings_symlink; 694 | if (b->ops_mkdir != NULL) ops.mkdir = bindings_mkdir; 695 | if (b->ops_rmdir != NULL) ops.rmdir = bindings_rmdir; 696 | if (b->ops_init != NULL) ops.init = bindings_init; 697 | if (b->ops_destroy != NULL) ops.destroy = bindings_destroy; 698 | 699 | int argc = !strcmp(b->mntopts, "-o") ? 1 : 2; 700 | char *argv[] = { 701 | (char *) "fuse_bindings_dummy", 702 | (char *) b->mntopts 703 | }; 704 | 705 | struct fuse_args args = FUSE_ARGS_INIT(argc, argv); 706 | struct fuse_chan *ch = fuse_mount(b->mnt, &args); 707 | 708 | if (ch == NULL) { 709 | b->op = OP_ERROR; 710 | bindings_call(b); 711 | uv_close((uv_handle_t*) &(b->async), &bindings_on_close); 712 | return NULL; 713 | } 714 | 715 | struct fuse *fuse = fuse_new(ch, &args, &ops, sizeof(struct fuse_operations), b); 716 | 717 | if (fuse == NULL) { 718 | b->op = OP_ERROR; 719 | bindings_call(b); 720 | uv_close((uv_handle_t*) &(b->async), &bindings_on_close); 721 | return NULL; 722 | } 723 | 724 | fuse_loop(fuse); 725 | 726 | fuse_unmount(b->mnt, ch); 727 | fuse_session_remove_chan(ch); 728 | fuse_destroy(fuse); 729 | 730 | uv_close((uv_handle_t*) &(b->async), &bindings_on_close); 731 | 732 | return 0; 733 | } 734 | 735 | NAN_INLINE static Local bindings_get_date (struct timespec *out) { 736 | int ms = (out->tv_nsec / 1000); 737 | return Nan::New(out->tv_sec * 1000 + ms).ToLocalChecked(); 738 | } 739 | 740 | NAN_INLINE static void bindings_set_date (struct timespec *out, Local date) { 741 | double ms = date->NumberValue(); 742 | time_t secs = (time_t)(ms / 1000.0); 743 | time_t rem = ms - (1000.0 * secs); 744 | time_t ns = rem * 1000000.0; 745 | out->tv_sec = secs; 746 | out->tv_nsec = ns; 747 | } 748 | 749 | NAN_INLINE static void bindings_set_stat (struct FUSE_STAT *stat, Local obj) { 750 | if (obj->Has(LOCAL_STRING("dev"))) stat->st_dev = obj->Get(LOCAL_STRING("dev"))->NumberValue(); 751 | if (obj->Has(LOCAL_STRING("ino"))) stat->st_ino = obj->Get(LOCAL_STRING("ino"))->NumberValue(); 752 | if (obj->Has(LOCAL_STRING("mode"))) stat->st_mode = obj->Get(LOCAL_STRING("mode"))->Uint32Value(); 753 | if (obj->Has(LOCAL_STRING("nlink"))) stat->st_nlink = obj->Get(LOCAL_STRING("nlink"))->NumberValue(); 754 | if (obj->Has(LOCAL_STRING("uid"))) stat->st_uid = obj->Get(LOCAL_STRING("uid"))->NumberValue(); 755 | if (obj->Has(LOCAL_STRING("gid"))) stat->st_gid = obj->Get(LOCAL_STRING("gid"))->NumberValue(); 756 | if (obj->Has(LOCAL_STRING("rdev"))) stat->st_rdev = obj->Get(LOCAL_STRING("rdev"))->NumberValue(); 757 | if (obj->Has(LOCAL_STRING("size"))) stat->st_size = obj->Get(LOCAL_STRING("size"))->NumberValue(); 758 | if (obj->Has(LOCAL_STRING("blocks"))) stat->st_blocks = obj->Get(LOCAL_STRING("blocks"))->NumberValue(); 759 | if (obj->Has(LOCAL_STRING("blksize"))) stat->st_blksize = obj->Get(LOCAL_STRING("blksize"))->NumberValue(); 760 | #ifdef __APPLE__ 761 | if (obj->Has(LOCAL_STRING("mtime"))) bindings_set_date(&stat->st_mtimespec, obj->Get(LOCAL_STRING("mtime")).As()); 762 | if (obj->Has(LOCAL_STRING("ctime"))) bindings_set_date(&stat->st_ctimespec, obj->Get(LOCAL_STRING("ctime")).As()); 763 | if (obj->Has(LOCAL_STRING("atime"))) bindings_set_date(&stat->st_atimespec, obj->Get(LOCAL_STRING("atime")).As()); 764 | #else 765 | if (obj->Has(LOCAL_STRING("mtime"))) bindings_set_date(&stat->st_mtim, obj->Get(LOCAL_STRING("mtime")).As()); 766 | if (obj->Has(LOCAL_STRING("ctime"))) bindings_set_date(&stat->st_ctim, obj->Get(LOCAL_STRING("ctime")).As()); 767 | if (obj->Has(LOCAL_STRING("atime"))) bindings_set_date(&stat->st_atim, obj->Get(LOCAL_STRING("atime")).As()); 768 | #endif 769 | } 770 | 771 | NAN_INLINE static void bindings_set_statfs (struct statvfs *statfs, Local obj) { // from http://linux.die.net/man/2/stat 772 | if (obj->Has(LOCAL_STRING("bsize"))) statfs->f_bsize = obj->Get(LOCAL_STRING("bsize"))->Uint32Value(); 773 | if (obj->Has(LOCAL_STRING("frsize"))) statfs->f_frsize = obj->Get(LOCAL_STRING("frsize"))->Uint32Value(); 774 | if (obj->Has(LOCAL_STRING("blocks"))) statfs->f_blocks = obj->Get(LOCAL_STRING("blocks"))->Uint32Value(); 775 | if (obj->Has(LOCAL_STRING("bfree"))) statfs->f_bfree = obj->Get(LOCAL_STRING("bfree"))->Uint32Value(); 776 | if (obj->Has(LOCAL_STRING("bavail"))) statfs->f_bavail = obj->Get(LOCAL_STRING("bavail"))->Uint32Value(); 777 | if (obj->Has(LOCAL_STRING("files"))) statfs->f_files = obj->Get(LOCAL_STRING("files"))->Uint32Value(); 778 | if (obj->Has(LOCAL_STRING("ffree"))) statfs->f_ffree = obj->Get(LOCAL_STRING("ffree"))->Uint32Value(); 779 | if (obj->Has(LOCAL_STRING("favail"))) statfs->f_favail = obj->Get(LOCAL_STRING("favail"))->Uint32Value(); 780 | if (obj->Has(LOCAL_STRING("fsid"))) statfs->f_fsid = obj->Get(LOCAL_STRING("fsid"))->Uint32Value(); 781 | if (obj->Has(LOCAL_STRING("flag"))) statfs->f_flag = obj->Get(LOCAL_STRING("flag"))->Uint32Value(); 782 | if (obj->Has(LOCAL_STRING("namemax"))) statfs->f_namemax = obj->Get(LOCAL_STRING("namemax"))->Uint32Value(); 783 | } 784 | 785 | class SetDirWorker : public Nan::AsyncWorker { 786 | public: 787 | SetDirWorker(bindings_t *b, char **dirs, int dirs_length) 788 | : Nan::AsyncWorker(NULL), b(b), dirs(dirs), dirs_length(dirs_length) {} 789 | ~SetDirWorker() {} 790 | 791 | void Execute () { 792 | fuse_fill_dir_t fillerToCall = b->filler; 793 | void *data = b->data; 794 | for (int i = 0; i < dirs_length; i++) { 795 | fillerToCall(data, dirs[i], &empty_stat, 0); 796 | } 797 | } 798 | void WorkComplete(){ 799 | semaphore_signal(&(b->semaphore_readdir)); 800 | for (int i = 0; i < dirs_length; i++) { 801 | free(dirs[i]); 802 | } 803 | free(dirs); 804 | } 805 | private: 806 | bindings_t *b; 807 | char **dirs; 808 | int dirs_length; 809 | }; 810 | 811 | 812 | NAN_METHOD(OpCallback) { 813 | bindings_t *b = bindings_mounted[info[0]->Uint32Value()]; 814 | b->result = (info.Length() > 1 && info[1]->IsNumber()) ? info[1]->Uint32Value() : 0; 815 | bindings_current = NULL; 816 | 817 | if (!b->result) { 818 | switch (b->op) { 819 | case OP_STATFS: { 820 | if (info.Length() > 2 && info[2]->IsObject()) bindings_set_statfs((struct statvfs *) b->data, info[2].As()); 821 | } 822 | break; 823 | 824 | case OP_GETATTR: 825 | case OP_FGETATTR: { 826 | if (info.Length() > 2 && info[2]->IsObject()) bindings_set_stat((struct FUSE_STAT *) b->data, info[2].As()); 827 | } 828 | break; 829 | 830 | case OP_READDIR: { 831 | if (info.Length() > 2 && info[2]->IsArray()) { 832 | Local dirs = info[2].As(); 833 | 834 | char **dirs_alloc = (char**)malloc(sizeof(char*)*dirs->Length()); 835 | 836 | for (uint32_t i = 0; i < dirs->Length(); i++) { 837 | 838 | Nan::Utf8String dir(dirs->Get(i)); 839 | 840 | dirs_alloc[i] = (char *) malloc(1024); 841 | strcpy(dirs_alloc[i], *dir); 842 | } 843 | 844 | Nan::AsyncQueueWorker(new SetDirWorker(b, dirs_alloc, dirs->Length())); 845 | return; 846 | } 847 | } 848 | break; 849 | 850 | case OP_CREATE: 851 | case OP_OPEN: 852 | case OP_OPENDIR: { 853 | if (info.Length() > 2 && info[2]->IsNumber()) { 854 | b->info->fh = info[2].As()->Uint32Value(); 855 | } 856 | } 857 | break; 858 | 859 | case OP_READLINK: { 860 | if (info.Length() > 2 && info[2]->IsString()) { 861 | Nan::Utf8String path(info[2]); 862 | strcpy((char *) b->data, *path); 863 | } 864 | } 865 | break; 866 | 867 | case OP_INIT: 868 | case OP_ERROR: 869 | case OP_ACCESS: 870 | case OP_FLUSH: 871 | case OP_FSYNC: 872 | case OP_FSYNCDIR: 873 | case OP_TRUNCATE: 874 | case OP_FTRUNCATE: 875 | case OP_CHOWN: 876 | case OP_CHMOD: 877 | case OP_MKNOD: 878 | case OP_SETXATTR: 879 | case OP_GETXATTR: 880 | case OP_LISTXATTR: 881 | case OP_REMOVEXATTR: 882 | case OP_READ: 883 | case OP_UTIMENS: 884 | case OP_WRITE: 885 | case OP_RELEASE: 886 | case OP_RELEASEDIR: 887 | case OP_UNLINK: 888 | case OP_RENAME: 889 | case OP_LINK: 890 | case OP_SYMLINK: 891 | case OP_MKDIR: 892 | case OP_RMDIR: 893 | case OP_DESTROY: 894 | break; 895 | } 896 | } 897 | 898 | semaphore_signal(&(b->semaphore)); 899 | } 900 | 901 | NAN_INLINE static void bindings_call_op_ex (bindings_t *b, Nan::Callback *fn, int argc, Local *argv, bool isreaddir) { 902 | if (fn == NULL){ 903 | if (isreaddir) { 904 | semaphore_signal(&(b->semaphore_readdir)); 905 | } else { 906 | semaphore_signal(&(b->semaphore));} 907 | } 908 | else { 909 | fn->Call(argc, argv); 910 | } 911 | } 912 | 913 | NAN_INLINE static void bindings_call_op (bindings_t *b, Nan::Callback *fn, int argc, Local *argv) { 914 | bindings_call_op_ex(b, fn, argc, argv, false); 915 | } 916 | 917 | static void bindings_dispatch (uv_async_t* handle, int status) { 918 | Nan::HandleScope scope; 919 | 920 | bindings_t *b = bindings_current = (bindings_t *) handle->data; 921 | Local callback = b->callback->GetFunction(); 922 | b->result = -1; 923 | 924 | switch (b->op) { 925 | case OP_INIT: { 926 | Local tmp[] = {callback}; 927 | bindings_call_op(b, b->ops_init, 1, tmp); 928 | } 929 | return; 930 | 931 | case OP_ERROR: { 932 | Local tmp[] = {callback}; 933 | bindings_call_op(b, b->ops_error, 1, tmp); 934 | } 935 | return; 936 | 937 | case OP_STATFS: { 938 | Local tmp[] = {LOCAL_STRING(b->path), callback}; 939 | bindings_call_op(b, b->ops_statfs, 2, tmp); 940 | } 941 | return; 942 | 943 | case OP_FGETATTR: { 944 | Local tmp[] = {LOCAL_STRING(b->path), Nan::New(b->info->fh), callback}; 945 | bindings_call_op(b, b->ops_fgetattr, 3, tmp); 946 | } 947 | return; 948 | 949 | case OP_GETATTR: { 950 | Local tmp[] = {LOCAL_STRING(b->path), callback}; 951 | bindings_call_op(b, b->ops_getattr, 2, tmp); 952 | } 953 | return; 954 | 955 | case OP_READDIR: { 956 | Local tmp[] = {LOCAL_STRING(b->path), callback}; 957 | bindings_call_op_ex(b, b->ops_readdir, 2, tmp, true); 958 | } 959 | return; 960 | 961 | case OP_CREATE: { 962 | Local tmp[] = {LOCAL_STRING(b->path), Nan::New(b->mode), callback}; 963 | bindings_call_op(b, b->ops_create, 3, tmp); 964 | } 965 | return; 966 | 967 | case OP_TRUNCATE: { 968 | Local tmp[] = {LOCAL_STRING(b->path), Nan::New(b->length), callback}; 969 | bindings_call_op(b, b->ops_truncate, 3, tmp); 970 | } 971 | return; 972 | 973 | case OP_FTRUNCATE: { 974 | Local tmp[] = {LOCAL_STRING(b->path), Nan::New(b->info->fh), Nan::New(b->length), callback}; 975 | bindings_call_op(b, b->ops_ftruncate, 4, tmp); 976 | } 977 | return; 978 | 979 | case OP_ACCESS: { 980 | Local tmp[] = {LOCAL_STRING(b->path), Nan::New(b->mode), callback}; 981 | bindings_call_op(b, b->ops_access, 3, tmp); 982 | } 983 | return; 984 | 985 | case OP_OPEN: { 986 | Local tmp[] = {LOCAL_STRING(b->path), Nan::New(b->mode), callback}; 987 | bindings_call_op(b, b->ops_open, 3, tmp); 988 | } 989 | return; 990 | 991 | case OP_OPENDIR: { 992 | Local tmp[] = {LOCAL_STRING(b->path), Nan::New(b->mode), callback}; 993 | bindings_call_op(b, b->ops_opendir, 3, tmp); 994 | } 995 | return; 996 | 997 | case OP_WRITE: { 998 | Local tmp[] = { 999 | LOCAL_STRING(b->path), 1000 | Nan::New(b->info->fh), 1001 | bindings_buffer((char *) b->data, b->length), 1002 | Nan::New(b->length), // TODO: remove me 1003 | Nan::New(b->offset), 1004 | callback 1005 | }; 1006 | bindings_call_op(b, b->ops_write, 6, tmp); 1007 | } 1008 | return; 1009 | 1010 | case OP_READ: { 1011 | Local tmp[] = { 1012 | LOCAL_STRING(b->path), 1013 | Nan::New(b->info->fh), 1014 | bindings_buffer((char *) b->data, b->length), 1015 | Nan::New(b->length), // TODO: remove me 1016 | Nan::New(b->offset), 1017 | callback 1018 | }; 1019 | bindings_call_op(b, b->ops_read, 6, tmp); 1020 | } 1021 | return; 1022 | 1023 | case OP_RELEASE: { 1024 | Local tmp[] = {LOCAL_STRING(b->path), Nan::New(b->info->fh), callback}; 1025 | bindings_call_op(b, b->ops_release, 3, tmp); 1026 | } 1027 | return; 1028 | 1029 | case OP_RELEASEDIR: { 1030 | Local tmp[] = {LOCAL_STRING(b->path), Nan::New(b->info->fh), callback}; 1031 | bindings_call_op(b, b->ops_releasedir, 3, tmp); 1032 | } 1033 | return; 1034 | 1035 | case OP_UNLINK: { 1036 | Local tmp[] = {LOCAL_STRING(b->path), callback}; 1037 | bindings_call_op(b, b->ops_unlink, 2, tmp); 1038 | } 1039 | return; 1040 | 1041 | case OP_RENAME: { 1042 | Local tmp[] = {LOCAL_STRING(b->path), LOCAL_STRING((char *) b->data), callback}; 1043 | bindings_call_op(b, b->ops_rename, 3, tmp); 1044 | } 1045 | return; 1046 | 1047 | case OP_LINK: { 1048 | Local tmp[] = {LOCAL_STRING(b->path), LOCAL_STRING((char *) b->data), callback}; 1049 | bindings_call_op(b, b->ops_link, 3, tmp); 1050 | } 1051 | return; 1052 | 1053 | case OP_SYMLINK: { 1054 | Local tmp[] = {LOCAL_STRING(b->path), LOCAL_STRING((char *) b->data), callback}; 1055 | bindings_call_op(b, b->ops_symlink, 3, tmp); 1056 | } 1057 | return; 1058 | 1059 | case OP_CHMOD: { 1060 | Local tmp[] = {LOCAL_STRING(b->path), Nan::New(b->mode), callback}; 1061 | bindings_call_op(b, b->ops_chmod, 3, tmp); 1062 | } 1063 | return; 1064 | 1065 | case OP_MKNOD: { 1066 | Local tmp[] = {LOCAL_STRING(b->path), Nan::New(b->mode), Nan::New(b->dev), callback}; 1067 | bindings_call_op(b, b->ops_mknod, 4, tmp); 1068 | } 1069 | return; 1070 | 1071 | case OP_CHOWN: { 1072 | Local tmp[] = {LOCAL_STRING(b->path), Nan::New(b->uid), Nan::New(b->gid), callback}; 1073 | bindings_call_op(b, b->ops_chown, 4, tmp); 1074 | } 1075 | return; 1076 | 1077 | case OP_READLINK: { 1078 | Local tmp[] = {LOCAL_STRING(b->path), callback}; 1079 | bindings_call_op(b, b->ops_readlink, 2, tmp); 1080 | } 1081 | return; 1082 | 1083 | case OP_SETXATTR: { 1084 | Local tmp[] = { 1085 | LOCAL_STRING(b->path), 1086 | LOCAL_STRING(b->name), 1087 | bindings_buffer((char *) b->data, b->length), 1088 | Nan::New(b->length), 1089 | Nan::New(b->offset), 1090 | Nan::New(b->mode), 1091 | callback 1092 | }; 1093 | bindings_call_op(b, b->ops_setxattr, 7, tmp); 1094 | } 1095 | return; 1096 | 1097 | case OP_GETXATTR: { 1098 | Local tmp[] = { 1099 | LOCAL_STRING(b->path), 1100 | LOCAL_STRING(b->name), 1101 | bindings_buffer((char *) b->data, b->length), 1102 | Nan::New(b->length), 1103 | Nan::New(b->offset), 1104 | callback 1105 | }; 1106 | bindings_call_op(b, b->ops_getxattr, 6, tmp); 1107 | } 1108 | return; 1109 | 1110 | case OP_LISTXATTR: { 1111 | Local tmp[] = { 1112 | LOCAL_STRING(b->path), 1113 | bindings_buffer((char *) b->data, b->length), 1114 | Nan::New(b->length), 1115 | callback 1116 | }; 1117 | bindings_call_op(b, b->ops_listxattr, 4, tmp); 1118 | } 1119 | return; 1120 | 1121 | case OP_REMOVEXATTR: { 1122 | Local tmp[] = { 1123 | LOCAL_STRING(b->path), 1124 | LOCAL_STRING(b->name), 1125 | callback 1126 | }; 1127 | bindings_call_op(b, b->ops_removexattr, 3, tmp); 1128 | } 1129 | return; 1130 | 1131 | case OP_MKDIR: { 1132 | Local tmp[] = {LOCAL_STRING(b->path), Nan::New(b->mode), callback}; 1133 | bindings_call_op(b, b->ops_mkdir, 3, tmp); 1134 | } 1135 | return; 1136 | 1137 | case OP_RMDIR: { 1138 | Local tmp[] = {LOCAL_STRING(b->path), callback}; 1139 | bindings_call_op(b, b->ops_rmdir, 2, tmp); 1140 | } 1141 | return; 1142 | 1143 | case OP_DESTROY: { 1144 | Local tmp[] = {callback}; 1145 | bindings_call_op(b, b->ops_destroy, 1, tmp); 1146 | } 1147 | return; 1148 | 1149 | case OP_UTIMENS: { 1150 | struct timespec *tv = (struct timespec *) b->data; 1151 | Local tmp[] = {LOCAL_STRING(b->path), bindings_get_date(tv), bindings_get_date(tv + 1), callback}; 1152 | bindings_call_op(b, b->ops_utimens, 4, tmp); 1153 | } 1154 | return; 1155 | 1156 | case OP_FLUSH: { 1157 | Local tmp[] = {LOCAL_STRING(b->path), Nan::New(b->info->fh), callback}; 1158 | bindings_call_op(b, b->ops_flush, 3, tmp); 1159 | } 1160 | return; 1161 | 1162 | case OP_FSYNC: { 1163 | Local tmp[] = {LOCAL_STRING(b->path), Nan::New(b->info->fh), Nan::New(b->mode), callback}; 1164 | bindings_call_op(b, b->ops_fsync, 4, tmp); 1165 | } 1166 | return; 1167 | 1168 | case OP_FSYNCDIR: { 1169 | Local tmp[] = {LOCAL_STRING(b->path), Nan::New(b->info->fh), Nan::New(b->mode), callback}; 1170 | bindings_call_op(b, b->ops_fsyncdir, 4, tmp); 1171 | } 1172 | return; 1173 | } 1174 | 1175 | semaphore_signal(&(b->semaphore)); 1176 | } 1177 | 1178 | static int bindings_alloc () { 1179 | int free_index = -1; 1180 | size_t size = sizeof(bindings_t); 1181 | 1182 | for (int i = 0; i < bindings_mounted_count; i++) { 1183 | if (bindings_mounted[i] == NULL) { 1184 | free_index = i; 1185 | break; 1186 | } 1187 | } 1188 | 1189 | if (free_index == -1 && bindings_mounted_count < 1024) free_index = bindings_mounted_count++; 1190 | if (free_index != -1) { 1191 | bindings_t *b = bindings_mounted[free_index] = (bindings_t *) malloc(size); 1192 | memset(b, 0, size); 1193 | b->index = free_index; 1194 | } 1195 | 1196 | return free_index; 1197 | } 1198 | 1199 | NAN_METHOD(Mount) { 1200 | if (!info[0]->IsString()) return Nan::ThrowError("mnt must be a string"); 1201 | 1202 | mutex_lock(&mutex); 1203 | int index = bindings_alloc(); 1204 | mutex_unlock(&mutex); 1205 | 1206 | if (index == -1) return Nan::ThrowError("You cannot mount more than 1024 filesystem in one process"); 1207 | 1208 | mutex_lock(&mutex); 1209 | bindings_t *b = bindings_mounted[index]; 1210 | mutex_unlock(&mutex); 1211 | 1212 | memset(&empty_stat, 0, sizeof(empty_stat)); 1213 | 1214 | Nan::Utf8String path(info[0]); 1215 | Local ops = info[1].As(); 1216 | 1217 | b->ops_init = LOOKUP_CALLBACK(ops, "init"); 1218 | b->ops_error = LOOKUP_CALLBACK(ops, "error"); 1219 | b->ops_access = LOOKUP_CALLBACK(ops, "access"); 1220 | b->ops_statfs = LOOKUP_CALLBACK(ops, "statfs"); 1221 | b->ops_getattr = LOOKUP_CALLBACK(ops, "getattr"); 1222 | b->ops_fgetattr = LOOKUP_CALLBACK(ops, "fgetattr"); 1223 | b->ops_flush = LOOKUP_CALLBACK(ops, "flush"); 1224 | b->ops_fsync = LOOKUP_CALLBACK(ops, "fsync"); 1225 | b->ops_fsyncdir = LOOKUP_CALLBACK(ops, "fsyncdir"); 1226 | b->ops_readdir = LOOKUP_CALLBACK(ops, "readdir"); 1227 | b->ops_truncate = LOOKUP_CALLBACK(ops, "truncate"); 1228 | b->ops_ftruncate = LOOKUP_CALLBACK(ops, "ftruncate"); 1229 | b->ops_readlink = LOOKUP_CALLBACK(ops, "readlink"); 1230 | b->ops_chown = LOOKUP_CALLBACK(ops, "chown"); 1231 | b->ops_chmod = LOOKUP_CALLBACK(ops, "chmod"); 1232 | b->ops_mknod = LOOKUP_CALLBACK(ops, "mknod"); 1233 | b->ops_setxattr = LOOKUP_CALLBACK(ops, "setxattr"); 1234 | b->ops_getxattr = LOOKUP_CALLBACK(ops, "getxattr"); 1235 | b->ops_listxattr = LOOKUP_CALLBACK(ops, "listxattr"); 1236 | b->ops_removexattr = LOOKUP_CALLBACK(ops, "removexattr"); 1237 | b->ops_open = LOOKUP_CALLBACK(ops, "open"); 1238 | b->ops_opendir = LOOKUP_CALLBACK(ops, "opendir"); 1239 | b->ops_read = LOOKUP_CALLBACK(ops, "read"); 1240 | b->ops_write = LOOKUP_CALLBACK(ops, "write"); 1241 | b->ops_release = LOOKUP_CALLBACK(ops, "release"); 1242 | b->ops_releasedir = LOOKUP_CALLBACK(ops, "releasedir"); 1243 | b->ops_create = LOOKUP_CALLBACK(ops, "create"); 1244 | b->ops_utimens = LOOKUP_CALLBACK(ops, "utimens"); 1245 | b->ops_unlink = LOOKUP_CALLBACK(ops, "unlink"); 1246 | b->ops_rename = LOOKUP_CALLBACK(ops, "rename"); 1247 | b->ops_link = LOOKUP_CALLBACK(ops, "link"); 1248 | b->ops_symlink = LOOKUP_CALLBACK(ops, "symlink"); 1249 | b->ops_mkdir = LOOKUP_CALLBACK(ops, "mkdir"); 1250 | b->ops_rmdir = LOOKUP_CALLBACK(ops, "rmdir"); 1251 | b->ops_destroy = LOOKUP_CALLBACK(ops, "destroy"); 1252 | 1253 | Local tmp[] = {Nan::New(index), Nan::New(OpCallback)->GetFunction()}; 1254 | b->callback = new Nan::Callback(callback_constructor->Call(2, tmp).As()); 1255 | 1256 | strcpy(b->mnt, *path); 1257 | strcpy(b->mntopts, "-o"); 1258 | 1259 | Local options = ops->Get(LOCAL_STRING("options")).As(); 1260 | if (options->IsArray()) { 1261 | for (uint32_t i = 0; i < options->Length(); i++) { 1262 | Nan::Utf8String option(options->Get(i)); 1263 | if (strcmp(b->mntopts, "-o")) strcat(b->mntopts, ","); 1264 | strcat(b->mntopts, *option); 1265 | } 1266 | } 1267 | 1268 | semaphore_init(&(b->semaphore)); 1269 | semaphore_init(&(b->semaphore_readdir)); 1270 | uv_async_init(uv_default_loop(), &(b->async), (uv_async_cb) bindings_dispatch); 1271 | b->async.data = b; 1272 | 1273 | thread_create(&(b->thread), bindings_thread, b); 1274 | } 1275 | 1276 | class UnmountWorker : public Nan::AsyncWorker { 1277 | public: 1278 | UnmountWorker(Nan::Callback *callback, char *path) 1279 | : Nan::AsyncWorker(callback), path(path), result(0) {} 1280 | ~UnmountWorker() {} 1281 | 1282 | void Execute () { 1283 | result = bindings_unmount(path); 1284 | free(path); 1285 | 1286 | if (result != 0) { 1287 | SetErrorMessage("Error"); 1288 | } 1289 | } 1290 | 1291 | void HandleOKCallback () { 1292 | Nan::HandleScope scope; 1293 | callback->Call(0, NULL); 1294 | } 1295 | 1296 | private: 1297 | char *path; 1298 | int result; 1299 | }; 1300 | 1301 | NAN_METHOD(SetCallback) { 1302 | callback_constructor = new Nan::Callback(info[0].As()); 1303 | } 1304 | 1305 | NAN_METHOD(SetBuffer) { 1306 | buffer_constructor.Reset(info[0].As()); 1307 | } 1308 | 1309 | NAN_METHOD(PopulateContext) { 1310 | if (bindings_current == NULL) return Nan::ThrowError("You have to call this inside a fuse operation"); 1311 | 1312 | Local ctx = info[0].As(); 1313 | ctx->Set(LOCAL_STRING("uid"), Nan::New(bindings_current->context_uid)); 1314 | ctx->Set(LOCAL_STRING("gid"), Nan::New(bindings_current->context_gid)); 1315 | ctx->Set(LOCAL_STRING("pid"), Nan::New(bindings_current->context_pid)); 1316 | } 1317 | 1318 | NAN_METHOD(Unmount) { 1319 | if (!info[0]->IsString()) return Nan::ThrowError("mnt must be a string"); 1320 | Nan::Utf8String path(info[0]); 1321 | Local callback = info[1].As(); 1322 | 1323 | char *path_alloc = (char *) malloc(1024); 1324 | strcpy(path_alloc, *path); 1325 | 1326 | Nan::AsyncQueueWorker(new UnmountWorker(new Nan::Callback(callback), path_alloc)); 1327 | } 1328 | 1329 | void Init(Handle exports) { 1330 | exports->Set(LOCAL_STRING("setCallback"), Nan::New(SetCallback)->GetFunction()); 1331 | exports->Set(LOCAL_STRING("setBuffer"), Nan::New(SetBuffer)->GetFunction()); 1332 | exports->Set(LOCAL_STRING("mount"), Nan::New(Mount)->GetFunction()); 1333 | exports->Set(LOCAL_STRING("unmount"), Nan::New(Unmount)->GetFunction()); 1334 | exports->Set(LOCAL_STRING("populateContext"), Nan::New(PopulateContext)->GetFunction()); 1335 | } 1336 | 1337 | NODE_MODULE(fuse_bindings, Init) 1338 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var fuse = require('node-gyp-build')(__dirname) 2 | var fs = require('fs') 3 | var os = require('os') 4 | var xtend = require('xtend') 5 | var path = require('path') 6 | 7 | var noop = function () {} 8 | var call = function (cb) { cb() } 9 | 10 | var IS_OSX = os.platform() === 'darwin' 11 | var OSX_FOLDER_ICON = '/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericFolderIcon.icns' 12 | var HAS_FOLDER_ICON = IS_OSX && fs.existsSync(OSX_FOLDER_ICON) 13 | 14 | var FuseBuffer = function () { 15 | this.length = 0 16 | this.parent = undefined 17 | } 18 | 19 | FuseBuffer.prototype = Buffer.prototype 20 | 21 | fuse.setBuffer(FuseBuffer) 22 | fuse.setCallback(function (index, callback) { 23 | return callback.bind(null, index) 24 | }) 25 | 26 | exports.context = function () { 27 | var ctx = {} 28 | fuse.populateContext(ctx) 29 | return ctx 30 | } 31 | 32 | exports.mount = function (mnt, ops, opts, cb) { 33 | if (typeof opts === 'function') return exports.mount(mnt, ops, null, opts) 34 | if (!cb) cb = noop 35 | 36 | ops = xtend(ops, opts) // clone 37 | if (/\*|(^,)fuse-bindings(,$)/.test(process.env.DEBUG)) ops.options = ['debug'].concat(ops.options || []) 38 | mnt = path.resolve(mnt) 39 | 40 | if (ops.displayFolder && IS_OSX) { // only works on osx 41 | if (!ops.options) ops.options = [] 42 | ops.options.push('volname=' + path.basename(mnt)) 43 | if (HAS_FOLDER_ICON) ops.options.push('volicon=' + OSX_FOLDER_ICON) 44 | } 45 | 46 | var callback = function (err) { 47 | callback = noop 48 | setImmediate(cb.bind(null, err)) 49 | } 50 | 51 | var init = ops.init || call 52 | ops.init = function (next) { 53 | callback() 54 | if (init.length > 1) init(mnt, next) // backwards compat for now 55 | else init(next) 56 | } 57 | 58 | var error = ops.error || call 59 | ops.error = function (next) { 60 | callback(new Error('Mount failed')) 61 | error(next) 62 | } 63 | 64 | if (!ops.getattr) { // we need this for unmount to work on osx 65 | ops.getattr = function (path, cb) { 66 | if (path !== '/') return cb(fuse.EPERM) 67 | cb(null, {mtime: new Date(0), atime: new Date(0), ctime: new Date(0), mode: 16877, size: 4096}) 68 | } 69 | } 70 | 71 | var mount = function () { 72 | // TODO: I got a feeling this can be done better 73 | if (os.platform() !== 'win32') { 74 | fs.stat(mnt, function (err, stat) { 75 | if (err) return cb(new Error('Mountpoint does not exist')) 76 | if (!stat.isDirectory()) return cb(new Error('Mountpoint is not a directory')) 77 | fs.stat(path.join(mnt, '..'), function (_, parent) { 78 | if (parent && parent.dev !== stat.dev) return cb(new Error('Mountpoint in use')) 79 | fuse.mount(mnt, ops) 80 | }) 81 | }) 82 | } else { 83 | fuse.mount(mnt, ops) 84 | } 85 | } 86 | 87 | if (!ops.force) return mount() 88 | exports.unmount(mnt, mount) 89 | } 90 | 91 | exports.unmount = function (mnt, cb) { 92 | fuse.unmount(path.resolve(mnt), cb) 93 | } 94 | 95 | exports.errno = function (code) { 96 | return (code && exports[code.toUpperCase()]) || -1 97 | } 98 | 99 | exports.EPERM = -1 100 | exports.ENOENT = -2 101 | exports.ESRCH = -3 102 | exports.EINTR = -4 103 | exports.EIO = -5 104 | exports.ENXIO = -6 105 | exports.E2BIG = -7 106 | exports.ENOEXEC = -8 107 | exports.EBADF = -9 108 | exports.ECHILD = -10 109 | exports.EAGAIN = -11 110 | exports.ENOMEM = -12 111 | exports.EACCES = -13 112 | exports.EFAULT = -14 113 | exports.ENOTBLK = -15 114 | exports.EBUSY = -16 115 | exports.EEXIST = -17 116 | exports.EXDEV = -18 117 | exports.ENODEV = -19 118 | exports.ENOTDIR = -20 119 | exports.EISDIR = -21 120 | exports.EINVAL = -22 121 | exports.ENFILE = -23 122 | exports.EMFILE = -24 123 | exports.ENOTTY = -25 124 | exports.ETXTBSY = -26 125 | exports.EFBIG = -27 126 | exports.ENOSPC = -28 127 | exports.ESPIPE = -29 128 | exports.EROFS = -30 129 | exports.EMLINK = -31 130 | exports.EPIPE = -32 131 | exports.EDOM = -33 132 | exports.ERANGE = -34 133 | exports.EDEADLK = -35 134 | exports.ENAMETOOLONG = -36 135 | exports.ENOLCK = -37 136 | exports.ENOSYS = -38 137 | exports.ENOTEMPTY = -39 138 | exports.ELOOP = -40 139 | exports.EWOULDBLOCK = -11 140 | exports.ENOMSG = -42 141 | exports.EIDRM = -43 142 | exports.ECHRNG = -44 143 | exports.EL2NSYNC = -45 144 | exports.EL3HLT = -46 145 | exports.EL3RST = -47 146 | exports.ELNRNG = -48 147 | exports.EUNATCH = -49 148 | exports.ENOCSI = -50 149 | exports.EL2HLT = -51 150 | exports.EBADE = -52 151 | exports.EBADR = -53 152 | exports.EXFULL = -54 153 | exports.ENOANO = -55 154 | exports.EBADRQC = -56 155 | exports.EBADSLT = -57 156 | exports.EDEADLOCK = -35 157 | exports.EBFONT = -59 158 | exports.ENOSTR = -60 159 | exports.ENODATA = -61 160 | exports.ETIME = -62 161 | exports.ENOSR = -63 162 | exports.ENONET = -64 163 | exports.ENOPKG = -65 164 | exports.EREMOTE = -66 165 | exports.ENOLINK = -67 166 | exports.EADV = -68 167 | exports.ESRMNT = -69 168 | exports.ECOMM = -70 169 | exports.EPROTO = -71 170 | exports.EMULTIHOP = -72 171 | exports.EDOTDOT = -73 172 | exports.EBADMSG = -74 173 | exports.EOVERFLOW = -75 174 | exports.ENOTUNIQ = -76 175 | exports.EBADFD = -77 176 | exports.EREMCHG = -78 177 | exports.ELIBACC = -79 178 | exports.ELIBBAD = -80 179 | exports.ELIBSCN = -81 180 | exports.ELIBMAX = -82 181 | exports.ELIBEXEC = -83 182 | exports.EILSEQ = -84 183 | exports.ERESTART = -85 184 | exports.ESTRPIPE = -86 185 | exports.EUSERS = -87 186 | exports.ENOTSOCK = -88 187 | exports.EDESTADDRREQ = -89 188 | exports.EMSGSIZE = -90 189 | exports.EPROTOTYPE = -91 190 | exports.ENOPROTOOPT = -92 191 | exports.EPROTONOSUPPORT = -93 192 | exports.ESOCKTNOSUPPORT = -94 193 | exports.EOPNOTSUPP = -95 194 | exports.EPFNOSUPPORT = -96 195 | exports.EAFNOSUPPORT = -97 196 | exports.EADDRINUSE = -98 197 | exports.EADDRNOTAVAIL = -99 198 | exports.ENETDOWN = -100 199 | exports.ENETUNREACH = -101 200 | exports.ENETRESET = -102 201 | exports.ECONNABORTED = -103 202 | exports.ECONNRESET = -104 203 | exports.ENOBUFS = -105 204 | exports.EISCONN = -106 205 | exports.ENOTCONN = -107 206 | exports.ESHUTDOWN = -108 207 | exports.ETOOMANYREFS = -109 208 | exports.ETIMEDOUT = -110 209 | exports.ECONNREFUSED = -111 210 | exports.EHOSTDOWN = -112 211 | exports.EHOSTUNREACH = -113 212 | exports.EALREADY = -114 213 | exports.EINPROGRESS = -115 214 | exports.ESTALE = -116 215 | exports.EUCLEAN = -117 216 | exports.ENOTNAM = -118 217 | exports.ENAVAIL = -119 218 | exports.EISNAM = -120 219 | exports.EREMOTEIO = -121 220 | exports.EDQUOT = -122 221 | exports.ENOMEDIUM = -123 222 | exports.EMEDIUMTYPE = -124 223 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fuse-bindings", 3 | "version": "2.11.2", 4 | "description": "Fully maintained fuse bindings for Node that aims to cover the entire FUSE api", 5 | "main": "index.js", 6 | "scripts": { 7 | "install": "node-gyp-build", 8 | "test": "standard && tape test/*.js", 9 | "prebuild": "prebuildify -a --strip", 10 | "prebuild-ia32": "prebuildify -a --strip --arch=ia32" 11 | }, 12 | "gypfile": true, 13 | "dependencies": { 14 | "nan": "^2.3.5", 15 | "node-gyp-build": "^3.2.2", 16 | "xtend": "^4.0.1" 17 | }, 18 | "devDependencies": { 19 | "concat-stream": "^1.4.7", 20 | "prebuildify": "^2.4.3", 21 | "standard": "^7.1.2", 22 | "tape": "^4.6.0" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/mafintosh/fuse-bindings.git" 27 | }, 28 | "author": "Mathias Buus (@mafintosh)", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/mafintosh/fuse-bindings/issues" 32 | }, 33 | "homepage": "https://github.com/mafintosh/fuse-bindings" 34 | } 35 | -------------------------------------------------------------------------------- /test/fixtures/mnt.js: -------------------------------------------------------------------------------- 1 | var os = require('os') 2 | var path = require('path') 3 | var fs = require('fs') 4 | 5 | var mnt = path.join(os.tmpdir(), 'fuse-bindings-' + process.pid + '-' + Date.now()) 6 | 7 | try { 8 | fs.mkdirSync(mnt) 9 | } catch (err) { 10 | // do nothing 11 | } 12 | 13 | module.exports = mnt 14 | -------------------------------------------------------------------------------- /test/fixtures/stat.js: -------------------------------------------------------------------------------- 1 | module.exports = function (st) { 2 | return { 3 | mtime: st.mtime || new Date(), 4 | atime: st.atime || new Date(), 5 | ctime: st.ctime || new Date(), 6 | size: st.size !== undefined ? st.size : 0, 7 | mode: st.mode === 'dir' ? 16877 : (st.mode === 'file' ? 33188 : (st.mode === 'link' ? 41453 : st.mode)), 8 | uid: st.uid !== undefined ? st.uid : process.getuid(), 9 | gid: st.gid !== undefined ? st.gid : process.getgid() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/links.js: -------------------------------------------------------------------------------- 1 | var mnt = require('./fixtures/mnt') 2 | var stat = require('./fixtures/stat') 3 | var fuse = require('../') 4 | var tape = require('tape') 5 | var fs = require('fs') 6 | var path = require('path') 7 | 8 | tape('readlink', function (t) { 9 | var ops = { 10 | force: true, 11 | readdir: function (path, cb) { 12 | if (path === '/') return cb(null, ['hello', 'link']) 13 | return cb(fuse.ENOENT) 14 | }, 15 | readlink: function (path, cb) { 16 | cb(0, 'hello') 17 | }, 18 | getattr: function (path, cb) { 19 | if (path === '/') return cb(null, stat({mode: 'dir', size: 4096})) 20 | if (path === '/hello') return cb(null, stat({mode: 'file', size: 11})) 21 | if (path === '/link') return cb(null, stat({mode: 'link', size: 5})) 22 | return cb(fuse.ENOENT) 23 | }, 24 | open: function (path, flags, cb) { 25 | cb(0, 42) 26 | }, 27 | read: function (path, fd, buf, len, pos, cb) { 28 | var str = 'hello world'.slice(pos, pos + len) 29 | if (!str) return cb(0) 30 | buf.write(str) 31 | return cb(str.length) 32 | } 33 | } 34 | 35 | fuse.mount(mnt, ops, function (err) { 36 | t.error(err, 'no error') 37 | 38 | fs.lstat(path.join(mnt, 'link'), function (err, stat) { 39 | t.error(err, 'no error') 40 | t.same(stat.size, 5, 'correct size') 41 | 42 | fs.stat(path.join(mnt, 'hello'), function (err, stat) { 43 | t.error(err, 'no error') 44 | t.same(stat.size, 11, 'correct size') 45 | 46 | fs.readlink(path.join(mnt, 'link'), function (err, dest) { 47 | t.error(err, 'no error') 48 | t.same(dest, 'hello', 'link resolves') 49 | 50 | fs.readFile(path.join(mnt, 'link'), function (err, buf) { 51 | t.error(err, 'no error') 52 | t.same(buf, new Buffer('hello world'), 'can read link content') 53 | 54 | fuse.unmount(mnt, function () { 55 | t.end() 56 | }) 57 | }) 58 | }) 59 | }) 60 | }) 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /test/misc.js: -------------------------------------------------------------------------------- 1 | var mnt = require('./fixtures/mnt') 2 | var fuse = require('../') 3 | var tape = require('tape') 4 | 5 | tape('mount', function (t) { 6 | fuse.mount(mnt, {force: true}, function (err) { 7 | t.error(err, 'no error') 8 | t.ok(true, 'works') 9 | fuse.unmount(mnt, function () { 10 | t.end() 11 | }) 12 | }) 13 | }) 14 | 15 | tape('mount + unmount + mount', function (t) { 16 | fuse.mount(mnt, {force: true}, function (err) { 17 | t.error(err, 'no error') 18 | t.ok(true, 'works') 19 | fuse.unmount(mnt, function () { 20 | fuse.mount(mnt, {force: true}, function (err) { 21 | t.error(err, 'no error') 22 | t.ok(true, 'works') 23 | fuse.unmount(mnt, function () { 24 | t.end() 25 | }) 26 | }) 27 | }) 28 | }) 29 | }) 30 | 31 | tape('mnt point must exist', function (t) { 32 | fuse.mount(mnt + '.does-not-exist', {}, function (err) { 33 | t.ok(err, 'had error') 34 | t.end() 35 | }) 36 | }) 37 | 38 | tape('mnt point must be directory', function (t) { 39 | fuse.mount(__filename, {}, function (err) { 40 | t.ok(err, 'had error') 41 | t.end() 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /test/read.js: -------------------------------------------------------------------------------- 1 | var mnt = require('./fixtures/mnt') 2 | var stat = require('./fixtures/stat') 3 | var fuse = require('../') 4 | var tape = require('tape') 5 | var fs = require('fs') 6 | var path = require('path') 7 | var concat = require('concat-stream') 8 | 9 | tape('read', function (t) { 10 | var ops = { 11 | force: true, 12 | readdir: function (path, cb) { 13 | if (path === '/') return cb(null, ['test']) 14 | return cb(fuse.ENOENT) 15 | }, 16 | getattr: function (path, cb) { 17 | if (path === '/') return cb(null, stat({mode: 'dir', size: 4096})) 18 | if (path === '/test') return cb(null, stat({mode: 'file', size: 11})) 19 | return cb(fuse.ENOENT) 20 | }, 21 | open: function (path, flags, cb) { 22 | cb(0, 42) 23 | }, 24 | release: function (path, fd, cb) { 25 | t.same(fd, 42, 'fd was passed to release') 26 | cb(0) 27 | }, 28 | read: function (path, fd, buf, len, pos, cb) { 29 | var str = 'hello world'.slice(pos, pos + len) 30 | if (!str) return cb(0) 31 | buf.write(str) 32 | return cb(str.length) 33 | } 34 | } 35 | 36 | fuse.mount(mnt, ops, function (err) { 37 | t.error(err, 'no error') 38 | 39 | fs.readFile(path.join(mnt, 'test'), function (err, buf) { 40 | t.error(err, 'no error') 41 | t.same(buf, new Buffer('hello world'), 'read file') 42 | 43 | fs.readFile(path.join(mnt, 'test'), function (err, buf) { 44 | t.error(err, 'no error') 45 | t.same(buf, new Buffer('hello world'), 'read file again') 46 | 47 | fs.createReadStream(path.join(mnt, 'test'), {start: 0, end: 4}).pipe(concat(function (buf) { 48 | t.same(buf, new Buffer('hello'), 'partial read file') 49 | 50 | fs.createReadStream(path.join(mnt, 'test'), {start: 6, end: 10}).pipe(concat(function (buf) { 51 | t.same(buf, new Buffer('world'), 'partial read file + start offset') 52 | 53 | fuse.unmount(mnt, function () { 54 | t.end() 55 | }) 56 | })) 57 | })) 58 | }) 59 | }) 60 | }) 61 | }) 62 | -------------------------------------------------------------------------------- /test/write.js: -------------------------------------------------------------------------------- 1 | var mnt = require('./fixtures/mnt') 2 | var stat = require('./fixtures/stat') 3 | var fuse = require('../') 4 | var tape = require('tape') 5 | var fs = require('fs') 6 | var path = require('path') 7 | 8 | tape('write', function (t) { 9 | var created = false 10 | var data = new Buffer(1024) 11 | var size = 0 12 | 13 | var ops = { 14 | force: true, 15 | readdir: function (path, cb) { 16 | if (path === '/') return cb(null, created ? ['hello'] : []) 17 | return cb(fuse.ENOENT) 18 | }, 19 | truncate: function (path, size, cb) { 20 | cb(0) 21 | }, 22 | getattr: function (path, cb) { 23 | if (path === '/') return cb(null, stat({mode: 'dir', size: 4096})) 24 | if (path === '/hello' && created) return cb(null, stat({mode: 'file', size: size})) 25 | return cb(fuse.ENOENT) 26 | }, 27 | create: function (path, flags, cb) { 28 | t.ok(!created, 'file not created yet') 29 | created = true 30 | cb(0, 42) 31 | }, 32 | release: function (path, fd, cb) { 33 | cb(0) 34 | }, 35 | write: function (path, fd, buf, len, pos, cb) { 36 | buf.slice(0, len).copy(data, pos) 37 | size = Math.max(pos + len, size) 38 | cb(buf.length) 39 | } 40 | } 41 | 42 | fuse.mount(mnt, ops, function (err) { 43 | t.error(err, 'no error') 44 | 45 | fs.writeFile(path.join(mnt, 'hello'), 'hello world', function (err) { 46 | t.error(err, 'no error') 47 | t.same(data.slice(0, size), new Buffer('hello world'), 'data was written') 48 | 49 | fuse.unmount(mnt, function () { 50 | t.end() 51 | }) 52 | }) 53 | }) 54 | }) 55 | --------------------------------------------------------------------------------