├── .gitignore ├── .travis.yml ├── README.md ├── bin.js ├── binding.gyp ├── example.js ├── fuse-native.c ├── index.js ├── package.json ├── semaphore.h └── test ├── big.js ├── fixtures ├── mnt.js ├── simple-fs.js └── stat.js ├── helpers └── index.js ├── links.js ├── misc.js ├── read.js ├── statfs.js └── write.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | sudo: required 4 | 5 | osx_image: xcode8.3 6 | 7 | node_js: 8 | - node 9 | 10 | addons: 11 | apt: 12 | sources: 13 | - ubuntu-toolchain-r-test 14 | packages: 15 | - g++-4.8 16 | - gcc-4.8-multilib 17 | - g++-4.8-multilib 18 | - gcc-multilib 19 | - g++-multilib 20 | 21 | os: 22 | - osx 23 | - linux 24 | 25 | before_install: 26 | - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then cinst -y python2; fi 27 | 28 | install: 29 | - npm install 30 | - npm run configure 31 | 32 | before_deploy: 33 | - ARCHIVE_NAME="${TRAVIS_TAG:-latest}-$TRAVIS_OS_NAME.tar" 34 | - npm run prebuild 35 | - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then PREBUILD_ARCH=ia32 npm run prebuild; fi 36 | - cd prebuilds && tar cvf "../$ARCHIVE_NAME" . && cd .. 37 | 38 | cache: 39 | npm: false 40 | 41 | deploy: 42 | provider: releases 43 | draft: false 44 | prerelease: true 45 | api_key: 46 | secure: "KPn3xR4LWcg/H259aSZh26XX0eapR88xSNUkBmEri/sCJSyZ0+asLZSv/HDD3KJP4HeuIKsQc0v8fcebD83fkvaSvlUzSppMQgniwuGC1cAefbrgZDwmJJ/n+lE8Wr9x4adOBTgICS5Uc8LlZ1PuJGm4mmequVs29BEw9738LzN4+3NpoCoWd0FAgGF0tDTsaYL1tJqERAyNIxHS+adUPe0F2r0d2UJ7mOrW7s8Ai6e6QryFsFvA2m0Xn/pQmNO/mcq+LPcw57pWuI3Hm3Cu3W8VPJXYp/yJaFqTAn3D9Fwz4tkmbfmca4ETwZYOS3lvL/rjLQ+69SJlRRu/QfPECkZven+wwsLX/DmyGHgEWqeGWjKj/NxYOIKUKEZZCVrF8cy4j9mac+LK6bAeDZERKSxUJ9GT5WsjvV3RNKgp3MZF7mtmj4IWXfgcuwFX49oIqhzSJsucBBXlB74J7Qua5VJPEAo/7X7Q+Y9IT9JHwxXsXVF5ZNj1PMlJicVD6oKi4XCFOVxSE9wdzlBwMOlUyBGhAIzS6lmxHOELYO9C7l8t/8Zvi4a+YGvOwn0dzLb9zuA1bzqJmEB1fkQMZXHvcEY1o5jSTQ0cNn1Wx4Ck9zyLyhnZ5KRXKzGQ1du55iVOThcbl/8j6zT218SiZMMtv8ZwPy4pJt4skMGsoOZtYlE=" 47 | file: "$ARCHIVE_NAME" 48 | skip_cleanup: true 49 | on: 50 | tags: true 51 | node: 'node' 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fuse-native 2 | [![Build Status](https://travis-ci.org/fuse-friends/fuse-native.svg?branch=master)](https://travis-ci.org/fuse-friends/fuse-native) 3 | 4 | Multithreaded FUSE bindings for Node JS. 5 | 6 | ## Features 7 | 8 | * N-API support means we ship prebuilds and in general works on new Node.js releases. 9 | * Multithreading support means multiple calls to FUSE can run in parallel. 10 | * Close to feature complete in terms of the the FUSE API. 11 | * Embedded shared library support means users do not have to install FUSE from a 3rd party. 12 | * API support for initial FUSE kernel extension configuration so you can control the user experience. 13 | 14 | ## Installation 15 | ``` 16 | npm i fuse-native --save 17 | ``` 18 | 19 | ## Example 20 | ```js 21 | const ops = { 22 | readdir: function (path, cb) { 23 | if (path === '/') return cb(null, ['test']) 24 | return cb(Fuse.ENOENT) 25 | }, 26 | getattr: function (path, cb) { 27 | if (path === '/') return cb(null, stat({ mode: 'dir', size: 4096 })) 28 | if (path === '/test') return cb(null, stat({ mode: 'file', size: 11 })) 29 | return cb(Fuse.ENOENT) 30 | }, 31 | open: function (path, flags, cb) { 32 | return cb(0, 42) 33 | }, 34 | release: function (path, fd, cb) { 35 | return cb(0) 36 | }, 37 | read: function (path, fd, buf, len, pos, cb) { 38 | var str = 'hello world'.slice(pos, pos + len) 39 | if (!str) return cb(0) 40 | buf.write(str) 41 | return cb(str.length) 42 | } 43 | } 44 | 45 | const fuse = new Fuse(mnt, ops, { debug: true }) 46 | fuse.mount(function (err) { 47 | fs.readFile(path.join(mnt, 'test'), function (err, buf) { 48 | // buf should be 'hello world' 49 | }) 50 | }) 51 | ``` 52 | 53 | ## API 54 | In order to create a FUSE mountpoint, you first need to create a `Fuse` object that wraps a set of implemented FUSE syscall handlers: 55 | 56 | #### `const fuse = new Fuse(mnt, handlers, opts = {})` 57 | Create a new `Fuse` object. 58 | 59 | `mnt` is the string path of your desired mountpoint. 60 | 61 | `handlers` is an object mapping syscall names to implementations. The complete list of available syscalls is described below. As an example, if you wanted to implement a filesystem that only supports `getattr`, your handle object would look like: 62 | ```js 63 | { 64 | getattr: function (path, cb) { 65 | if (path === '/') return process.nextTick(cb, null, stat({ mode: 'dir', size: 4096 })) 66 | if (path === '/test') return process.nextTick(cb, null, stat({ mode: 'file', size: 11 })) 67 | return process.nextTick(cb, Fuse.ENOENT) 68 | } 69 | } 70 | ``` 71 | 72 | `opts` can be include: 73 | ``` 74 | displayFolder: 'Folder Name', // Add a name/icon to the mount volume on OSX, 75 | debug: false, // Enable detailed tracing of operations. 76 | force: false, // Attempt to unmount a the mountpoint before remounting. 77 | mkdir: false // Create the mountpoint before mounting. 78 | ``` 79 | Additionally, all (FUSE-specific options)[http://man7.org/linux/man-pages/man8/mount.fuse.8.html] will be passed to the underlying FUSE module (though we use camel casing instead of snake casing). 80 | 81 | #### `Fuse.isConfigured(cb)` 82 | 83 | Returns `true` if FUSE has been configured on your machine and ready to be used, `false` otherwise. 84 | 85 | #### `Fuse.configure(cb)` 86 | 87 | Configures FUSE on your machine by enabling the FUSE kernel extension. 88 | You usually want to do this as part of an installation phase for your app. 89 | Might require `sudo` access. 90 | 91 | #### `Fuse.unconfigure(cb)` 92 | 93 | Unconfigures FUSE on your machine. Basically undos any change the above 94 | method does. 95 | 96 | See the CLI section below on how to run these commands from the command line if you prefer doing that. 97 | 98 | ### FUSE API 99 | 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. 100 | 101 | #### `ops.init(cb)` 102 | 103 | Called on filesystem init. 104 | 105 | #### `ops.access(path, mode, cb)` 106 | 107 | Called before the filesystem accessed a file 108 | 109 | #### `ops.statfs(path, cb)` 110 | 111 | Called when the filesystem is being stat'ed. Accepts a fs stat object after the return code in the callback. 112 | 113 | ``` js 114 | ops.statfs = function (path, cb) { 115 | cb(0, { 116 | bsize: 1000000, 117 | frsize: 1000000, 118 | blocks: 1000000, 119 | bfree: 1000000, 120 | bavail: 1000000, 121 | files: 1000000, 122 | ffree: 1000000, 123 | favail: 1000000, 124 | fsid: 1000000, 125 | flag: 1000000, 126 | namemax: 1000000 127 | }) 128 | } 129 | ``` 130 | 131 | #### `ops.getattr(path, cb)` 132 | 133 | 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. 134 | 135 | ``` js 136 | ops.getattr = function (path, cb) { 137 | cb(0, { 138 | mtime: new Date(), 139 | atime: new Date(), 140 | ctime: new Date(), 141 | size: 100, 142 | mode: 16877, 143 | uid: process.getuid(), 144 | gid: process.getgid() 145 | }) 146 | } 147 | ``` 148 | 149 | #### `ops.fgetattr(path, fd, cb)` 150 | 151 | Same as above but is called when someone stats a file descriptor 152 | 153 | #### `ops.flush(path, fd, cb)` 154 | 155 | Called when a file descriptor is being flushed 156 | 157 | #### `ops.fsync(path, fd, datasync, cb)` 158 | 159 | Called when a file descriptor is being fsync'ed. 160 | 161 | #### `ops.fsyncdir(path, fd, datasync, cb)` 162 | 163 | Same as above but on a directory 164 | 165 | #### `ops.readdir(path, cb)` 166 | 167 | Called when a directory is being listed. Accepts an array of file/directory names after the return code in the callback 168 | 169 | ``` js 170 | ops.readdir = function (path, cb) { 171 | cb(0, ['file-1.txt', 'dir']) 172 | } 173 | ``` 174 | 175 | #### `ops.truncate(path, size, cb)` 176 | 177 | Called when a path is being truncated to a specific size 178 | 179 | #### `ops.ftruncate(path, fd, size, cb)` 180 | 181 | Same as above but on a file descriptor 182 | 183 | #### `ops.readlink(path, cb)` 184 | 185 | Called when a symlink is being resolved. Accepts a pathname (that the link should resolve to) after the return code in the callback 186 | 187 | ``` js 188 | ops.readlink = function (path, cb) { 189 | cb(null, 'file.txt') // make link point to file.txt 190 | } 191 | ``` 192 | 193 | #### `ops.chown(path, uid, gid, cb)` 194 | 195 | Called when ownership of a path is being changed 196 | 197 | #### `ops.chmod(path, mode, cb)` 198 | 199 | Called when the mode of a path is being changed 200 | 201 | #### `ops.mknod(path, mode, dev, cb)` 202 | 203 | Called when the a new device file is being made. 204 | 205 | #### `ops.setxattr(path, name, value, position, flags, cb)` 206 | 207 | Called when extended attributes is being set (see the extended docs for your platform). 208 | 209 | Copy the `value` buffer somewhere to store it. 210 | 211 | The position argument is mostly a legacy argument only used on MacOS but see the getxattr docs 212 | on Mac for more on that (you probably don't need to use that). 213 | 214 | #### `ops.getxattr(path, name, position, cb)` 215 | 216 | Called when extended attributes is being read. 217 | 218 | Return the extended attribute as the second argument to the callback (needs to be a buffer). 219 | If no attribute is stored return `null` as the second argument. 220 | 221 | The position argument is mostly a legacy argument only used on MacOS but see the getxattr docs 222 | on Mac for more on that (you probably don't need to use that). 223 | 224 | #### `ops.listxattr(path, cb)` 225 | 226 | Called when extended attributes of a path are being listed. 227 | 228 | Return a list of strings of the names of the attributes you have stored as the second argument to the callback. 229 | 230 | #### `ops.removexattr(path, name, cb)` 231 | 232 | Called when an extended attribute is being removed. 233 | 234 | #### `ops.open(path, flags, cb)` 235 | 236 | 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. 237 | 238 | ``` js 239 | var toFlag = function(flags) { 240 | flags = flags & 3 241 | if (flags === 0) return 'r' 242 | if (flags === 1) return 'w' 243 | return 'r+' 244 | } 245 | 246 | ops.open = function (path, flags, cb) { 247 | var flag = toFlag(flags) // convert flags to a node style string 248 | ... 249 | cb(0, 42) // 42 is a file descriptor 250 | } 251 | ``` 252 | 253 | #### `ops.opendir(path, flags, cb)` 254 | 255 | Same as above but for directories 256 | 257 | #### `ops.read(path, fd, buffer, length, position, cb)` 258 | 259 | 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. 260 | If no bytes were written (read is complete) return 0 in the callback. 261 | 262 | ``` js 263 | var data = new Buffer('hello world') 264 | 265 | ops.read = function (path, fd, buffer, length, position, cb) { 266 | if (position >= data.length) return cb(0) // done 267 | var part = data.slice(position, position + length) 268 | part.copy(buffer) // write the result of the read to the result buffer 269 | cb(part.length) // return the number of bytes read 270 | } 271 | ``` 272 | 273 | #### `ops.write(path, fd, buffer, length, position, cb)` 274 | 275 | 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. 276 | 277 | ``` js 278 | ops.write = function (path, fd, buffer, length, position, cb) { 279 | console.log('writing', buffer.slice(0, length)) 280 | cb(length) // we handled all the data 281 | } 282 | ``` 283 | 284 | #### `ops.release(path, fd, cb)` 285 | 286 | Called when a file descriptor is being released. Happens when a read/write is done etc. 287 | 288 | #### `ops.releasedir(path, fd, cb)` 289 | 290 | Same as above but for directories 291 | 292 | #### `ops.create(path, mode, cb)` 293 | 294 | Called when a new file is being opened. 295 | 296 | #### `ops.utimens(path, atime, mtime, cb)` 297 | 298 | Called when the atime/mtime of a file is being changed. 299 | 300 | #### `ops.unlink(path, cb)` 301 | 302 | Called when a file is being unlinked. 303 | 304 | #### `ops.rename(src, dest, cb)` 305 | 306 | Called when a file is being renamed. 307 | 308 | #### `ops.link(src, dest, cb)` 309 | 310 | Called when a new link is created. 311 | 312 | #### `ops.symlink(src, dest, cb)` 313 | 314 | Called when a new symlink is created 315 | 316 | #### `ops.mkdir(path, mode, cb)` 317 | 318 | Called when a new directory is being created 319 | 320 | #### `ops.rmdir(path, cb)` 321 | 322 | Called when a directory is being removed 323 | 324 | ## CLI 325 | 326 | There is a CLI tool available to help you configure the FUSE kernel extension setup 327 | if you don't want to use the JavaScript API for that 328 | 329 | ``` 330 | npm install -g fuse-native 331 | fuse-native is-configured # checks if the kernel extension is already configured 332 | fuse-native configure # configures the kernel extension 333 | ``` 334 | 335 | ## License 336 | 337 | MIT for these bindings. 338 | 339 | See the [OSXFUSE](https://github.com/osxfuse/osxfuse) license for MacOS and the [libfuse](https://github.com/libfuse/libfuse) license for Linux/BSD 340 | for the FUSE shared library licence. 341 | -------------------------------------------------------------------------------- /bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const Fuse = require('./') 4 | const cmd = process.argv[2] 5 | 6 | if (cmd === 'configure') { 7 | Fuse.configure(onerror) 8 | } else if (cmd === 'unconfigure') { 9 | Fuse.unconfigure(onerror) 10 | } else if (cmd === 'is-configured') { 11 | Fuse.isConfigured(function (err, bool) { 12 | if (err) return onerror(err) 13 | console.log('' + bool) 14 | process.exit(bool ? 0 : 1) 15 | }) 16 | } 17 | 18 | function onerror (err) { 19 | if (err) throw err 20 | } 21 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [{ 3 | "target_name": "fuse", 4 | "include_dirs": [ 5 | " { 70 | if (err) throw err 71 | console.log('filesystem mounted on ' + fuse.mnt) 72 | }) 73 | 74 | process.once('SIGINT', function () { 75 | fuse.unmount(err => { 76 | if (err) { 77 | console.log('filesystem at ' + fuse.mnt + ' not unmounted', err) 78 | } else { 79 | console.log('filesystem at ' + fuse.mnt + ' unmounted') 80 | } 81 | }) 82 | }) 83 | -------------------------------------------------------------------------------- /fuse-native.c: -------------------------------------------------------------------------------- 1 | #define FUSE_USE_VERSION 29 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | static int IS_ARRAY_BUFFER_DETACH_SUPPORTED = 0; 22 | 23 | napi_status napi_detach_arraybuffer(napi_env env, napi_value buf); 24 | 25 | #define FUSE_NATIVE_CALLBACK(fn, blk)\ 26 | napi_env env = ft->env;\ 27 | napi_handle_scope scope;\ 28 | napi_open_handle_scope(env, &scope);\ 29 | napi_value ctx;\ 30 | napi_get_reference_value(env, ft->ctx, &ctx);\ 31 | napi_value callback;\ 32 | napi_get_reference_value(env, fn, &callback);\ 33 | blk\ 34 | napi_close_handle_scope(env, scope); 35 | 36 | #define FUSE_NATIVE_HANDLER(name, blk)\ 37 | fuse_thread_locals_t *l = get_thread_locals();\ 38 | l->op = op_##name;\ 39 | l->op_fn = fuse_native_dispatch_##name;\ 40 | blk\ 41 | uv_async_send(&(l->async));\ 42 | uv_sem_wait(&(l->sem));\ 43 | return l->res; 44 | 45 | #define FUSE_METHOD(name, callbackArgs, signalArgs, signature, callBlk, callbackBlk, signalBlk)\ 46 | static void fuse_native_dispatch_##name (uv_async_t* handle, fuse_thread_locals_t* l, fuse_thread_t* ft) {\ 47 | uint32_t op = op_##name;\ 48 | FUSE_NATIVE_CALLBACK(ft->handlers[op], {\ 49 | napi_value argv[callbackArgs + 2];\ 50 | napi_get_reference_value(env, l->self, &(argv[0]));\ 51 | napi_create_uint32(env, l->op, &(argv[1]));\ 52 | callbackBlk\ 53 | NAPI_MAKE_CALLBACK(env, NULL, ctx, callback, callbackArgs + 2, argv, NULL)\ 54 | })\ 55 | }\ 56 | NAPI_METHOD(fuse_native_signal_##name) {\ 57 | NAPI_ARGV(signalArgs + 2)\ 58 | NAPI_ARGV_BUFFER_CAST(fuse_thread_locals_t *, l, 0);\ 59 | NAPI_ARGV_INT32(res, 1);\ 60 | signalBlk\ 61 | l->res = res;\ 62 | uv_sem_post(&(l->sem));\ 63 | return NULL;\ 64 | }\ 65 | static int fuse_native_##name signature {\ 66 | FUSE_NATIVE_HANDLER(name, callBlk)\ 67 | } 68 | 69 | #define FUSE_METHOD_VOID(name, callbackArgs, signalArgs, signature, callBlk, callbackBlk)\ 70 | FUSE_METHOD(name, callbackArgs, signalArgs, signature, callBlk, callbackBlk, {}) 71 | 72 | #define FUSE_UINT64_TO_INTS_ARGV(n, pos)\ 73 | uint32_t low##pos = n % 4294967296;\ 74 | uint32_t high##pos = (n - low##pos) / 4294967296;\ 75 | napi_create_uint32(env, low##pos, &(argv[pos]));\ 76 | napi_create_uint32(env, high##pos, &(argv[pos + 1])); 77 | 78 | 79 | // Opcodes 80 | 81 | static const uint32_t op_init = 0; 82 | static const uint32_t op_error = 1; 83 | static const uint32_t op_access = 2; 84 | static const uint32_t op_statfs = 3; 85 | static const uint32_t op_fgetattr = 4; 86 | static const uint32_t op_getattr = 5; 87 | static const uint32_t op_flush = 6; 88 | static const uint32_t op_fsync = 7; 89 | static const uint32_t op_fsyncdir = 8; 90 | static const uint32_t op_readdir = 9; 91 | static const uint32_t op_truncate = 10; 92 | static const uint32_t op_ftruncate = 11; 93 | static const uint32_t op_utimens = 12; 94 | static const uint32_t op_readlink = 13; 95 | static const uint32_t op_chown = 14; 96 | static const uint32_t op_chmod = 15; 97 | static const uint32_t op_mknod = 16; 98 | static const uint32_t op_setxattr = 17; 99 | static const uint32_t op_getxattr = 18; 100 | static const uint32_t op_listxattr = 19; 101 | static const uint32_t op_removexattr = 20; 102 | static const uint32_t op_open = 21; 103 | static const uint32_t op_opendir = 22; 104 | static const uint32_t op_read = 23; 105 | static const uint32_t op_write = 24; 106 | static const uint32_t op_release = 25; 107 | static const uint32_t op_releasedir = 26; 108 | static const uint32_t op_create = 27; 109 | static const uint32_t op_unlink = 28; 110 | static const uint32_t op_rename = 29; 111 | static const uint32_t op_link = 30; 112 | static const uint32_t op_symlink = 31; 113 | static const uint32_t op_mkdir = 32; 114 | static const uint32_t op_rmdir = 33; 115 | 116 | // Data structures 117 | 118 | typedef struct { 119 | napi_env env; 120 | pthread_t thread; 121 | pthread_attr_t attr; 122 | napi_ref ctx; 123 | napi_ref malloc; 124 | 125 | // Operation handlers 126 | napi_ref handlers[35]; 127 | 128 | struct fuse *fuse; 129 | struct fuse_chan *ch; 130 | char mnt[1024]; 131 | char mntopts[1024]; 132 | int mounted; 133 | 134 | uv_async_t async; 135 | uv_mutex_t mut; 136 | uv_sem_t sem; 137 | } fuse_thread_t; 138 | 139 | typedef struct { 140 | napi_ref self; 141 | 142 | // Opcode 143 | uint32_t op; 144 | void *op_fn; 145 | 146 | // Payloads 147 | const char *path; 148 | const char *dest; 149 | char *linkname; 150 | struct fuse_file_info *info; 151 | const void *buf; 152 | off_t offset; 153 | size_t len; 154 | mode_t mode; 155 | dev_t dev; 156 | uid_t uid; 157 | gid_t gid; 158 | size_t atime; 159 | size_t mtime; 160 | int32_t res; 161 | 162 | // Extended attributes 163 | const char *name; 164 | const char *value; 165 | char *list; 166 | size_t size; 167 | uint32_t position; 168 | int flags; 169 | 170 | // Stat + Statfs 171 | struct stat *stat; 172 | struct statvfs *statvfs; 173 | 174 | // Readdir 175 | fuse_fill_dir_t readdir_filler; 176 | 177 | // Internal bookkeeping 178 | fuse_thread_t *fuse; 179 | uv_sem_t sem; 180 | uv_async_t async; 181 | 182 | } fuse_thread_locals_t; 183 | 184 | static pthread_key_t thread_locals_key; 185 | static fuse_thread_locals_t* get_thread_locals(); 186 | 187 | // Helpers 188 | // TODO: Extract into a separate file. 189 | 190 | static uint64_t uint32s_to_uint64 (uint32_t **ints) { 191 | uint64_t low = *((*ints)++); 192 | uint64_t high = *((*ints)++); 193 | return high * 4294967296 + low; 194 | } 195 | 196 | static void uint32s_to_timespec (struct timespec* ts, uint32_t** ints) { 197 | uint64_t ms = uint32s_to_uint64(ints); 198 | ts->tv_sec = ms / 1000; 199 | ts->tv_nsec = (ms % 1000) * 1000000; 200 | } 201 | 202 | static uint64_t timespec_to_uint64 (const struct timespec* ts) { 203 | uint64_t ms = (ts->tv_sec * 1000) + (ts->tv_nsec / 1000000); 204 | return ms; 205 | } 206 | 207 | static void populate_stat (uint32_t *ints, struct stat* stat) { 208 | stat->st_mode = *ints++; 209 | stat->st_uid = *ints++; 210 | stat->st_gid = *ints++; 211 | stat->st_size = uint32s_to_uint64(&ints); 212 | stat->st_dev = *ints++; 213 | stat->st_nlink = *ints++; 214 | stat->st_ino = *ints++; 215 | stat->st_rdev = *ints++; 216 | stat->st_blksize = *ints++; 217 | stat->st_blocks = uint32s_to_uint64(&ints); 218 | #ifdef __APPLE__ 219 | uint32s_to_timespec(&stat->st_atimespec, &ints); 220 | uint32s_to_timespec(&stat->st_mtimespec, &ints); 221 | uint32s_to_timespec(&stat->st_ctimespec, &ints); 222 | #else 223 | uint32s_to_timespec(&stat->st_atim, &ints); 224 | uint32s_to_timespec(&stat->st_mtim, &ints); 225 | uint32s_to_timespec(&stat->st_ctim, &ints); 226 | #endif 227 | } 228 | 229 | static void populate_statvfs (uint32_t *ints, struct statvfs* statvfs) { 230 | statvfs->f_bsize = *ints++; 231 | statvfs->f_frsize = *ints++; 232 | statvfs->f_blocks = *ints++; 233 | statvfs->f_bfree = *ints++; 234 | statvfs->f_bavail = *ints++; 235 | statvfs->f_files = *ints++; 236 | statvfs->f_ffree = *ints++; 237 | statvfs->f_favail = *ints++; 238 | statvfs->f_fsid = *ints++; 239 | statvfs->f_flag = *ints++; 240 | statvfs->f_namemax = *ints++; 241 | } 242 | 243 | // Methods 244 | 245 | FUSE_METHOD(statfs, 1, 1, (const char * path, struct statvfs *statvfs), { 246 | l->path = path; 247 | l->statvfs = statvfs; 248 | }, { 249 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 250 | }, { 251 | NAPI_ARGV_BUFFER_CAST(uint32_t*, ints, 2) 252 | populate_statvfs(ints, l->statvfs); 253 | }) 254 | 255 | FUSE_METHOD(getattr, 1, 1, (const char *path, struct stat *stat), { 256 | l->path = path; 257 | l->stat = stat; 258 | }, { 259 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 260 | }, { 261 | NAPI_ARGV_BUFFER_CAST(uint32_t*, ints, 2) 262 | populate_stat(ints, l->stat); 263 | }) 264 | 265 | FUSE_METHOD(fgetattr, 2, 1, (const char *path, struct stat *stat, struct fuse_file_info *info), { 266 | l->path = path; 267 | l->stat = stat; 268 | l->info = info; 269 | }, { 270 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 271 | if (l->info != NULL) { 272 | napi_create_uint32(env, l->info->fh, &(argv[3])); 273 | } else { 274 | napi_create_uint32(env, 0, &(argv[3])); 275 | } 276 | }, { 277 | NAPI_ARGV_BUFFER_CAST(uint32_t*, ints, 2) 278 | populate_stat(ints, l->stat); 279 | }) 280 | 281 | FUSE_METHOD_VOID(access, 2, 0, (const char *path, int mode), { 282 | l->path = path; 283 | l->mode = mode; 284 | }, { 285 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 286 | napi_create_uint32(env, l->mode, &(argv[3])); 287 | }) 288 | 289 | FUSE_METHOD(open, 2, 1, (const char *path, struct fuse_file_info *info), { 290 | l->path = path; 291 | l->info = info; 292 | }, { 293 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 294 | if (l->info != NULL) { 295 | napi_create_uint32(env, l->info->flags, &(argv[3])); 296 | } else { 297 | napi_create_uint32(env, 0, &(argv[3])); 298 | } 299 | }, { 300 | NAPI_ARGV_INT32(fd, 2) 301 | if (fd != 0) { 302 | l->info->fh = fd; 303 | } 304 | }) 305 | 306 | FUSE_METHOD(opendir, 3, 1, (const char *path, struct fuse_file_info *info), { 307 | l->path = path; 308 | l->info = info; 309 | }, { 310 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 311 | if (l->info != NULL) { 312 | napi_create_uint32(env, l->info->fh, &(argv[3])); 313 | napi_create_uint32(env, l->info->flags, &(argv[4])); 314 | } else { 315 | napi_create_uint32(env, 0, &(argv[3])); 316 | napi_create_uint32(env, 0, &(argv[4])); 317 | } 318 | }, { 319 | NAPI_ARGV_INT32(fd, 2) 320 | if (fd != 0) { 321 | l->info->fh = fd; 322 | } 323 | }) 324 | 325 | FUSE_METHOD(create, 2, 1, (const char *path, mode_t mode, struct fuse_file_info *info), { 326 | l->path = path; 327 | l->mode = mode; 328 | l->info = info; 329 | }, { 330 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 331 | napi_create_uint32(env, l->mode, &(argv[3])); 332 | }, { 333 | NAPI_ARGV_INT32(fd, 2) 334 | if (fd != 0) { 335 | l->info->fh = fd; 336 | } 337 | }) 338 | 339 | FUSE_METHOD_VOID(utimens, 5, 0, (const char *path, const struct timespec tv[2]), { 340 | l->path = path; 341 | l->atime = timespec_to_uint64(&tv[0]); 342 | l->mtime = timespec_to_uint64(&tv[1]); 343 | }, { 344 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 345 | FUSE_UINT64_TO_INTS_ARGV(l->atime, 3) 346 | FUSE_UINT64_TO_INTS_ARGV(l->atime, 5) 347 | }) 348 | 349 | FUSE_METHOD_VOID(release, 2, 0, (const char *path, struct fuse_file_info *info), { 350 | l->path = path; 351 | l->info = info; 352 | }, { 353 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 354 | if (l->info != NULL) { 355 | napi_create_uint32(env, l->info->fh, &(argv[3])); 356 | } else { 357 | napi_create_uint32(env, 0, &(argv[3])); 358 | } 359 | }) 360 | 361 | FUSE_METHOD_VOID(releasedir, 2, 0, (const char *path, struct fuse_file_info *info), { 362 | l->path = path; 363 | l->info = info; 364 | }, { 365 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 366 | if (l->info != NULL) { 367 | napi_create_uint32(env, l->info->fh, &(argv[3])); 368 | } else { 369 | napi_create_uint32(env, 0, &(argv[3])); 370 | } 371 | }) 372 | 373 | FUSE_METHOD(read, 6, 2, (const char *path, char *buf, size_t len, off_t offset, struct fuse_file_info *info), { 374 | l->path = path; 375 | l->buf = buf; 376 | l->len = len; 377 | l->offset = offset; 378 | l->info = info; 379 | }, { 380 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 381 | napi_create_uint32(env, l->info->fh, &(argv[3])); 382 | napi_create_external_buffer(env, l->len, (char *) l->buf, NULL, NULL, &(argv[4])); 383 | napi_create_uint32(env, l->len, &(argv[5])); 384 | FUSE_UINT64_TO_INTS_ARGV(l->offset, 6) 385 | }, { 386 | if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[3]) == napi_ok); 387 | }) 388 | 389 | FUSE_METHOD(write, 6, 2, (const char *path, const char *buf, size_t len, off_t offset, struct fuse_file_info *info), { 390 | l->path = path; 391 | l->buf = buf; 392 | l->len = len; 393 | l->offset = offset; 394 | l->info = info; 395 | }, { 396 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 397 | napi_create_uint32(env, l->info->fh, &(argv[3])); 398 | napi_create_external_buffer(env, l->len, (char *) l->buf, NULL, NULL, &(argv[4])); 399 | napi_create_uint32(env, l->len, &(argv[5])); 400 | FUSE_UINT64_TO_INTS_ARGV(l->offset, 6) 401 | }, { 402 | if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[3]) == napi_ok); 403 | }) 404 | 405 | FUSE_METHOD(readdir, 1, 2, (const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *info), { 406 | l->buf = buf; 407 | l->path = path; 408 | l->offset = offset; 409 | l->info = info; 410 | l->readdir_filler = filler; 411 | }, { 412 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 413 | }, { 414 | uint32_t stats_length; 415 | uint32_t names_length; 416 | napi_get_array_length(env, argv[3], &stats_length); 417 | napi_get_array_length(env, argv[2], &names_length); 418 | 419 | napi_value raw_names = argv[2]; 420 | napi_value raw_stats = argv[3]; 421 | 422 | if (names_length != stats_length) { 423 | NAPI_FOR_EACH(raw_names, raw_name) { 424 | NAPI_UTF8(name, 1024, raw_name) 425 | int err = l->readdir_filler((char *) l->buf, name, NULL, 0); 426 | if (err == 1) { 427 | break; 428 | } 429 | } 430 | } else { 431 | NAPI_FOR_EACH(raw_names, raw_name) { 432 | NAPI_UTF8(name, 1024, raw_name) 433 | napi_value raw_stat; 434 | napi_get_element(env, raw_stats, i, &raw_stat); 435 | 436 | NAPI_BUFFER_CAST(uint32_t*, stats_array, raw_stat); 437 | struct stat st; 438 | populate_stat(stats_array, &st); 439 | 440 | // TODO: It turns out readdirplus likely won't work with FUSE 29... 441 | // Metadata caching between readdir/getattr will be enabled when we upgrade fuse-shared-library 442 | int err = l->readdir_filler((char *) l->buf, name, (struct stat *) &st, 0); 443 | if (err == 1) { 444 | break; 445 | } 446 | } 447 | } 448 | }) 449 | 450 | #ifdef __APPLE__ 451 | 452 | FUSE_METHOD(setxattr, 5, 1, (const char *path, const char *name, const char *value, size_t size, int flags, uint32_t position), { 453 | l->path = path; 454 | l->name = name; 455 | l->value = value; 456 | l->size = size; 457 | l->flags = flags; 458 | l->position = position; 459 | }, { 460 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 461 | napi_create_string_utf8(env, l->name, NAPI_AUTO_LENGTH, &(argv[3])); 462 | napi_create_external_buffer(env, l->size, (char *) l->value, NULL, NULL, &(argv[4])); 463 | napi_create_uint32(env, l->position, &(argv[5])); 464 | napi_create_uint32(env, l->flags, &(argv[6])); 465 | }, { 466 | if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[2]) == napi_ok); 467 | }) 468 | 469 | FUSE_METHOD(getxattr, 4, 1, (const char *path, const char *name, char *value, size_t size, uint32_t position), { 470 | l->path = path; 471 | l->name = name; 472 | l->value = value; 473 | l->size = size; 474 | l->position = position; 475 | }, { 476 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 477 | napi_create_string_utf8(env, l->name, NAPI_AUTO_LENGTH, &(argv[3])); 478 | napi_create_external_buffer(env, l->size, (char *) l->value, NULL, NULL, &(argv[4])); 479 | napi_create_uint32(env, l->position, &(argv[5])); 480 | }, { 481 | if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[2]) == napi_ok); 482 | }) 483 | 484 | #else 485 | 486 | FUSE_METHOD(setxattr, 5, 1, (const char *path, const char *name, const char *value, size_t size, int flags), { 487 | l->path = path; 488 | l->name = name; 489 | l->value = value; 490 | l->size = size; 491 | l->flags = flags; 492 | }, { 493 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 494 | napi_create_string_utf8(env, l->name, NAPI_AUTO_LENGTH, &(argv[3])); 495 | napi_create_external_buffer(env, l->size, (char *) l->value, NULL, NULL, &(argv[4])); 496 | napi_create_uint32(env, 0, &(argv[5])); // normalize apis between mac and linux 497 | napi_create_uint32(env, l->flags, &(argv[6])); 498 | }, { 499 | if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[2]) == napi_ok); 500 | }) 501 | 502 | FUSE_METHOD(getxattr, 4, 1, (const char *path, const char *name, char *value, size_t size), { 503 | l->path = path; 504 | l->name = name; 505 | l->value = value; 506 | l->size = size; 507 | }, { 508 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 509 | napi_create_string_utf8(env, l->name, NAPI_AUTO_LENGTH, &(argv[3])); 510 | napi_create_external_buffer(env, l->size, (char *) l->value, NULL, NULL, &(argv[4])); 511 | napi_create_uint32(env, 0, &(argv[5])); 512 | }, { 513 | if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[2]) == napi_ok); 514 | }) 515 | 516 | #endif 517 | 518 | FUSE_METHOD(listxattr, 2, 1, (const char *path, char *list, size_t size), { 519 | l->path = path; 520 | l->list = list; 521 | l->size = size; 522 | }, { 523 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 524 | napi_create_external_buffer(env, l->size, l->list, NULL, NULL, &(argv[3])); 525 | }, { 526 | if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[2]) == napi_ok); 527 | }) 528 | 529 | FUSE_METHOD_VOID(removexattr, 2, 0, (const char *path, const char *name), { 530 | l->path = path; 531 | l->name = name; 532 | }, { 533 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 534 | napi_create_string_utf8(env, l->name, NAPI_AUTO_LENGTH, &(argv[3])); 535 | }) 536 | 537 | FUSE_METHOD_VOID(flush, 2, 0, (const char *path, struct fuse_file_info *info), { 538 | l->path = path; 539 | l->info = info; 540 | }, { 541 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 542 | if (l->info != NULL) { 543 | napi_create_uint32(env, l->info->fh, &(argv[3])); 544 | } else { 545 | napi_create_uint32(env, 0, &(argv[3])); 546 | } 547 | }) 548 | 549 | FUSE_METHOD_VOID(fsync, 3, 0, (const char *path, int datasync, struct fuse_file_info *info), { 550 | l->path = path; 551 | l->mode = datasync; 552 | l->info = info; 553 | }, { 554 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 555 | napi_create_uint32(env, l->mode, &(argv[3])); 556 | if (l->info != NULL) { 557 | napi_create_uint32(env, l->info->fh, &(argv[4])); 558 | } else { 559 | napi_create_uint32(env, 0, &(argv[4])); 560 | } 561 | }) 562 | 563 | FUSE_METHOD_VOID(fsyncdir, 3, 0, (const char *path, int datasync, struct fuse_file_info *info), { 564 | l->path = path; 565 | l->mode = datasync; 566 | l->info = info; 567 | }, { 568 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 569 | napi_create_uint32(env, l->mode, &(argv[3])); 570 | if (l->info != NULL) { 571 | napi_create_uint32(env, l->info->fh, &(argv[4])); 572 | } else { 573 | napi_create_uint32(env, 0, &(argv[4])); 574 | } 575 | }) 576 | 577 | 578 | FUSE_METHOD_VOID(truncate, 3, 0, (const char *path, off_t size), { 579 | l->path = path; 580 | l->offset = size; 581 | }, { 582 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 583 | FUSE_UINT64_TO_INTS_ARGV(l->offset, 3) 584 | }) 585 | 586 | FUSE_METHOD_VOID(ftruncate, 4, 0, (const char *path, off_t size, struct fuse_file_info *info), { 587 | l->path = path; 588 | l->offset = size; 589 | l->info = info; 590 | }, { 591 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 592 | if (l->info != NULL) { 593 | napi_create_uint32(env, l->info->fh, &(argv[3])); 594 | } else { 595 | napi_create_uint32(env, 0, &(argv[3])); 596 | } 597 | FUSE_UINT64_TO_INTS_ARGV(l->offset, 4) 598 | }) 599 | 600 | FUSE_METHOD(readlink, 1, 1, (const char *path, char *linkname, size_t len), { 601 | l->path = path; 602 | l->linkname = linkname; 603 | l->len = len; 604 | }, { 605 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 606 | }, { 607 | NAPI_ARGV_UTF8(linkname, l->len, 2) 608 | strncpy(l->linkname, linkname, l->len); 609 | }) 610 | 611 | FUSE_METHOD_VOID(chown, 3, 0, (const char *path, uid_t uid, gid_t gid), { 612 | l->path = path; 613 | l->uid = uid; 614 | l->gid = gid; 615 | }, { 616 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 617 | napi_create_uint32(env, l->uid, &(argv[3])); 618 | napi_create_uint32(env, l->gid, &(argv[4])); 619 | }) 620 | 621 | FUSE_METHOD_VOID(chmod, 2, 0, (const char *path, mode_t mode), { 622 | l->path = path; 623 | l->mode = mode; 624 | }, { 625 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 626 | napi_create_uint32(env, l->mode, &(argv[3])); 627 | }) 628 | 629 | FUSE_METHOD_VOID(mknod, 3, 0, (const char *path, mode_t mode, dev_t dev), { 630 | l->path = path; 631 | l->mode = mode; 632 | l->dev = dev; 633 | }, { 634 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 635 | napi_create_uint32(env, l->mode, &(argv[3])); 636 | napi_create_uint32(env, l->dev, &(argv[4])); 637 | }) 638 | 639 | FUSE_METHOD_VOID(unlink, 1, 0, (const char *path), { 640 | l->path = path; 641 | }, { 642 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 643 | }) 644 | 645 | FUSE_METHOD_VOID(rename, 2, 0, (const char *path, const char *dest), { 646 | l->path = path; 647 | l->dest = dest; 648 | }, { 649 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 650 | napi_create_string_utf8(env, l->dest, NAPI_AUTO_LENGTH, &(argv[3])); 651 | }) 652 | 653 | FUSE_METHOD_VOID(link, 2, 0, (const char *path, const char *dest), { 654 | l->path = path; 655 | l->dest = dest; 656 | }, { 657 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 658 | napi_create_string_utf8(env, l->dest, NAPI_AUTO_LENGTH, &(argv[3])); 659 | }) 660 | 661 | FUSE_METHOD_VOID(symlink, 2, 0, (const char *path, const char *dest), { 662 | l->path = path; 663 | l->dest = dest; 664 | }, { 665 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 666 | napi_create_string_utf8(env, l->dest, NAPI_AUTO_LENGTH, &(argv[3])); 667 | }) 668 | 669 | FUSE_METHOD_VOID(mkdir, 2, 0, (const char *path, mode_t mode), { 670 | l->path = path; 671 | l->mode = mode; 672 | }, { 673 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 674 | napi_create_uint32(env, l->mode, &(argv[3])); 675 | }) 676 | 677 | FUSE_METHOD_VOID(rmdir, 1, 0, (const char *path), { 678 | l->path = path; 679 | }, { 680 | napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 681 | }) 682 | 683 | static void fuse_native_dispatch_init (uv_async_t* handle, fuse_thread_locals_t* l, fuse_thread_t* ft) {\ 684 | FUSE_NATIVE_CALLBACK(ft->handlers[op_init], { 685 | napi_value argv[2]; 686 | 687 | napi_get_reference_value(env, l->self, &(argv[0])); 688 | napi_create_uint32(env, l->op, &(argv[1])); 689 | 690 | NAPI_MAKE_CALLBACK(env, NULL, ctx, callback, 2, argv, NULL); 691 | }) 692 | } 693 | 694 | NAPI_METHOD(fuse_native_signal_init) { 695 | NAPI_ARGV(2) 696 | NAPI_ARGV_BUFFER_CAST(fuse_thread_locals_t *, l, 0); 697 | NAPI_ARGV_INT32(res, 1); 698 | l->res = res; 699 | uv_sem_post(&(l->sem)); 700 | return NULL; 701 | } 702 | 703 | static void * fuse_native_init (struct fuse_conn_info *conn) { 704 | fuse_thread_locals_t *l = get_thread_locals(); 705 | 706 | l->op = op_init; 707 | l->op_fn = fuse_native_dispatch_init; 708 | 709 | uv_async_send(&(l->async)); 710 | uv_sem_wait(&(l->sem)); 711 | 712 | return l->fuse; 713 | } 714 | 715 | // Top-level dispatcher 716 | 717 | static void fuse_native_dispatch (uv_async_t* handle) { 718 | fuse_thread_locals_t *l = (fuse_thread_locals_t *) handle->data; 719 | fuse_thread_t *ft = l->fuse; 720 | void (*fn)(uv_async_t *, fuse_thread_locals_t *, fuse_thread_t *) = l->op_fn; 721 | 722 | fn(handle, l, ft); 723 | } 724 | 725 | static void fuse_native_async_init (uv_async_t* handle) { 726 | fuse_thread_t *ft = (fuse_thread_t *) handle->data; 727 | fuse_thread_locals_t *l; 728 | 729 | FUSE_NATIVE_CALLBACK(ft->malloc, { 730 | napi_value argv[1]; 731 | napi_create_uint32(ft->env, (uint32_t) sizeof(fuse_thread_locals_t), &(argv[0])); 732 | 733 | napi_value buf; 734 | NAPI_MAKE_CALLBACK(ft->env, NULL, ctx, callback, 1, argv, &buf); 735 | 736 | size_t l_len; 737 | 738 | napi_get_buffer_info(env, buf, (void **) &l, &l_len); 739 | napi_create_reference(env, buf, 1, &(l->self)); 740 | }) 741 | 742 | 743 | int err = uv_async_init(uv_default_loop(), &(l->async), (uv_async_cb) fuse_native_dispatch); 744 | assert(err >= 0); 745 | 746 | uv_unref((uv_handle_t *) &(l->async)); 747 | 748 | uv_sem_init(&(l->sem), 0); 749 | l->async.data = l; 750 | ft->async.data = l; 751 | l->fuse = ft; 752 | 753 | uv_sem_post(&(ft->sem)); 754 | } 755 | 756 | static fuse_thread_locals_t* get_thread_locals () { 757 | struct fuse_context *ctx = fuse_get_context(); 758 | fuse_thread_t *ft = (fuse_thread_t *) ctx->private_data; 759 | 760 | void *data = pthread_getspecific(thread_locals_key); 761 | 762 | if (data != NULL) { 763 | return (fuse_thread_locals_t *) data; 764 | } 765 | 766 | // Need to lock the mutation of l->async. 767 | uv_mutex_lock(&(ft->mut)); 768 | ft->async.data = ft; 769 | 770 | // Notify the main thread to uv_async_init l->async. 771 | uv_async_send(&(ft->async)); 772 | uv_sem_wait(&(ft->sem)); 773 | 774 | fuse_thread_locals_t *l = (fuse_thread_locals_t*) ft->async.data; 775 | 776 | pthread_setspecific(thread_locals_key, (void *) l); 777 | uv_mutex_unlock(&(ft->mut)); 778 | 779 | return l; 780 | } 781 | 782 | static void* start_fuse_thread (void *data) { 783 | fuse_thread_t *ft = (fuse_thread_t *) data; 784 | fuse_loop_mt(ft->fuse); 785 | 786 | fuse_unmount(ft->mnt, ft->ch); 787 | fuse_session_remove_chan(ft->ch); 788 | fuse_destroy(ft->fuse); 789 | 790 | return NULL; 791 | } 792 | 793 | NAPI_METHOD(fuse_native_mount) { 794 | NAPI_ARGV(7) 795 | 796 | NAPI_ARGV_UTF8(mnt, 1024, 0); 797 | NAPI_ARGV_UTF8(mntopts, 1024, 1); 798 | NAPI_ARGV_BUFFER_CAST(fuse_thread_t *, ft, 2); 799 | napi_create_reference(env, argv[3], 1, &(ft->ctx)); 800 | napi_create_reference(env, argv[4], 1, &(ft->malloc)); 801 | napi_value handlers = argv[5]; 802 | NAPI_ARGV_BUFFER_CAST(uint32_t *, implemented, 6) 803 | 804 | for (int i = 0; i < 35; i++) { 805 | ft->handlers[i] = NULL; 806 | } 807 | 808 | NAPI_FOR_EACH(handlers, handler) { 809 | napi_create_reference(env, handler, 1, &ft->handlers[i]); 810 | } 811 | 812 | ft->env = env; 813 | 814 | struct fuse_operations ops = { }; 815 | if (implemented[op_access]) ops.access = fuse_native_access; 816 | if (implemented[op_truncate]) ops.truncate = fuse_native_truncate; 817 | if (implemented[op_ftruncate]) ops.ftruncate = fuse_native_ftruncate; 818 | if (implemented[op_getattr]) ops.getattr = fuse_native_getattr; 819 | if (implemented[op_fgetattr]) ops.fgetattr = fuse_native_fgetattr; 820 | if (implemented[op_flush]) ops.flush = fuse_native_flush; 821 | if (implemented[op_fsync]) ops.fsync = fuse_native_fsync; 822 | if (implemented[op_fsyncdir]) ops.fsyncdir = fuse_native_fsyncdir; 823 | if (implemented[op_readdir]) ops.readdir = fuse_native_readdir; 824 | if (implemented[op_readlink]) ops.readlink = fuse_native_readlink; 825 | if (implemented[op_chown]) ops.chown = fuse_native_chown; 826 | if (implemented[op_chmod]) ops.chmod = fuse_native_chmod; 827 | if (implemented[op_mknod]) ops.mknod = fuse_native_mknod; 828 | if (implemented[op_setxattr]) ops.setxattr = fuse_native_setxattr; 829 | if (implemented[op_getxattr]) ops.getxattr = fuse_native_getxattr; 830 | if (implemented[op_listxattr]) ops.listxattr = fuse_native_listxattr; 831 | if (implemented[op_removexattr]) ops.removexattr = fuse_native_removexattr; 832 | if (implemented[op_statfs]) ops.statfs = fuse_native_statfs; 833 | if (implemented[op_open]) ops.open = fuse_native_open; 834 | if (implemented[op_opendir]) ops.opendir = fuse_native_opendir; 835 | if (implemented[op_read]) ops.read = fuse_native_read; 836 | if (implemented[op_write]) ops.write = fuse_native_write; 837 | if (implemented[op_release]) ops.release = fuse_native_release; 838 | if (implemented[op_releasedir]) ops.releasedir = fuse_native_releasedir; 839 | if (implemented[op_create]) ops.create = fuse_native_create; 840 | if (implemented[op_utimens]) ops.utimens = fuse_native_utimens; 841 | if (implemented[op_unlink]) ops.unlink = fuse_native_unlink; 842 | if (implemented[op_rename]) ops.rename = fuse_native_rename; 843 | if (implemented[op_link]) ops.link = fuse_native_link; 844 | if (implemented[op_symlink]) ops.symlink = fuse_native_symlink; 845 | if (implemented[op_mkdir]) ops.mkdir = fuse_native_mkdir; 846 | if (implemented[op_rmdir]) ops.rmdir = fuse_native_rmdir; 847 | if (implemented[op_init]) ops.init = fuse_native_init; 848 | 849 | int _argc = (strcmp(mntopts, "-o") <= 0) ? 1 : 2; 850 | char *_argv[] = { 851 | (char *) "fuse_bindings_dummy", 852 | (char *) mntopts 853 | }; 854 | 855 | struct fuse_args args = FUSE_ARGS_INIT(_argc, _argv); 856 | struct fuse_chan *ch = fuse_mount(mnt, &args); 857 | 858 | if (ch == NULL) { 859 | napi_throw_error(env, "fuse failed", "fuse failed"); 860 | return NULL; 861 | } 862 | 863 | struct fuse *fuse = fuse_new(ch, &args, &ops, sizeof(struct fuse_operations), ft); 864 | 865 | uv_mutex_init(&(ft->mut)); 866 | uv_sem_init(&(ft->sem), 0); 867 | 868 | strncpy(ft->mnt, mnt, 1024); 869 | strncpy(ft->mntopts, mntopts, 1024); 870 | ft->fuse = fuse; 871 | ft->ch = ch; 872 | ft->mounted++; 873 | 874 | int err = uv_async_init(uv_default_loop(), &(ft->async), (uv_async_cb) fuse_native_async_init); 875 | 876 | if (fuse == NULL || err < 0) { 877 | napi_throw_error(env, "fuse failed", "fuse failed"); 878 | return NULL; 879 | } 880 | 881 | pthread_attr_init(&(ft->attr)); 882 | pthread_create(&(ft->thread), &(ft->attr), start_fuse_thread, ft); 883 | 884 | return NULL; 885 | } 886 | 887 | NAPI_METHOD(fuse_native_unmount) { 888 | NAPI_ARGV(2) 889 | NAPI_ARGV_UTF8(mnt, 1024, 0); 890 | NAPI_ARGV_BUFFER_CAST(fuse_thread_t *, ft, 1); 891 | 892 | if (ft != NULL && ft->mounted) { 893 | // TODO: Investigate why the FUSE thread is not always killed after fusermount. 894 | // pthread_join(ft->thread, NULL); 895 | } 896 | 897 | // TODO: fix the async holding the loop 898 | uv_unref((uv_handle_t *) &(ft->async)); 899 | ft->mounted--; 900 | 901 | return NULL; 902 | } 903 | 904 | NAPI_INIT() { 905 | const napi_node_version* version; 906 | assert(napi_get_node_version(env, &version) == napi_ok); 907 | 908 | if (version->major > 12 || (version->major == 12 && version->minor >= 16)) { 909 | IS_ARRAY_BUFFER_DETACH_SUPPORTED = 1; 910 | } 911 | 912 | pthread_key_create(&(thread_locals_key), NULL); // TODO: add destructor 913 | 914 | NAPI_EXPORT_SIZEOF(fuse_thread_t) 915 | 916 | NAPI_EXPORT_FUNCTION(fuse_native_mount) 917 | NAPI_EXPORT_FUNCTION(fuse_native_unmount) 918 | 919 | NAPI_EXPORT_FUNCTION(fuse_native_signal_getattr) 920 | NAPI_EXPORT_FUNCTION(fuse_native_signal_init) 921 | NAPI_EXPORT_FUNCTION(fuse_native_signal_access) 922 | NAPI_EXPORT_FUNCTION(fuse_native_signal_statfs) 923 | NAPI_EXPORT_FUNCTION(fuse_native_signal_fgetattr) 924 | NAPI_EXPORT_FUNCTION(fuse_native_signal_getattr) 925 | NAPI_EXPORT_FUNCTION(fuse_native_signal_flush) 926 | NAPI_EXPORT_FUNCTION(fuse_native_signal_fsync) 927 | NAPI_EXPORT_FUNCTION(fuse_native_signal_fsyncdir) 928 | NAPI_EXPORT_FUNCTION(fuse_native_signal_readdir) 929 | NAPI_EXPORT_FUNCTION(fuse_native_signal_truncate) 930 | NAPI_EXPORT_FUNCTION(fuse_native_signal_ftruncate) 931 | NAPI_EXPORT_FUNCTION(fuse_native_signal_utimens) 932 | NAPI_EXPORT_FUNCTION(fuse_native_signal_readlink) 933 | NAPI_EXPORT_FUNCTION(fuse_native_signal_chown) 934 | NAPI_EXPORT_FUNCTION(fuse_native_signal_chmod) 935 | NAPI_EXPORT_FUNCTION(fuse_native_signal_mknod) 936 | NAPI_EXPORT_FUNCTION(fuse_native_signal_setxattr) 937 | NAPI_EXPORT_FUNCTION(fuse_native_signal_getxattr) 938 | NAPI_EXPORT_FUNCTION(fuse_native_signal_listxattr) 939 | NAPI_EXPORT_FUNCTION(fuse_native_signal_removexattr) 940 | NAPI_EXPORT_FUNCTION(fuse_native_signal_open) 941 | NAPI_EXPORT_FUNCTION(fuse_native_signal_opendir) 942 | NAPI_EXPORT_FUNCTION(fuse_native_signal_read) 943 | NAPI_EXPORT_FUNCTION(fuse_native_signal_write) 944 | NAPI_EXPORT_FUNCTION(fuse_native_signal_release) 945 | NAPI_EXPORT_FUNCTION(fuse_native_signal_releasedir) 946 | NAPI_EXPORT_FUNCTION(fuse_native_signal_create) 947 | NAPI_EXPORT_FUNCTION(fuse_native_signal_unlink) 948 | NAPI_EXPORT_FUNCTION(fuse_native_signal_rename) 949 | NAPI_EXPORT_FUNCTION(fuse_native_signal_link) 950 | NAPI_EXPORT_FUNCTION(fuse_native_signal_symlink) 951 | NAPI_EXPORT_FUNCTION(fuse_native_signal_mkdir) 952 | NAPI_EXPORT_FUNCTION(fuse_native_signal_rmdir) 953 | 954 | NAPI_EXPORT_UINT32(op_getattr) 955 | NAPI_EXPORT_UINT32(op_init) 956 | NAPI_EXPORT_UINT32(op_error) 957 | NAPI_EXPORT_UINT32(op_access) 958 | NAPI_EXPORT_UINT32(op_statfs) 959 | NAPI_EXPORT_UINT32(op_fgetattr) 960 | NAPI_EXPORT_UINT32(op_getattr) 961 | NAPI_EXPORT_UINT32(op_flush) 962 | NAPI_EXPORT_UINT32(op_fsync) 963 | NAPI_EXPORT_UINT32(op_fsyncdir) 964 | NAPI_EXPORT_UINT32(op_readdir) 965 | NAPI_EXPORT_UINT32(op_truncate) 966 | NAPI_EXPORT_UINT32(op_ftruncate) 967 | NAPI_EXPORT_UINT32(op_utimens) 968 | NAPI_EXPORT_UINT32(op_readlink) 969 | NAPI_EXPORT_UINT32(op_chown) 970 | NAPI_EXPORT_UINT32(op_chmod) 971 | NAPI_EXPORT_UINT32(op_mknod) 972 | NAPI_EXPORT_UINT32(op_setxattr) 973 | NAPI_EXPORT_UINT32(op_getxattr) 974 | NAPI_EXPORT_UINT32(op_listxattr) 975 | NAPI_EXPORT_UINT32(op_removexattr) 976 | NAPI_EXPORT_UINT32(op_open) 977 | NAPI_EXPORT_UINT32(op_opendir) 978 | NAPI_EXPORT_UINT32(op_read) 979 | NAPI_EXPORT_UINT32(op_write) 980 | NAPI_EXPORT_UINT32(op_release) 981 | NAPI_EXPORT_UINT32(op_releasedir) 982 | NAPI_EXPORT_UINT32(op_create) 983 | NAPI_EXPORT_UINT32(op_unlink) 984 | NAPI_EXPORT_UINT32(op_rename) 985 | NAPI_EXPORT_UINT32(op_link) 986 | NAPI_EXPORT_UINT32(op_symlink) 987 | NAPI_EXPORT_UINT32(op_mkdir) 988 | NAPI_EXPORT_UINT32(op_rmdir) 989 | } 990 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const os = require('os') 2 | const fs = require('fs') 3 | const path = require('path') 4 | const { exec } = require('child_process') 5 | 6 | const Nanoresource = require('nanoresource') 7 | const { beforeMount, beforeUnmount, configure, unconfigure, isConfigured } = require('fuse-shared-library') 8 | 9 | const binding = require('node-gyp-build')(__dirname) 10 | 11 | const IS_OSX = os.platform() === 'darwin' 12 | const OSX_FOLDER_ICON = '/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericFolderIcon.icns' 13 | const HAS_FOLDER_ICON = IS_OSX && fs.existsSync(OSX_FOLDER_ICON) 14 | const DEFAULT_TIMEOUT = 15 * 1000 15 | const TIMEOUT_ERRNO = IS_OSX ? -60 : -110 16 | const ENOTCONN = IS_OSX ? -57 : -107 17 | 18 | const OpcodesAndDefaults = new Map([ 19 | ['init', { 20 | op: binding.op_init 21 | }], 22 | ['error', { 23 | op: binding.op_error 24 | }], 25 | ['access', { 26 | op: binding.op_access, 27 | defaults: [0] 28 | }], 29 | ['statfs', { 30 | op: binding.op_statfs, 31 | defaults: [getStatfsArray()] 32 | }], 33 | ['fgetattr', { 34 | op: binding.op_fgetattr, 35 | defaults: [getStatArray()] 36 | }], 37 | ['getattr', { 38 | op: binding.op_getattr, 39 | defaults: [getStatArray()] 40 | }], 41 | ['flush', { 42 | op: binding.op_flush 43 | }], 44 | ['fsync', { 45 | op: binding.op_fsync 46 | }], 47 | ['fsyncdir', { 48 | op: binding.op_fsyncdir 49 | }], 50 | ['readdir', { 51 | op: binding.op_readdir, 52 | defaults: [[], []] 53 | }], 54 | ['truncate', { 55 | op: binding.op_truncate 56 | }], 57 | ['ftruncate', { 58 | op: binding.op_ftruncate 59 | }], 60 | ['utimens', { 61 | op: binding.op_utimens 62 | }], 63 | ['readlink', { 64 | op: binding.op_readlink, 65 | defaults: [''] 66 | }], 67 | ['chown', { 68 | op: binding.op_chown 69 | }], 70 | ['chmod', { 71 | op: binding.op_chmod 72 | }], 73 | ['mknod', { 74 | op: binding.op_mknod 75 | }], 76 | ['setxattr', { 77 | op: binding.op_setxattr 78 | }], 79 | ['getxattr', { 80 | op: binding.op_getxattr 81 | }], 82 | ['listxattr', { 83 | op: binding.op_listxattr 84 | }], 85 | ['removexattr', { 86 | op: binding.op_removexattr 87 | }], 88 | ['open', { 89 | op: binding.op_open, 90 | defaults: [0] 91 | }], 92 | ['opendir', { 93 | op: binding.op_opendir, 94 | defaults: [0] 95 | }], 96 | ['read', { 97 | op: binding.op_read, 98 | defaults: [0] 99 | }], 100 | ['write', { 101 | op: binding.op_write, 102 | defaults: [0] 103 | }], 104 | ['release', { 105 | op: binding.op_release 106 | }], 107 | ['releasedir', { 108 | op: binding.op_releasedir 109 | }], 110 | ['create', { 111 | op: binding.op_create, 112 | defaults: [0] 113 | }], 114 | ['unlink', { 115 | op: binding.op_unlink 116 | }], 117 | ['rename', { 118 | op: binding.op_rename 119 | }], 120 | ['link', { 121 | op: binding.op_link 122 | }], 123 | ['symlink', { 124 | op: binding.op_symlink 125 | }], 126 | ['mkdir', { 127 | op: binding.op_mkdir 128 | }], 129 | ['rmdir', { 130 | op: binding.op_rmdir 131 | }] 132 | ]) 133 | 134 | class Fuse extends Nanoresource { 135 | constructor (mnt, ops, opts = {}) { 136 | super() 137 | 138 | this.opts = opts 139 | this.mnt = path.resolve(mnt) 140 | this.ops = ops 141 | this.timeout = opts.timeout === false ? 0 : (opts.timeout || DEFAULT_TIMEOUT) 142 | 143 | this._force = !!opts.force 144 | this._mkdir = !!opts.mkdir 145 | this._thread = null 146 | this._handlers = this._makeHandlerArray() 147 | this._threads = new Set() 148 | 149 | const implemented = [binding.op_init, binding.op_error, binding.op_getattr] 150 | if (ops) { 151 | for (const [name, { op }] of OpcodesAndDefaults) { 152 | if (ops[name]) implemented.push(op) 153 | } 154 | } 155 | this._implemented = new Set(implemented) 156 | 157 | // Used to determine if the user-defined callback needs to be nextTick'd. 158 | this._sync = true 159 | } 160 | 161 | _getImplementedArray () { 162 | const implemented = new Uint32Array(35) 163 | for (const impl of this._implemented) { 164 | implemented[impl] = 1 165 | } 166 | return implemented 167 | } 168 | 169 | _fuseOptions () { 170 | const options = [] 171 | 172 | if ((/\*|(^,)fuse-bindings(,$)/.test(process.env.DEBUG)) || this.opts.debug) options.push('debug') 173 | if (this.opts.allowOther) options.push('allow_other') 174 | if (this.opts.allowRoot) options.push('allow_root') 175 | if (this.opts.autoUnmount) options.push('auto_unmount') 176 | if (this.opts.defaultPermissions) options.push('default_permissions') 177 | if (this.opts.blkdev) options.push('blkdev') 178 | if (this.opts.blksize) options.push('blksize=' + this.opts.blksize) 179 | if (this.opts.maxRead) options.push('max_read=' + this.opts.maxRead) 180 | if (this.opts.fd) options.push('fd=' + this.opts.fd) 181 | if (this.opts.userId) options.push('user_id=', this.opts.userId) 182 | if (this.opts.fsname) options.push('fsname=' + this.opts.fsname) 183 | if (this.opts.subtype) options.push('subtype=' + this.opts.subtype) 184 | if (this.opts.kernelCache) options.push('kernel_cache') 185 | if (this.opts.autoCache) options.push('auto_cache') 186 | if (this.opts.umask) options.push('umask=' + this.opts.umask) 187 | if (this.opts.uid) options.push('uid=' + this.opts.uid) 188 | if (this.opts.gid) options.push('gid=' + this.opts.gid) 189 | if (this.opts.entryTimeout) options.push('entry_timeout=' + this.opts.entryTimeout) 190 | if (this.opts.attrTimeout) options.push('attr_timeout=' + this.opts.attrTimeout) 191 | if (this.opts.acAttrTimeout) options.push('ac_attr_timeout=' + this.opts.acAttrTimeout) 192 | if (this.opts.noforget) options.push('noforget') 193 | if (this.opts.remember) options.push('remember=' + this.opts.remember) 194 | if (this.opts.modules) options.push('modules=' + this.opts.modules) 195 | 196 | if (this.opts.displayFolder && IS_OSX) { // only works on osx 197 | options.push('volname=' + path.basename(this.opts.name || this.mnt)) 198 | if (HAS_FOLDER_ICON) options.push('volicon=' + OSX_FOLDER_ICON) 199 | } 200 | 201 | return options.length ? '-o' + options.join(',') : '' 202 | } 203 | 204 | _malloc (size) { 205 | const buf = Buffer.alloc(size) 206 | this._threads.add(buf) 207 | return buf 208 | } 209 | 210 | _makeHandlerArray () { 211 | const self = this 212 | const handlers = new Array(OpcodesAndDefaults.size) 213 | 214 | for (const [name, { op, defaults }] of OpcodesAndDefaults) { 215 | const nativeSignal = binding[`fuse_native_signal_${name}`] 216 | if (!nativeSignal) continue 217 | 218 | handlers[op] = makeHandler(name, op, defaults, nativeSignal) 219 | } 220 | 221 | return handlers 222 | 223 | function makeHandler (name, op, defaults, nativeSignal) { 224 | let to = self.timeout 225 | if (typeof to === 'object' && to) { 226 | const defaultTimeout = to.default || DEFAULT_TIMEOUT 227 | to = to[name] 228 | if (!to && to !== false) to = defaultTimeout 229 | } 230 | 231 | return function (nativeHandler, opCode, ...args) { 232 | const sig = signal.bind(null, nativeHandler) 233 | const input = [...args] 234 | const boundSignal = to ? autoTimeout(sig, input) : sig 235 | const funcName = `_op_${name}` 236 | if (!self[funcName] || !self._implemented.has(op)) return boundSignal(-1, ...defaults) 237 | return self[funcName].apply(self, [boundSignal, ...args]) 238 | } 239 | 240 | function signal (nativeHandler, err, ...args) { 241 | var arr = [nativeHandler, err, ...args] 242 | 243 | if (defaults) { 244 | while (arr.length > 2 && arr[arr.length - 1] === undefined) arr.pop() 245 | if (arr.length === 2) arr = arr.concat(defaults) 246 | } 247 | 248 | return process.nextTick(nativeSignal, ...arr) 249 | } 250 | 251 | function autoTimeout (cb, input) { 252 | let called = false 253 | const timeout = setTimeout(timeoutWrap, to, TIMEOUT_ERRNO) 254 | return timeoutWrap 255 | 256 | function timeoutWrap (err, ...args) { 257 | if (called) return 258 | called = true 259 | 260 | clearTimeout(timeout) 261 | 262 | if (err === TIMEOUT_ERRNO) { 263 | switch (name) { 264 | case 'write': 265 | case 'read': 266 | return cb(TIMEOUT_ERRNO, 0, input[2].buffer) 267 | case 'setxattr': 268 | return cb(TIMEOUT_ERRNO, input[2].buffer) 269 | case 'getxattr': 270 | return cb(TIMEOUT_ERRNO, input[2].buffer) 271 | case 'listxattr': 272 | return cb(TIMEOUT_ERRNO, input[1].buffer) 273 | } 274 | } 275 | 276 | cb(err, ...args) 277 | } 278 | } 279 | } 280 | } 281 | 282 | // Static methods 283 | 284 | static unmount (mnt, cb) { 285 | mnt = JSON.stringify(mnt) 286 | const cmd = IS_OSX ? `diskutil unmount force ${mnt}` : `fusermount -uz ${mnt}` 287 | exec(cmd, err => { 288 | if (err) return cb(err) 289 | return cb(null) 290 | }) 291 | } 292 | 293 | // Debugging methods 294 | 295 | // Lifecycle methods 296 | 297 | _open (cb) { 298 | const self = this 299 | 300 | if (this._force) { 301 | return fs.stat(path.join(this.mnt, 'test'), (err, st) => { 302 | if (err && (err.errno === ENOTCONN || err.errno === Fuse.ENXIO)) return Fuse.unmount(this.mnt, open) 303 | return open() 304 | }) 305 | } 306 | return open() 307 | 308 | function open () { 309 | // If there was an unmount error, continue attempting to mount (this is the best we can do) 310 | self._thread = Buffer.alloc(binding.sizeof_fuse_thread_t) 311 | self._openCallback = cb 312 | 313 | const opts = self._fuseOptions() 314 | const implemented = self._getImplementedArray() 315 | 316 | return fs.stat(self.mnt, (err, stat) => { 317 | if (err && err.errno !== -2) return cb(err) 318 | if (err) { 319 | if (!self._mkdir) return cb(new Error('Mountpoint does not exist')) 320 | return fs.mkdir(self.mnt, { recursive: true }, err => { 321 | if (err) return cb(err) 322 | fs.stat(self.mnt, (err, stat) => { 323 | if (err) return cb(err) 324 | return onexists(stat) 325 | }) 326 | }) 327 | } 328 | if (!stat.isDirectory()) return cb(new Error('Mountpoint is not a directory')) 329 | return onexists(stat) 330 | }) 331 | 332 | function onexists (stat) { 333 | fs.stat(path.join(self.mnt, '..'), (_, parent) => { 334 | if (parent && parent.dev !== stat.dev) return cb(new Error('Mountpoint in use')) 335 | try { 336 | // TODO: asyncify 337 | binding.fuse_native_mount(self.mnt, opts, self._thread, self, self._malloc, self._handlers, implemented) 338 | } catch (err) { 339 | return cb(err) 340 | } 341 | }) 342 | } 343 | } 344 | } 345 | 346 | _close (cb) { 347 | const self = this 348 | 349 | Fuse.unmount(this.mnt, err => { 350 | if (err) { 351 | err.unmountFailure = true 352 | return cb(err) 353 | } 354 | nativeUnmount() 355 | }) 356 | 357 | function nativeUnmount () { 358 | try { 359 | binding.fuse_native_unmount(self.mnt, self._thread) 360 | } catch (err) { 361 | return cb(err) 362 | } 363 | return cb(null) 364 | } 365 | } 366 | 367 | // Handlers 368 | 369 | _op_init (signal) { 370 | if (this._openCallback) { 371 | process.nextTick(this._openCallback, null) 372 | this._openCallback = null 373 | } 374 | if (!this.ops.init) { 375 | signal(0) 376 | return 377 | } 378 | this.ops.init(err => { 379 | return signal(err) 380 | }) 381 | } 382 | 383 | _op_error (signal) { 384 | if (!this.ops.error) { 385 | signal(0) 386 | return 387 | } 388 | this.ops.error(err => { 389 | return signal(err) 390 | }) 391 | } 392 | 393 | _op_statfs (signal, path) { 394 | this.ops.statfs(path, (err, statfs) => { 395 | if (err) return signal(err) 396 | const arr = getStatfsArray(statfs) 397 | return signal(0, arr) 398 | }) 399 | } 400 | 401 | _op_getattr (signal, path) { 402 | if (!this.ops.getattr) { 403 | if (path !== '/') { 404 | signal(Fuse.EPERM) 405 | } else { 406 | signal(0, getStatArray({ mtime: new Date(0), atime: new Date(0), ctime: new Date(0), mode: 16877, size: 4096 })) 407 | } 408 | return 409 | } 410 | 411 | this.ops.getattr(path, (err, stat) => { 412 | if (err) return signal(err, getStatArray()) 413 | return signal(0, getStatArray(stat)) 414 | }) 415 | } 416 | 417 | _op_fgetattr (signal, path, fd) { 418 | if (!this.ops.fgetattr) { 419 | if (path !== '/') { 420 | signal(Fuse.EPERM) 421 | } else { 422 | signal(0, getStatArray({ mtime: new Date(0), atime: new Date(0), ctime: new Date(0), mode: 16877, size: 4096 })) 423 | } 424 | return 425 | } 426 | this.ops.getattr(path, (err, stat) => { 427 | if (err) return signal(err) 428 | return signal(0, getStatArray(stat)) 429 | }) 430 | } 431 | 432 | _op_access (signal, path, mode) { 433 | this.ops.access(path, mode, err => { 434 | return signal(err) 435 | }) 436 | } 437 | 438 | _op_open (signal, path, flags) { 439 | this.ops.open(path, flags, (err, fd) => { 440 | return signal(err, fd) 441 | }) 442 | } 443 | 444 | _op_opendir (signal, path, flags) { 445 | this.ops.opendir(path, flags, (err, fd) => { 446 | return signal(err, fd) 447 | }) 448 | } 449 | 450 | _op_create (signal, path, mode) { 451 | this.ops.create(path, mode, (err, fd) => { 452 | return signal(err, fd) 453 | }) 454 | } 455 | 456 | _op_utimens (signal, path, atimeLow, atimeHigh, mtimeLow, mtimeHigh) { 457 | const atime = getDoubleArg(atimeLow, atimeHigh) 458 | const mtime = getDoubleArg(mtimeLow, mtimeHigh) 459 | this.ops.utimens(path, atime, mtime, err => { 460 | return signal(err) 461 | }) 462 | } 463 | 464 | _op_release (signal, path, fd) { 465 | this.ops.release(path, fd, err => { 466 | return signal(err) 467 | }) 468 | } 469 | 470 | _op_releasedir (signal, path, fd) { 471 | this.ops.releasedir(path, fd, err => { 472 | return signal(err) 473 | }) 474 | } 475 | 476 | _op_read (signal, path, fd, buf, len, offsetLow, offsetHigh) { 477 | this.ops.read(path, fd, buf, len, getDoubleArg(offsetLow, offsetHigh), (err, bytesRead) => { 478 | return signal(err, bytesRead || 0, buf.buffer) 479 | }) 480 | } 481 | 482 | _op_write (signal, path, fd, buf, len, offsetLow, offsetHigh) { 483 | this.ops.write(path, fd, buf, len, getDoubleArg(offsetLow, offsetHigh), (err, bytesWritten) => { 484 | return signal(err, bytesWritten || 0, buf.buffer) 485 | }) 486 | } 487 | 488 | _op_readdir (signal, path) { 489 | this.ops.readdir(path, (err, names, stats) => { 490 | if (err) return signal(err) 491 | if (stats) stats = stats.map(getStatArray) 492 | return signal(0, names, stats || []) 493 | }) 494 | } 495 | 496 | _op_setxattr (signal, path, name, value, position, flags) { 497 | this.ops.setxattr(path, name, value, position, flags, err => { 498 | return signal(err, value.buffer) 499 | }) 500 | } 501 | 502 | _op_getxattr (signal, path, name, valueBuf, position) { 503 | this.ops.getxattr(path, name, position, (err, value) => { 504 | if (!err) { 505 | if (!value) return signal(IS_OSX ? -93 : -61, valueBuf.buffer) 506 | value.copy(valueBuf) 507 | return signal(value.length, valueBuf.buffer) 508 | } 509 | return signal(err, valueBuf.buffer) 510 | }) 511 | } 512 | 513 | _op_listxattr (signal, path, listBuf) { 514 | this.ops.listxattr(path, (err, list) => { 515 | if (list && !err) { 516 | if (!listBuf.length) { 517 | let size = 0 518 | for (const name of list) size += Buffer.byteLength(name) + 1 519 | size += 128 // fuse yells if we do not signal room for some mac stuff also 520 | return signal(size, listBuf.buffer) 521 | } 522 | 523 | let ptr = 0 524 | for (const name of list) { 525 | listBuf.write(name, ptr) 526 | ptr += Buffer.byteLength(name) 527 | listBuf[ptr++] = 0 528 | } 529 | 530 | return signal(ptr, listBuf.buffer) 531 | } 532 | return signal(err, listBuf.buffer) 533 | }) 534 | } 535 | 536 | _op_removexattr (signal, path, name) { 537 | this.ops.removexattr(path, name, err => { 538 | return signal(err) 539 | }) 540 | } 541 | 542 | _op_flush (signal, path, fd) { 543 | this.ops.flush(path, fd, err => { 544 | return signal(err) 545 | }) 546 | } 547 | 548 | _op_fsync (signal, path, datasync, fd) { 549 | this.ops.fsync(path, datasync, fd, err => { 550 | return signal(err) 551 | }) 552 | } 553 | 554 | _op_fsyncdir (signal, path, datasync, fd) { 555 | this.ops.fsyncdir(path, datasync, fd, err => { 556 | return signal(err) 557 | }) 558 | } 559 | 560 | _op_truncate (signal, path, sizeLow, sizeHigh) { 561 | const size = getDoubleArg(sizeLow, sizeHigh) 562 | this.ops.truncate(path, size, err => { 563 | return signal(err) 564 | }) 565 | } 566 | 567 | _op_ftruncate (signal, path, fd, sizeLow, sizeHigh) { 568 | const size = getDoubleArg(sizeLow, sizeHigh) 569 | this.ops.ftruncate(path, fd, size, err => { 570 | return signal(err) 571 | }) 572 | } 573 | 574 | _op_readlink (signal, path) { 575 | this.ops.readlink(path, (err, linkname) => { 576 | return signal(err, linkname) 577 | }) 578 | } 579 | 580 | _op_chown (signal, path, uid, gid) { 581 | this.ops.chown(path, uid, gid, err => { 582 | return signal(err) 583 | }) 584 | } 585 | 586 | _op_chmod (signal, path, mode) { 587 | this.ops.chmod(path, mode, err => { 588 | return signal(err) 589 | }) 590 | } 591 | 592 | _op_mknod (signal, path, mode, dev) { 593 | this.ops.mknod(path, mode, dev, err => { 594 | return signal(err) 595 | }) 596 | } 597 | 598 | _op_unlink (signal, path) { 599 | this.ops.unlink(path, err => { 600 | return signal(err) 601 | }) 602 | } 603 | 604 | _op_rename (signal, src, dest) { 605 | this.ops.rename(src, dest, err => { 606 | return signal(err) 607 | }) 608 | } 609 | 610 | _op_link (signal, src, dest) { 611 | this.ops.link(src, dest, err => { 612 | return signal(err) 613 | }) 614 | } 615 | 616 | _op_symlink (signal, src, dest) { 617 | this.ops.symlink(src, dest, err => { 618 | return signal(err) 619 | }) 620 | } 621 | 622 | _op_mkdir (signal, path, mode) { 623 | this.ops.mkdir(path, mode, err => { 624 | return signal(err) 625 | }) 626 | } 627 | 628 | _op_rmdir (signal, path) { 629 | this.ops.rmdir(path, err => { 630 | return signal(err) 631 | }) 632 | } 633 | 634 | // Public API 635 | 636 | mount (cb) { 637 | return this.open(cb) 638 | } 639 | 640 | unmount (cb) { 641 | return this.close(cb) 642 | } 643 | 644 | errno (code) { 645 | return (code && Fuse[code.toUpperCase()]) || -1 646 | } 647 | } 648 | 649 | Fuse.EPERM = -1 650 | Fuse.ENOENT = -2 651 | Fuse.ESRCH = -3 652 | Fuse.EINTR = -4 653 | Fuse.EIO = -5 654 | Fuse.ENXIO = -6 655 | Fuse.E2BIG = -7 656 | Fuse.ENOEXEC = -8 657 | Fuse.EBADF = -9 658 | Fuse.ECHILD = -10 659 | Fuse.EAGAIN = -11 660 | Fuse.ENOMEM = -12 661 | Fuse.EACCES = -13 662 | Fuse.EFAULT = -14 663 | Fuse.ENOTBLK = -15 664 | Fuse.EBUSY = -16 665 | Fuse.EEXIST = -17 666 | Fuse.EXDEV = -18 667 | Fuse.ENODEV = -19 668 | Fuse.ENOTDIR = -20 669 | Fuse.EISDIR = -21 670 | Fuse.EINVAL = -22 671 | Fuse.ENFILE = -23 672 | Fuse.EMFILE = -24 673 | Fuse.ENOTTY = -25 674 | Fuse.ETXTBSY = -26 675 | Fuse.EFBIG = -27 676 | Fuse.ENOSPC = -28 677 | Fuse.ESPIPE = -29 678 | Fuse.EROFS = -30 679 | Fuse.EMLINK = -31 680 | Fuse.EPIPE = -32 681 | Fuse.EDOM = -33 682 | Fuse.ERANGE = -34 683 | Fuse.EDEADLK = -35 684 | Fuse.ENAMETOOLONG = -36 685 | Fuse.ENOLCK = -37 686 | Fuse.ENOSYS = -38 687 | Fuse.ENOTEMPTY = -39 688 | Fuse.ELOOP = -40 689 | Fuse.EWOULDBLOCK = -11 690 | Fuse.ENOMSG = -42 691 | Fuse.EIDRM = -43 692 | Fuse.ECHRNG = -44 693 | Fuse.EL2NSYNC = -45 694 | Fuse.EL3HLT = -46 695 | Fuse.EL3RST = -47 696 | Fuse.ELNRNG = -48 697 | Fuse.EUNATCH = -49 698 | Fuse.ENOCSI = -50 699 | Fuse.EL2HLT = -51 700 | Fuse.EBADE = -52 701 | Fuse.EBADR = -53 702 | Fuse.EXFULL = -54 703 | Fuse.ENOANO = -55 704 | Fuse.EBADRQC = -56 705 | Fuse.EBADSLT = -57 706 | Fuse.EDEADLOCK = -35 707 | Fuse.EBFONT = -59 708 | Fuse.ENOSTR = -60 709 | Fuse.ENODATA = -61 710 | Fuse.ETIME = -62 711 | Fuse.ENOSR = -63 712 | Fuse.ENONET = -64 713 | Fuse.ENOPKG = -65 714 | Fuse.EREMOTE = -66 715 | Fuse.ENOLINK = -67 716 | Fuse.EADV = -68 717 | Fuse.ESRMNT = -69 718 | Fuse.ECOMM = -70 719 | Fuse.EPROTO = -71 720 | Fuse.EMULTIHOP = -72 721 | Fuse.EDOTDOT = -73 722 | Fuse.EBADMSG = -74 723 | Fuse.EOVERFLOW = -75 724 | Fuse.ENOTUNIQ = -76 725 | Fuse.EBADFD = -77 726 | Fuse.EREMCHG = -78 727 | Fuse.ELIBACC = -79 728 | Fuse.ELIBBAD = -80 729 | Fuse.ELIBSCN = -81 730 | Fuse.ELIBMAX = -82 731 | Fuse.ELIBEXEC = -83 732 | Fuse.EILSEQ = -84 733 | Fuse.ERESTART = -85 734 | Fuse.ESTRPIPE = -86 735 | Fuse.EUSERS = -87 736 | Fuse.ENOTSOCK = -88 737 | Fuse.EDESTADDRREQ = -89 738 | Fuse.EMSGSIZE = -90 739 | Fuse.EPROTOTYPE = -91 740 | Fuse.ENOPROTOOPT = -92 741 | Fuse.EPROTONOSUPPORT = -93 742 | Fuse.ESOCKTNOSUPPORT = -94 743 | Fuse.EOPNOTSUPP = -95 744 | Fuse.EPFNOSUPPORT = -96 745 | Fuse.EAFNOSUPPORT = -97 746 | Fuse.EADDRINUSE = -98 747 | Fuse.EADDRNOTAVAIL = -99 748 | Fuse.ENETDOWN = -100 749 | Fuse.ENETUNREACH = -101 750 | Fuse.ENETRESET = -102 751 | Fuse.ECONNABORTED = -103 752 | Fuse.ECONNRESET = -104 753 | Fuse.ENOBUFS = -105 754 | Fuse.EISCONN = -106 755 | Fuse.ENOTCONN = -107 756 | Fuse.ESHUTDOWN = -108 757 | Fuse.ETOOMANYREFS = -109 758 | Fuse.ETIMEDOUT = -110 759 | Fuse.ECONNREFUSED = -111 760 | Fuse.EHOSTDOWN = -112 761 | Fuse.EHOSTUNREACH = -113 762 | Fuse.EALREADY = -114 763 | Fuse.EINPROGRESS = -115 764 | Fuse.ESTALE = -116 765 | Fuse.EUCLEAN = -117 766 | Fuse.ENOTNAM = -118 767 | Fuse.ENAVAIL = -119 768 | Fuse.EISNAM = -120 769 | Fuse.EREMOTEIO = -121 770 | Fuse.EDQUOT = -122 771 | Fuse.ENOMEDIUM = -123 772 | Fuse.EMEDIUMTYPE = -124 773 | 774 | // Forward configuration functions through the exported class. 775 | Fuse.beforeMount = beforeMount 776 | Fuse.beforeUnmount = beforeUnmount 777 | Fuse.configure = configure 778 | Fuse.unconfigure = unconfigure 779 | Fuse.isConfigured = isConfigured 780 | 781 | module.exports = Fuse 782 | 783 | function getStatfsArray (statfs) { 784 | const ints = new Uint32Array(11) 785 | 786 | ints[0] = (statfs && statfs.bsize) || 0 787 | ints[1] = (statfs && statfs.frsize) || 0 788 | ints[2] = (statfs && statfs.blocks) || 0 789 | ints[3] = (statfs && statfs.bfree) || 0 790 | ints[4] = (statfs && statfs.bavail) || 0 791 | ints[5] = (statfs && statfs.files) || 0 792 | ints[6] = (statfs && statfs.ffree) || 0 793 | ints[7] = (statfs && statfs.favail) || 0 794 | ints[8] = (statfs && statfs.fsid) || 0 795 | ints[9] = (statfs && statfs.flag) || 0 796 | ints[10] = (statfs && statfs.namemax) || 0 797 | 798 | return ints 799 | } 800 | 801 | function setDoubleInt (arr, idx, num) { 802 | arr[idx] = num % 4294967296 803 | arr[idx + 1] = (num - arr[idx]) / 4294967296 804 | } 805 | 806 | function getDoubleArg (a, b) { 807 | return a + b * 4294967296 808 | } 809 | 810 | function toDateMS (st) { 811 | if (typeof st === 'number') return st 812 | if (!st) return Date.now() 813 | return st.getTime() 814 | } 815 | 816 | function getStatArray (stat) { 817 | const ints = new Uint32Array(18) 818 | 819 | ints[0] = (stat && stat.mode) || 0 820 | ints[1] = (stat && stat.uid) || 0 821 | ints[2] = (stat && stat.gid) || 0 822 | setDoubleInt(ints, 3, (stat && stat.size) || 0) 823 | ints[5] = (stat && stat.dev) || 0 824 | ints[6] = (stat && stat.nlink) || 1 825 | ints[7] = (stat && stat.ino) || 0 826 | ints[8] = (stat && stat.rdev) || 0 827 | ints[9] = (stat && stat.blksize) || 0 828 | setDoubleInt(ints, 10, (stat && stat.blocks) || 0) 829 | setDoubleInt(ints, 12, toDateMS(stat && stat.atime)) 830 | setDoubleInt(ints, 14, toDateMS(stat && stat.mtime)) 831 | setDoubleInt(ints, 16, toDateMS(stat && stat.ctime)) 832 | 833 | return ints 834 | } 835 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fuse-native", 3 | "version": "2.2.6", 4 | "description": "Fully maintained fuse bindings for Node that aims to cover the entire FUSE api", 5 | "main": "index.js", 6 | "bin": { 7 | "fuse-native": "./bin.js" 8 | }, 9 | "scripts": { 10 | "install": "node-gyp-build", 11 | "test": "tape test/*.js", 12 | "prebuild": "prebuildify --napi --strip", 13 | "prebuild-ia32": "prebuildify --napi --strip --arch=ia32", 14 | "configure": "NODE=$(which node) && sudo -E $NODE ./bin.js configure || true" 15 | }, 16 | "gypfile": true, 17 | "dependencies": { 18 | "fuse-shared-library": "^1.0.2", 19 | "nanoresource": "^1.3.0", 20 | "napi-macros": "^2.0.0", 21 | "node-gyp-build": "^4.2.0" 22 | }, 23 | "devDependencies": { 24 | "concat-stream": "^2.0.0", 25 | "prebuildify": "^3.0.4", 26 | "standard": "^13.1.0", 27 | "tape": "^4.12.0" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "https://github.com/fuse-friends/fuse-native.git" 32 | }, 33 | "author": "Mathias Buus (@mafintosh)", 34 | "license": "MIT", 35 | "bugs": { 36 | "url": "https://github.com/fuse-friends/fuse-native/issues" 37 | }, 38 | "homepage": "https://github.com/fuse-friends/fuse-native" 39 | } 40 | -------------------------------------------------------------------------------- /semaphore.h: -------------------------------------------------------------------------------- 1 | 2 | #ifdef __APPLE__ 3 | 4 | #include 5 | #include 6 | 7 | typedef dispatch_semaphore_t fuse_native_semaphore_t; 8 | 9 | static int fuse_native_semaphore_init (dispatch_semaphore_t *sem) { 10 | *sem = dispatch_semaphore_create(0); 11 | return *sem == NULL ? -1 : 0; 12 | } 13 | 14 | static void fuse_native_semaphore_wait (dispatch_semaphore_t *sem) { 15 | dispatch_semaphore_wait(*sem, DISPATCH_TIME_FOREVER); 16 | } 17 | 18 | static void fuse_native_semaphore_signal (dispatch_semaphore_t *sem) { 19 | dispatch_semaphore_signal(*sem); 20 | } 21 | 22 | #else 23 | 24 | #include 25 | 26 | typedef sem_t fuse_native_semaphore_t; 27 | 28 | static int fuse_native_semaphore_init (sem_t *sem) { 29 | return sem_init(sem, 0, 0); 30 | } 31 | 32 | static void fuse_native_semaphore_wait (sem_t *sem) { 33 | sem_wait(sem); 34 | } 35 | 36 | static void fuse_native_semaphore_signal (sem_t *sem) { 37 | sem_post(sem); 38 | } 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /test/big.js: -------------------------------------------------------------------------------- 1 | const tape = require('tape') 2 | const fs = require('fs') 3 | const path = require('path') 4 | const concat = require('concat-stream') 5 | 6 | const Fuse = require('../') 7 | const createMountpoint = require('./fixtures/mnt') 8 | const stat = require('./fixtures/stat') 9 | const { unmount } = require('./helpers') 10 | 11 | const mnt = createMountpoint() 12 | 13 | tape('read and write big file', function (t) { 14 | let size = 0 15 | const reads = [0, 4 * 1024 * 1024 * 1024, 6 * 1024 * 1024 * 1024] 16 | const writes = [0, 4 * 1024 * 1024 * 1024, 6 * 1024 * 1024 * 1024] 17 | 18 | var ops = { 19 | force: true, 20 | readdir (path, cb) { 21 | if (path === '/') return process.nextTick(cb, null, ['test']) 22 | return process.nextTick(cb, Fuse.ENOENT) 23 | }, 24 | getattr (path, cb) { 25 | if (path === '/') return process.nextTick(cb, null, stat({ mode: 'dir', size: 4096 })) 26 | if (path === '/test') return process.nextTick(cb, null, stat({ mode: 'file', size, mtime: new Date() })) 27 | return process.nextTick(cb, Fuse.ENOENT) 28 | }, 29 | open (path, flags, cb) { 30 | return process.nextTick(cb, 0, 42) 31 | }, 32 | release (path, fd, cb) { 33 | t.same(fd, 42, 'fd was passed to release') 34 | return process.nextTick(cb, 0) 35 | }, 36 | read (path, fd, buf, len, pos, cb) { 37 | t.same(pos, reads.shift(), 'read is expected') 38 | buf.fill(0) 39 | if (pos + len > size) return cb(Math.max(size - pos, 0)) 40 | cb(len) 41 | }, 42 | ftruncate (path, fd, len, cb) { 43 | size = len 44 | cb(0) 45 | }, 46 | truncate (path, len, cb) { 47 | size = len 48 | cb(0) 49 | }, 50 | write (path, fd, buf, len, pos, cb) { 51 | if (!writes.length) return cb(-1) 52 | t.same(pos, writes.shift(), 'write is expected') 53 | size = Math.max(pos + len, size) 54 | cb(len) 55 | } 56 | } 57 | 58 | const fuse = new Fuse(mnt, ops, { debug: !true, autoCache: true }) 59 | let fd = 0 60 | 61 | run( 62 | (_, cb) => fuse.mount(cb), 63 | open('w+'), 64 | (_, cb) => fs.fstat(fd, cb), 65 | checkSize(0), 66 | (_, cb) => fs.ftruncate(fd, 4 * 1024 * 1024 * 1024 + 1, cb), 67 | (_, cb) => fs.fstat(fd, cb), 68 | checkSize(4 * 1024 * 1024 * 1024 + 1), 69 | (_, cb) => fs.truncate(path.join(mnt, 'test'), 6 * 1024 * 1024 * 1024 + 2, cb), 70 | (_, cb) => fs.fstat(fd, cb), 71 | checkSize(6 * 1024 * 1024 * 1024 + 2), 72 | (_, cb) => fs.write(fd, Buffer.alloc(4096), 0, 4096, 0, cb), 73 | (_, cb) => fs.write(fd, Buffer.alloc(4096), 0, 4096, 4 * 1024 * 1024 * 1024, cb), 74 | (_, cb) => fs.write(fd, Buffer.alloc(4096), 0, 4096, 6 * 1024 * 1024 * 1024, cb), 75 | (_, cb) => fs.fstat(fd, cb), 76 | checkSize(6 * 1024 * 1024 * 1024 + 4096), 77 | (_, cb) => fs.close(fd, cb), 78 | open('a+'), 79 | (_, cb) => fs.read(fd, Buffer.alloc(4096), 0, 4096, 0, cb), 80 | (_, cb) => fs.read(fd, Buffer.alloc(4096), 0, 4096, 4 * 1024 * 1024 * 1024, cb), 81 | (_, cb) => fs.read(fd, Buffer.alloc(4096), 0, 4096, 6 * 1024 * 1024 * 1024, cb), 82 | (_, cb) => fs.close(fd, cb), 83 | (_, cb) => unmount(fuse, cb), 84 | () => { 85 | t.same(writes.length, 0) 86 | t.same(reads.length, 0) 87 | t.end() 88 | } 89 | ) 90 | 91 | function open (mode) { 92 | return (_, cb) => { 93 | fs.open(path.join(mnt, 'test'), mode, function (_, res) { 94 | fd = res 95 | cb() 96 | }) 97 | } 98 | } 99 | 100 | function checkSize (n) { 101 | return ({ size}, cb) => { 102 | t.same(size, n) 103 | cb() 104 | } 105 | } 106 | 107 | function run (...fns) { 108 | const all = [...fns] 109 | tick() 110 | function tick (err, val) { 111 | t.error(err, 'no error') 112 | const next = all.shift() 113 | if (next) next(val, tick) 114 | } 115 | } 116 | }) 117 | -------------------------------------------------------------------------------- /test/fixtures/mnt.js: -------------------------------------------------------------------------------- 1 | var os = require('os') 2 | var path = require('path') 3 | var fs = require('fs') 4 | 5 | function create (opts = {}) { 6 | var mnt = path.join(os.tmpdir(), 'fuse-bindings-' + process.pid + '-' + Date.now()) 7 | 8 | if (!opts.doNotCreate) { 9 | try { 10 | fs.mkdirSync(mnt) 11 | } catch (err) { 12 | // do nothing 13 | } 14 | } 15 | 16 | return mnt 17 | } 18 | 19 | module.exports = create 20 | -------------------------------------------------------------------------------- /test/fixtures/simple-fs.js: -------------------------------------------------------------------------------- 1 | const stat = require('./stat') 2 | const Fuse = require('../../') 3 | 4 | module.exports = function (tests = {}) { 5 | return { 6 | readdir: function (path, cb) { 7 | if (tests.readdir) tests.readdir(path) 8 | if (path === '/') return process.nextTick(cb, null, ['test']) 9 | return process.nextTick(cb, Fuse.ENOENT) 10 | }, 11 | getattr: function (path, cb) { 12 | if (tests.getattr) tests.getattr(path) 13 | if (path === '/') return process.nextTick(cb, null, stat({ mode: 'dir', size: 4096 })) 14 | if (path === '/test') return process.nextTick(cb, null, stat({ mode: 'file', size: 11 })) 15 | return process.nextTick(cb, Fuse.ENOENT) 16 | }, 17 | open: function (path, flags, cb) { 18 | if (tests.open) tests.open(path, flags) 19 | return process.nextTick(cb, 0, 42) 20 | }, 21 | release: function (path, fd, cb) { 22 | if (tests.release) tests.release(path, fd) 23 | return process.nextTick(cb, 0) 24 | }, 25 | read: function (path, fd, buf, len, pos, cb) { 26 | if (tests.read) tests.read(path, fd, buf, len, pos) 27 | var str = 'hello world'.slice(pos, pos + len) 28 | if (!str) return process.nextTick(cb, 0) 29 | buf.write(str) 30 | return process.nextTick(cb, str.length) 31 | } 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /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/helpers/index.js: -------------------------------------------------------------------------------- 1 | exports.unmount = function (fuse, cb) { // This only seems to be nessesary an the ancient osx we use on travis so ... yolo 2 | fuse.unmount(function (err) { 3 | if (err) return cb(err) 4 | setTimeout(cb, 1000) 5 | }) 6 | } 7 | -------------------------------------------------------------------------------- /test/links.js: -------------------------------------------------------------------------------- 1 | const tape = require('tape') 2 | const fs = require('fs') 3 | const path = require('path') 4 | const { unmount } = require('./helpers') 5 | 6 | const Fuse = require('../') 7 | const createMountpoint = require('./fixtures/mnt') 8 | const stat = require('./fixtures/stat') 9 | 10 | const mnt = createMountpoint() 11 | 12 | tape('readlink', function (t) { 13 | var ops = { 14 | force: true, 15 | readdir: function (path, cb) { 16 | if (path === '/') return process.nextTick(cb, null, ['hello', 'link']) 17 | return process.nextTick(cb, Fuse.ENOENT) 18 | }, 19 | readlink: function (path, cb) { 20 | process.nextTick(cb, 0, 'hello') 21 | }, 22 | getattr: function (path, cb) { 23 | if (path === '/') return process.nextTick(cb, null, stat({ mode: 'dir', size: 4096 })) 24 | if (path === '/hello') return process.nextTick(cb, null, stat({ mode: 'file', size: 11 })) 25 | if (path === '/link') return process.nextTick(cb, null, stat({ mode: 'link', size: 5 })) 26 | return process.nextTick(cb, Fuse.ENOENT) 27 | }, 28 | open: function (path, flags, cb) { 29 | process.nextTick(cb, 0, 42) 30 | }, 31 | read: function (path, fd, buf, len, pos, cb) { 32 | var str = 'hello world'.slice(pos, pos + len) 33 | if (!str) return process.nextTick(cb, 0) 34 | buf.write(str) 35 | return process.nextTick(cb, str.length) 36 | } 37 | } 38 | 39 | const fuse = new Fuse(mnt, ops, { debug: true }) 40 | fuse.mount(function (err) { 41 | t.error(err, 'no error') 42 | 43 | fs.lstat(path.join(mnt, 'link'), function (err, stat) { 44 | t.error(err, 'no error') 45 | t.same(stat.size, 5, 'correct size') 46 | 47 | fs.stat(path.join(mnt, 'hello'), function (err, stat) { 48 | t.error(err, 'no error') 49 | t.same(stat.size, 11, 'correct size') 50 | 51 | fs.readlink(path.join(mnt, 'link'), function (err, dest) { 52 | t.error(err, 'no error') 53 | t.same(dest, 'hello', 'link resolves') 54 | 55 | fs.readFile(path.join(mnt, 'link'), function (err, buf) { 56 | t.error(err, 'no error') 57 | t.same(buf, Buffer.from('hello world'), 'can read link content') 58 | 59 | unmount(fuse, function () { 60 | t.end() 61 | }) 62 | }) 63 | }) 64 | }) 65 | }) 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /test/misc.js: -------------------------------------------------------------------------------- 1 | const os = require('os') 2 | const fs = require('fs') 3 | const tape = require('tape') 4 | const { spawnSync, exec } = require('child_process') 5 | 6 | const createMountpoint = require('./fixtures/mnt') 7 | 8 | const Fuse = require('../') 9 | const { unmount } = require('./helpers') 10 | const simpleFS = require('./fixtures/simple-fs') 11 | 12 | const mnt = createMountpoint() 13 | 14 | tape('mount', function (t) { 15 | const fuse = new Fuse(mnt, {}, { force: true }) 16 | fuse.mount(function (err) { 17 | t.error(err, 'no error') 18 | t.ok(true, 'works') 19 | unmount(fuse, function () { 20 | t.end() 21 | }) 22 | }) 23 | }) 24 | 25 | tape('mount + unmount + mount', function (t) { 26 | const fuse1 = new Fuse(mnt, {}, { force: true, debug: false }) 27 | const fuse2 = new Fuse(mnt, {}, { force: true, debug: false }) 28 | 29 | fuse1.mount(function (err) { 30 | t.error(err, 'no error') 31 | t.ok(true, 'works') 32 | unmount(fuse1, function () { 33 | fuse2.mount(function (err) { 34 | t.error(err, 'no error') 35 | t.ok(true, 'works') 36 | unmount(fuse2, function () { 37 | t.end() 38 | }) 39 | }) 40 | }) 41 | }) 42 | }) 43 | 44 | tape('mount + unmount + mount with same instance fails', function (t) { 45 | const fuse = new Fuse(mnt, {}, { force: true, debug: false }) 46 | 47 | fuse.mount(function (err) { 48 | t.error(err, 'no error') 49 | t.pass('works') 50 | unmount(fuse, function () { 51 | fuse.mount(function (err) { 52 | t.ok(err, 'had error') 53 | t.end() 54 | }) 55 | }) 56 | }) 57 | }) 58 | 59 | tape('mnt point must exist', function (t) { 60 | const fuse = new Fuse('.does-not-exist', {}, { debug: false }) 61 | fuse.mount(function (err) { 62 | t.ok(err, 'had error') 63 | t.end() 64 | }) 65 | }) 66 | 67 | tape('mnt point must be directory', function (t) { 68 | const fuse = new Fuse(__filename, {}, { debug: false }) 69 | fuse.mount(function (err) { 70 | t.ok(err, 'had error') 71 | t.end() 72 | }) 73 | }) 74 | 75 | tape('mounting twice without force fails', function (t) { 76 | const fuse1 = new Fuse(mnt, {}, { force: true, debug: false }) 77 | const fuse2 = new Fuse(mnt, {}, { force: false, debug: false }) 78 | 79 | fuse1.mount(function (err) { 80 | t.error(err, 'no error') 81 | t.pass('works') 82 | fuse2.mount(function (err) { 83 | t.true(err, 'cannot mount over existing mountpoint') 84 | unmount(fuse1, function () { 85 | t.end() 86 | }) 87 | }) 88 | }) 89 | }) 90 | 91 | tape('mounting twice with force fail if mountpoint is not broken', function (t) { 92 | const fuse1 = new Fuse(mnt, {}, { force: true, debug: false }) 93 | const fuse2 = new Fuse(mnt, {}, { force: true, debug: false }) 94 | 95 | fuse1.mount(function (err) { 96 | t.error(err, 'no error') 97 | t.pass('works') 98 | fuse2.mount(function (err) { 99 | t.true(err, 'cannot mount over existing mountpoint') 100 | unmount(fuse1, function () { 101 | t.end() 102 | }) 103 | }) 104 | }) 105 | }) 106 | 107 | tape('mounting over a broken mountpoint with force succeeds', function (t) { 108 | createBrokenMountpoint(mnt) 109 | 110 | const fuse = new Fuse(mnt, {}, { force: true, debug: false }) 111 | fuse.mount(function (err) { 112 | t.error(err, 'no error') 113 | t.pass('works') 114 | unmount(fuse, function (err) { 115 | t.end() 116 | }) 117 | }) 118 | }) 119 | 120 | tape('mounting without mkdir option and a nonexistent mountpoint fails', function (t) { 121 | const nonexistentMnt = createMountpoint({ doNotCreate: true }) 122 | 123 | const fuse = new Fuse(nonexistentMnt, {}, { debug: false }) 124 | fuse.mount(function (err) { 125 | t.true(err, 'could not mount') 126 | t.end() 127 | }) 128 | }) 129 | 130 | tape('mounting with mkdir option and a nonexistent mountpoint succeeds', function (t) { 131 | const nonexistentMnt = createMountpoint({ doNotCreate: true }) 132 | 133 | const fuse = new Fuse(nonexistentMnt, {}, { debug: false, mkdir: true }) 134 | fuse.mount(function (err) { 135 | t.error(err, 'no error') 136 | unmount(fuse, function (err) { 137 | t.end() 138 | }) 139 | }) 140 | }) 141 | 142 | tape('(osx only) unmount with Finder open succeeds', function (t) { 143 | if (os.platform() !== 'darwin') return t.end() 144 | const fuse = new Fuse(mnt, simpleFS(), { force: true, debug: false }) 145 | fuse.mount(function (err) { 146 | t.error(err, 'no error') 147 | exec(`open ${mnt}`, err => { 148 | t.error(err, 'no error') 149 | setTimeout(() => { 150 | fs.readdir(mnt, (err, list) => { 151 | t.error(err, 'no error') 152 | t.same(list, ['test']) 153 | unmount(fuse, err => { 154 | t.error(err, 'no error') 155 | fs.readdir(mnt, (err, list) => { 156 | t.error(err, 'no error') 157 | t.same(list, []) 158 | t.end() 159 | }) 160 | }) 161 | }) 162 | }, 1000) 163 | }) 164 | }) 165 | }) 166 | 167 | tape('(osx only) unmount with Terminal open succeeds', function (t) { 168 | if (os.platform() !== 'darwin') return t.end() 169 | const fuse = new Fuse(mnt, simpleFS(), { force: true, debug: false }) 170 | fuse.mount(function (err) { 171 | t.error(err, 'no error') 172 | exec(`open -a Terminal ${mnt}`, err => { 173 | t.error(err, 'no error') 174 | setTimeout(() => { 175 | fs.readdir(mnt, (err, list) => { 176 | t.error(err, 'no error') 177 | t.same(list, ['test']) 178 | unmount(fuse, err => { 179 | t.error(err, 'no error') 180 | fs.readdir(mnt, (err, list) => { 181 | t.error(err, 'no error') 182 | t.same(list, []) 183 | t.end() 184 | }) 185 | }) 186 | }) 187 | }, 1000) 188 | }) 189 | }) 190 | }) 191 | 192 | tape('static unmounting', function (t) { 193 | t.end() 194 | }) 195 | 196 | function createBrokenMountpoint (mnt) { 197 | spawnSync(process.execPath, ['-e', ` 198 | const Fuse = require('..') 199 | const mnt = ${JSON.stringify(mnt)} 200 | const fuse = new Fuse(mnt, {}, { force: true, debug: false }) 201 | fuse.mount(() => { 202 | process.exit(0) 203 | }) 204 | `], { 205 | cwd: __dirname, 206 | stdio: 'inherit' 207 | }) 208 | } 209 | -------------------------------------------------------------------------------- /test/read.js: -------------------------------------------------------------------------------- 1 | const tape = require('tape') 2 | const fs = require('fs') 3 | const path = require('path') 4 | const concat = require('concat-stream') 5 | 6 | const Fuse = require('../') 7 | const createMountpoint = require('./fixtures/mnt') 8 | const stat = require('./fixtures/stat') 9 | const simpleFS = require('./fixtures/simple-fs') 10 | 11 | const { unmount } = require('./helpers') 12 | const mnt = createMountpoint() 13 | 14 | tape('read', function (t) { 15 | const testFS = simpleFS({ 16 | release: function (path, fd) { 17 | t.same(fd, 42, 'fd was passed to release') 18 | } 19 | }) 20 | const fuse = new Fuse(mnt, testFS, { debug: true }) 21 | fuse.mount(function (err) { 22 | t.error(err, 'no error') 23 | 24 | fs.readFile(path.join(mnt, 'test'), function (err, buf) { 25 | t.error(err, 'no error') 26 | t.same(buf, Buffer.from('hello world'), 'read file') 27 | 28 | fs.readFile(path.join(mnt, 'test'), function (err, buf) { 29 | t.error(err, 'no error') 30 | t.same(buf, Buffer.from('hello world'), 'read file again') 31 | 32 | fs.createReadStream(path.join(mnt, 'test'), { start: 0, end: 4 }).pipe(concat(function (buf) { 33 | t.same(buf, Buffer.from('hello'), 'partial read file') 34 | 35 | fs.createReadStream(path.join(mnt, 'test'), { start: 6, end: 10 }).pipe(concat(function (buf) { 36 | t.same(buf, Buffer.from('world'), 'partial read file + start offset') 37 | 38 | unmount(fuse, function () { 39 | t.end() 40 | }) 41 | })) 42 | })) 43 | }) 44 | }) 45 | }) 46 | }) 47 | 48 | // Skipped because this test takes 2 minutes to run. 49 | tape.skip('read timeout does not force unmount', function (t) { 50 | var ops = { 51 | force: true, 52 | readdir: function (path, cb) { 53 | if (path === '/') return process.nextTick(cb, null, ['test']) 54 | return process.nextTick(cb, Fuse.ENOENT) 55 | }, 56 | getattr: function (path, cb) { 57 | if (path === '/') return process.nextTick(cb, null, stat({ mode: 'dir', size: 4096 })) 58 | if (path === '/test') return process.nextTick(cb, null, stat({ mode: 'file', size: 11 })) 59 | if (path === '/timeout') return process.nextTick(cb, null, stat({ mode: 'file', size: 11 })) 60 | return process.nextTick(cb, Fuse.ENOENT) 61 | }, 62 | open: function (path, flags, cb) { 63 | return process.nextTick(cb, 0, 42) 64 | }, 65 | release: function (path, fd, cb) { 66 | t.same(fd, 42, 'fd was passed to release') 67 | return process.nextTick(cb, 0) 68 | }, 69 | read: function (path, fd, buf, len, pos, cb) { 70 | if (path === '/test') { 71 | var str = 'hello world'.slice(pos, pos + len) 72 | if (!str) return process.nextTick(cb, 0) 73 | buf.write(str) 74 | return process.nextTick(cb, str.length) 75 | } else if (path === '/timeout') { 76 | console.log('read is gonna time out') 77 | // Just let this one timeout 78 | setTimeout(cb, 20 * 1000, -2) 79 | return 80 | } 81 | return cb(-2) 82 | } 83 | } 84 | 85 | const fuse = new Fuse(mnt, ops, { debug: false }) 86 | fuse.mount(function (err) { 87 | t.error(err, 'no error') 88 | 89 | fs.readFile(path.join(mnt, 'test'), function (err, buf) { 90 | t.error(err, 'no error') 91 | t.same(buf, Buffer.from('hello world'), 'read file') 92 | 93 | // Start the read that will time out, wait a bit, then ensure that the second read works. 94 | console.time('timeout') 95 | fs.readFile(path.join(mnt, 'timeout'), function (err, buf) { 96 | console.timeEnd('timeout') 97 | console.log('the read timed out') 98 | t.true(err) 99 | }) 100 | 101 | // The default FUSE timeout is 2 minutes, so wait another second after the timeout. 102 | setTimeout(function () { 103 | console.log('reading from test') 104 | fs.readFile(path.join(mnt, 'test'), function (err, buf) { 105 | t.error(err, 'no error') 106 | t.same(buf, Buffer.from('hello world'), 'read file') 107 | unmount(fuse, function () { 108 | t.end() 109 | }) 110 | }) 111 | }, 1000 * 121) 112 | }) 113 | }) 114 | }) 115 | 116 | 117 | -------------------------------------------------------------------------------- /test/statfs.js: -------------------------------------------------------------------------------- 1 | const { exec } = require('child_process') 2 | const { unmount } = require('./helpers') 3 | const tape = require('tape') 4 | 5 | const Fuse = require('../') 6 | const createMountpoint = require('./fixtures/mnt') 7 | const stat = require('./fixtures/stat') 8 | 9 | const mnt = createMountpoint() 10 | 11 | tape('statfs', function (t) { 12 | const ops = { 13 | force: true, 14 | statfs: function (path, cb) { 15 | return cb(0, { 16 | bsize: 1000000, 17 | frsize: 1000000, 18 | blocks: 1000000, 19 | bfree: 1000000, 20 | bavail: 1000000, 21 | files: 1000000, 22 | ffree: 1000000, 23 | favail: 1000000, 24 | fsid: 1000000, 25 | flag: 1000000, 26 | namemax: 1000000 27 | }) 28 | }, 29 | } 30 | const fuse = new Fuse(mnt, ops, { debug: true }) 31 | fuse.mount(function (err) { 32 | t.error(err, 'no error') 33 | exec(`df ${mnt}`, (err) => { 34 | t.error(err, 'no error') 35 | unmount(fuse, function () { 36 | t.end() 37 | }) 38 | }) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /test/write.js: -------------------------------------------------------------------------------- 1 | const tape = require('tape') 2 | const fs = require('fs') 3 | const path = require('path') 4 | 5 | const Fuse = require('../') 6 | const createMountpoint = require('./fixtures/mnt') 7 | const stat = require('./fixtures/stat') 8 | const { unmount } = require('./helpers') 9 | 10 | const mnt = createMountpoint() 11 | 12 | tape('write', function (t) { 13 | var created = false 14 | var data = Buffer.alloc(1024) 15 | var size = 0 16 | 17 | var ops = { 18 | force: true, 19 | readdir: function (path, cb) { 20 | if (path === '/') return process.nextTick(cb, null, created ? ['hello'] : [], []) 21 | return process.nextTick(cb, Fuse.ENOENT) 22 | }, 23 | truncate: function (path, size, cb) { 24 | process.nextTick(cb, 0) 25 | }, 26 | getattr: function (path, cb) { 27 | if (path === '/') return process.nextTick(cb, null, stat({ mode: 'dir', size: 4096 })) 28 | if (path === '/hello' && created) return process.nextTick(cb, 0, stat({ mode: 'file', size: size })) 29 | return process.nextTick(cb, Fuse.ENOENT) 30 | }, 31 | create: function (path, flags, cb) { 32 | t.ok(!created, 'file not created yet') 33 | created = true 34 | process.nextTick(cb, 0, 42) 35 | }, 36 | release: function (path, fd, cb) { 37 | process.nextTick(cb, 0) 38 | }, 39 | write: function (path, fd, buf, len, pos, cb) { 40 | buf.slice(0, len).copy(data, pos) 41 | size = Math.max(pos + len, size) 42 | process.nextTick(cb, buf.length) 43 | } 44 | } 45 | 46 | const fuse = new Fuse(mnt, ops, { debug: true }) 47 | fuse.mount(function (err) { 48 | t.error(err, 'no error') 49 | 50 | fs.writeFile(path.join(mnt, 'hello'), 'hello world', function (err) { 51 | t.error(err, 'no error') 52 | t.same(data.slice(0, size), Buffer.from('hello world'), 'data was written') 53 | 54 | unmount(fuse, function () { 55 | t.end() 56 | }) 57 | }) 58 | }) 59 | }) 60 | --------------------------------------------------------------------------------