mirror of
https://github.com/fuse-friends/fuse-native
synced 2025-06-13 12:53:54 +00:00
Compare commits
No commits in common. "v1.1.0" and "master" have entirely different histories.
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,2 @@
|
||||
node_modules
|
||||
.nadconfig.mk
|
||||
build
|
||||
|
51
.travis.yml
Normal file
51
.travis.yml
Normal file
@ -0,0 +1,51 @@
|
||||
language: node_js
|
||||
|
||||
sudo: required
|
||||
|
||||
osx_image: xcode8.3
|
||||
|
||||
node_js:
|
||||
- node
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
||||
- gcc-4.8-multilib
|
||||
- g++-4.8-multilib
|
||||
- gcc-multilib
|
||||
- g++-multilib
|
||||
|
||||
os:
|
||||
- osx
|
||||
- linux
|
||||
|
||||
before_install:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then cinst -y python2; fi
|
||||
|
||||
install:
|
||||
- npm install
|
||||
- npm run configure
|
||||
|
||||
before_deploy:
|
||||
- ARCHIVE_NAME="${TRAVIS_TAG:-latest}-$TRAVIS_OS_NAME.tar"
|
||||
- npm run prebuild
|
||||
- if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then PREBUILD_ARCH=ia32 npm run prebuild; fi
|
||||
- cd prebuilds && tar cvf "../$ARCHIVE_NAME" . && cd ..
|
||||
|
||||
cache:
|
||||
npm: false
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
draft: false
|
||||
prerelease: true
|
||||
api_key:
|
||||
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="
|
||||
file: "$ARCHIVE_NAME"
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
node: 'node'
|
21
LICENSE
21
LICENSE
@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Mathias Buus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
533
README.md
533
README.md
@ -1,36 +1,139 @@
|
||||
# fuse-bindings
|
||||
# fuse-native
|
||||
[](https://travis-ci.org/fuse-friends/fuse-native)
|
||||
|
||||
Fully maintained fuse bindings for Node that aims to cover the entire FUSE api
|
||||
Multithreaded FUSE bindings for Node JS.
|
||||
|
||||
## Features
|
||||
|
||||
* N-API support means we ship prebuilds and in general works on new Node.js releases.
|
||||
* Multithreading support means multiple calls to FUSE can run in parallel.
|
||||
* Close to feature complete in terms of the the FUSE API.
|
||||
* Embedded shared library support means users do not have to install FUSE from a 3rd party.
|
||||
* API support for initial FUSE kernel extension configuration so you can control the user experience.
|
||||
|
||||
## Installation
|
||||
```
|
||||
npm install fuse-bindings
|
||||
npm i fuse-native --save
|
||||
```
|
||||
|
||||
Created because [fuse4js](https://github.com/bcle/fuse4js) isn't maintained anymore and doesn't compile on iojs
|
||||
|
||||
## Requirements
|
||||
|
||||
You need to have FUSE installed
|
||||
|
||||
* On Linux/Ubuntu `sudo apt-get install libfuse-dev`
|
||||
* On OSX install [OSXFuse](http://osxfuse.github.com/)
|
||||
|
||||
## Usage
|
||||
|
||||
Try creating an empty folder called `mnt` and run the below example
|
||||
|
||||
## Example
|
||||
```js
|
||||
var fuse = require('fuse-bindings')
|
||||
|
||||
fuse.mount('./mnt', {
|
||||
const ops = {
|
||||
readdir: function (path, cb) {
|
||||
console.log('readdir(%s)', path)
|
||||
if (path === '/') return cb(0, ['test'])
|
||||
cb(0)
|
||||
if (path === '/') return cb(null, ['test'])
|
||||
return cb(Fuse.ENOENT)
|
||||
},
|
||||
getattr: function (path, cb) {
|
||||
console.log('getattr(%s)', path)
|
||||
if (path === '/') {
|
||||
if (path === '/') return cb(null, stat({ mode: 'dir', size: 4096 }))
|
||||
if (path === '/test') return cb(null, stat({ mode: 'file', size: 11 }))
|
||||
return cb(Fuse.ENOENT)
|
||||
},
|
||||
open: function (path, flags, cb) {
|
||||
return cb(0, 42)
|
||||
},
|
||||
release: function (path, fd, cb) {
|
||||
return cb(0)
|
||||
},
|
||||
read: function (path, fd, buf, len, pos, cb) {
|
||||
var str = 'hello world'.slice(pos, pos + len)
|
||||
if (!str) return cb(0)
|
||||
buf.write(str)
|
||||
return cb(str.length)
|
||||
}
|
||||
}
|
||||
|
||||
const fuse = new Fuse(mnt, ops, { debug: true })
|
||||
fuse.mount(function (err) {
|
||||
fs.readFile(path.join(mnt, 'test'), function (err, buf) {
|
||||
// buf should be 'hello world'
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## API
|
||||
In order to create a FUSE mountpoint, you first need to create a `Fuse` object that wraps a set of implemented FUSE syscall handlers:
|
||||
|
||||
#### `const fuse = new Fuse(mnt, handlers, opts = {})`
|
||||
Create a new `Fuse` object.
|
||||
|
||||
`mnt` is the string path of your desired mountpoint.
|
||||
|
||||
`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:
|
||||
```js
|
||||
{
|
||||
getattr: function (path, cb) {
|
||||
if (path === '/') return process.nextTick(cb, null, stat({ mode: 'dir', size: 4096 }))
|
||||
if (path === '/test') return process.nextTick(cb, null, stat({ mode: 'file', size: 11 }))
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`opts` can be include:
|
||||
```
|
||||
displayFolder: 'Folder Name', // Add a name/icon to the mount volume on OSX,
|
||||
debug: false, // Enable detailed tracing of operations.
|
||||
force: false, // Attempt to unmount a the mountpoint before remounting.
|
||||
mkdir: false // Create the mountpoint before mounting.
|
||||
```
|
||||
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).
|
||||
|
||||
#### `Fuse.isConfigured(cb)`
|
||||
|
||||
Returns `true` if FUSE has been configured on your machine and ready to be used, `false` otherwise.
|
||||
|
||||
#### `Fuse.configure(cb)`
|
||||
|
||||
Configures FUSE on your machine by enabling the FUSE kernel extension.
|
||||
You usually want to do this as part of an installation phase for your app.
|
||||
Might require `sudo` access.
|
||||
|
||||
#### `Fuse.unconfigure(cb)`
|
||||
|
||||
Unconfigures FUSE on your machine. Basically undos any change the above
|
||||
method does.
|
||||
|
||||
See the CLI section below on how to run these commands from the command line if you prefer doing that.
|
||||
|
||||
### FUSE API
|
||||
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.
|
||||
|
||||
#### `ops.init(cb)`
|
||||
|
||||
Called on filesystem init.
|
||||
|
||||
#### `ops.access(path, mode, cb)`
|
||||
|
||||
Called before the filesystem accessed a file
|
||||
|
||||
#### `ops.statfs(path, cb)`
|
||||
|
||||
Called when the filesystem is being stat'ed. Accepts a fs stat object after the return code in the callback.
|
||||
|
||||
``` js
|
||||
ops.statfs = function (path, cb) {
|
||||
cb(0, {
|
||||
bsize: 1000000,
|
||||
frsize: 1000000,
|
||||
blocks: 1000000,
|
||||
bfree: 1000000,
|
||||
bavail: 1000000,
|
||||
files: 1000000,
|
||||
ffree: 1000000,
|
||||
favail: 1000000,
|
||||
fsid: 1000000,
|
||||
flag: 1000000,
|
||||
namemax: 1000000
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
#### `ops.getattr(path, cb)`
|
||||
|
||||
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.
|
||||
|
||||
``` js
|
||||
ops.getattr = function (path, cb) {
|
||||
cb(0, {
|
||||
mtime: new Date(),
|
||||
atime: new Date(),
|
||||
@ -40,234 +143,198 @@ fuse.mount('./mnt', {
|
||||
uid: process.getuid(),
|
||||
gid: process.getgid()
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (path === '/test') {
|
||||
cb(0, {
|
||||
mtime: new Date(),
|
||||
atime: new Date(),
|
||||
ctime: new Date(),
|
||||
size: 12,
|
||||
mode: 33188,
|
||||
uid: process.getuid(),
|
||||
gid: process.getgid()
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
cb(fuse.ENOENT)
|
||||
},
|
||||
open: function (path, flags, cb) {
|
||||
console.log('open(%s, %d)', path, flags)
|
||||
cb(0, 42) // 42 is an fd
|
||||
},
|
||||
read: function (path, fd, buf, len, pos, cb) {
|
||||
console.log('read(%s, %d, %d, %d)', path, fd, len, pos)
|
||||
var str = 'hello world\n'.slice(pos)
|
||||
if (!str) return cb(0)
|
||||
buf.write(str)
|
||||
return cb(str.length)
|
||||
}
|
||||
})
|
||||
|
||||
process.on('SIGINT', function () {
|
||||
fuse.unmount('./mnt', function () {
|
||||
process.exit()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## API
|
||||
#### `ops.fgetattr(path, fd, cb)`
|
||||
|
||||
#### `fuse.mount(mnt, ops)`
|
||||
Same as above but is called when someone stats a file descriptor
|
||||
|
||||
Mount a new filesystem on `mnt`.
|
||||
Pass the FUSE operations you want to support as the `ops` argument.
|
||||
#### `ops.flush(path, fd, cb)`
|
||||
|
||||
#### `fuse.unmount(mnt, [cb])`
|
||||
Called when a file descriptor is being flushed
|
||||
|
||||
Unmount a filesystem
|
||||
#### `ops.fsync(path, fd, datasync, cb)`
|
||||
|
||||
#### `fuse.unmountSync(mnt)`
|
||||
Called when a file descriptor is being fsync'ed.
|
||||
|
||||
Unmount a filesystem synchroniously
|
||||
#### `ops.fsyncdir(path, fd, datasync, cb)`
|
||||
|
||||
## FUSE operations
|
||||
Same as above but on a directory
|
||||
|
||||
Most of the [FUSE api](http://fuse.sourceforge.net/doxygen/structfuse__operations.html) is supported.
|
||||
#### `ops.readdir(path, cb)`
|
||||
|
||||
* `ops.init(cb)`
|
||||
* `ops.access(path, cb)`
|
||||
* `ops.statfs(path, cb)`
|
||||
* `ops.getattr(path, cb)`
|
||||
* `ops.fgetattr(path, fd, cb)`
|
||||
* `ops.flush(path, fd, cb)`
|
||||
* `ops.fsync(path, fd, cb)`
|
||||
* `ops.fsyncdir(path, fd, cb)`
|
||||
* `ops.readdir(path, cb)`
|
||||
* `ops.truncate(path, size, cb)`
|
||||
* `ops.ftruncate(path, fd, size, cb)`
|
||||
* `ops.readlink(path, result, cb)`
|
||||
* `ops.chown(path, uid, gid, cb)`
|
||||
* `ops.chmod(path, mode, cb)`
|
||||
* `ops.setxattr(path, name, buf, length, offset, flags, cb)`
|
||||
* `ops.getxattr(path, name, buf, length, offset, cb)`
|
||||
* `ops.open(path, flags, cb)`
|
||||
* `ops.opendir(path, flags, cb)`
|
||||
* `ops.read(path, fd, buf, length, position, cb)`
|
||||
* `ops.write(path, fd, buf, length, position, cb)`
|
||||
* `ops.release(path, fd, cb)`
|
||||
* `ops.releasedir(path, fd, cb)`
|
||||
* `ops.create(path, mode, cb)`
|
||||
* `ops.utimens(path, cb)`
|
||||
* `ops.unlink(path, cb)`
|
||||
* `ops.rename(src, dest, cb)`
|
||||
* `ops.link(src, dest, cb)`
|
||||
* `ops.symlink(src, dest, cb)`
|
||||
* `ops.mkdir(path, cb)`
|
||||
* `ops.rmdir(path, cb)`
|
||||
* `ops.destroy(cb)`
|
||||
Called when a directory is being listed. Accepts an array of file/directory names after the return code in the callback
|
||||
|
||||
The goal is to support 100% of the API.
|
||||
``` js
|
||||
ops.readdir = function (path, cb) {
|
||||
cb(0, ['file-1.txt', 'dir'])
|
||||
}
|
||||
```
|
||||
|
||||
When using `getattr` you should return a stat object after the status code,
|
||||
`readdir` expects an array of directory names, `open` an optional fd and
|
||||
`utimens` an object with `mtime` and `atime` dates.
|
||||
#### `ops.truncate(path, size, cb)`
|
||||
|
||||
Both `read` and `write` passes the underlying fuse buffer without copying them to be as fast as possible.
|
||||
Called when a path is being truncated to a specific size
|
||||
|
||||
You can pass [mount options](http://blog.woralelandia.com/2012/07/16/fuse-mount-options/) (i.e. `direct_io` etc) as `ops.options = ['direct_io']`
|
||||
#### `ops.ftruncate(path, fd, size, cb)`
|
||||
|
||||
## Error codes
|
||||
Same as above but on a file descriptor
|
||||
|
||||
The available error codes are exposes as well as properties. These include
|
||||
#### `ops.readlink(path, cb)`
|
||||
|
||||
* `fuse.EPERM === -1`
|
||||
* `fuse.ENOENT === -2`
|
||||
* `fuse.ESRCH === -3`
|
||||
* `fuse.EINTR === -4`
|
||||
* `fuse.EIO === -5`
|
||||
* `fuse.ENXIO === -6`
|
||||
* `fuse.E2BIG === -7`
|
||||
* `fuse.ENOEXEC === -8`
|
||||
* `fuse.EBADF === -9`
|
||||
* `fuse.ECHILD === -10`
|
||||
* `fuse.EAGAIN === -11`
|
||||
* `fuse.ENOMEM === -12`
|
||||
* `fuse.EACCES === -13`
|
||||
* `fuse.EFAULT === -14`
|
||||
* `fuse.ENOTBLK === -15`
|
||||
* `fuse.EBUSY === -16`
|
||||
* `fuse.EEXIST === -17`
|
||||
* `fuse.EXDEV === -18`
|
||||
* `fuse.ENODEV === -19`
|
||||
* `fuse.ENOTDIR === -20`
|
||||
* `fuse.EISDIR === -21`
|
||||
* `fuse.EINVAL === -22`
|
||||
* `fuse.ENFILE === -23`
|
||||
* `fuse.EMFILE === -24`
|
||||
* `fuse.ENOTTY === -25`
|
||||
* `fuse.ETXTBSY === -26`
|
||||
* `fuse.EFBIG === -27`
|
||||
* `fuse.ENOSPC === -28`
|
||||
* `fuse.ESPIPE === -29`
|
||||
* `fuse.EROFS === -30`
|
||||
* `fuse.EMLINK === -31`
|
||||
* `fuse.EPIPE === -32`
|
||||
* `fuse.EDOM === -33`
|
||||
* `fuse.ERANGE === -34`
|
||||
* `fuse.EDEADLK === -35`
|
||||
* `fuse.ENAMETOOLONG === -36`
|
||||
* `fuse.ENOLCK === -37`
|
||||
* `fuse.ENOSYS === -38`
|
||||
* `fuse.ENOTEMPTY === -39`
|
||||
* `fuse.ELOOP === -40`
|
||||
* `fuse.EWOULDBLOCK === -11`
|
||||
* `fuse.ENOMSG === -42`
|
||||
* `fuse.EIDRM === -43`
|
||||
* `fuse.ECHRNG === -44`
|
||||
* `fuse.EL2NSYNC === -45`
|
||||
* `fuse.EL3HLT === -46`
|
||||
* `fuse.EL3RST === -47`
|
||||
* `fuse.ELNRNG === -48`
|
||||
* `fuse.EUNATCH === -49`
|
||||
* `fuse.ENOCSI === -50`
|
||||
* `fuse.EL2HLT === -51`
|
||||
* `fuse.EBADE === -52`
|
||||
* `fuse.EBADR === -53`
|
||||
* `fuse.EXFULL === -54`
|
||||
* `fuse.ENOANO === -55`
|
||||
* `fuse.EBADRQC === -56`
|
||||
* `fuse.EBADSLT === -57`
|
||||
* `fuse.EDEADLOCK === -35`
|
||||
* `fuse.EBFONT === -59`
|
||||
* `fuse.ENOSTR === -60`
|
||||
* `fuse.ENODATA === -61`
|
||||
* `fuse.ETIME === -62`
|
||||
* `fuse.ENOSR === -63`
|
||||
* `fuse.ENONET === -64`
|
||||
* `fuse.ENOPKG === -65`
|
||||
* `fuse.EREMOTE === -66`
|
||||
* `fuse.ENOLINK === -67`
|
||||
* `fuse.EADV === -68`
|
||||
* `fuse.ESRMNT === -69`
|
||||
* `fuse.ECOMM === -70`
|
||||
* `fuse.EPROTO === -71`
|
||||
* `fuse.EMULTIHOP === -72`
|
||||
* `fuse.EDOTDOT === -73`
|
||||
* `fuse.EBADMSG === -74`
|
||||
* `fuse.EOVERFLOW === -75`
|
||||
* `fuse.ENOTUNIQ === -76`
|
||||
* `fuse.EBADFD === -77`
|
||||
* `fuse.EREMCHG === -78`
|
||||
* `fuse.ELIBACC === -79`
|
||||
* `fuse.ELIBBAD === -80`
|
||||
* `fuse.ELIBSCN === -81`
|
||||
* `fuse.ELIBMAX === -82`
|
||||
* `fuse.ELIBEXEC === -83`
|
||||
* `fuse.EILSEQ === -84`
|
||||
* `fuse.ERESTART === -85`
|
||||
* `fuse.ESTRPIPE === -86`
|
||||
* `fuse.EUSERS === -87`
|
||||
* `fuse.ENOTSOCK === -88`
|
||||
* `fuse.EDESTADDRREQ === -89`
|
||||
* `fuse.EMSGSIZE === -90`
|
||||
* `fuse.EPROTOTYPE === -91`
|
||||
* `fuse.ENOPROTOOPT === -92`
|
||||
* `fuse.EPROTONOSUPPORT === -93`
|
||||
* `fuse.ESOCKTNOSUPPORT === -94`
|
||||
* `fuse.EOPNOTSUPP === -95`
|
||||
* `fuse.EPFNOSUPPORT === -96`
|
||||
* `fuse.EAFNOSUPPORT === -97`
|
||||
* `fuse.EADDRINUSE === -98`
|
||||
* `fuse.EADDRNOTAVAIL === -99`
|
||||
* `fuse.ENETDOWN === -100`
|
||||
* `fuse.ENETUNREACH === -101`
|
||||
* `fuse.ENETRESET === -102`
|
||||
* `fuse.ECONNABORTED === -103`
|
||||
* `fuse.ECONNRESET === -104`
|
||||
* `fuse.ENOBUFS === -105`
|
||||
* `fuse.EISCONN === -106`
|
||||
* `fuse.ENOTCONN === -107`
|
||||
* `fuse.ESHUTDOWN === -108`
|
||||
* `fuse.ETOOMANYREFS === -109`
|
||||
* `fuse.ETIMEDOUT === -110`
|
||||
* `fuse.ECONNREFUSED === -111`
|
||||
* `fuse.EHOSTDOWN === -112`
|
||||
* `fuse.EHOSTUNREACH === -113`
|
||||
* `fuse.EALREADY === -114`
|
||||
* `fuse.EINPROGRESS === -115`
|
||||
* `fuse.ESTALE === -116`
|
||||
* `fuse.EUCLEAN === -117`
|
||||
* `fuse.ENOTNAM === -118`
|
||||
* `fuse.ENAVAIL === -119`
|
||||
* `fuse.EISNAM === -120`
|
||||
* `fuse.EREMOTEIO === -121`
|
||||
* `fuse.EDQUOT === -122`
|
||||
* `fuse.ENOMEDIUM === -123`
|
||||
* `fuse.EMEDIUMTYPE === -124`
|
||||
Called when a symlink is being resolved. Accepts a pathname (that the link should resolve to) after the return code in the callback
|
||||
|
||||
``` js
|
||||
ops.readlink = function (path, cb) {
|
||||
cb(null, 'file.txt') // make link point to file.txt
|
||||
}
|
||||
```
|
||||
|
||||
#### `ops.chown(path, uid, gid, cb)`
|
||||
|
||||
Called when ownership of a path is being changed
|
||||
|
||||
#### `ops.chmod(path, mode, cb)`
|
||||
|
||||
Called when the mode of a path is being changed
|
||||
|
||||
#### `ops.mknod(path, mode, dev, cb)`
|
||||
|
||||
Called when the a new device file is being made.
|
||||
|
||||
#### `ops.setxattr(path, name, value, position, flags, cb)`
|
||||
|
||||
Called when extended attributes is being set (see the extended docs for your platform).
|
||||
|
||||
Copy the `value` buffer somewhere to store it.
|
||||
|
||||
The position argument is mostly a legacy argument only used on MacOS but see the getxattr docs
|
||||
on Mac for more on that (you probably don't need to use that).
|
||||
|
||||
#### `ops.getxattr(path, name, position, cb)`
|
||||
|
||||
Called when extended attributes is being read.
|
||||
|
||||
Return the extended attribute as the second argument to the callback (needs to be a buffer).
|
||||
If no attribute is stored return `null` as the second argument.
|
||||
|
||||
The position argument is mostly a legacy argument only used on MacOS but see the getxattr docs
|
||||
on Mac for more on that (you probably don't need to use that).
|
||||
|
||||
#### `ops.listxattr(path, cb)`
|
||||
|
||||
Called when extended attributes of a path are being listed.
|
||||
|
||||
Return a list of strings of the names of the attributes you have stored as the second argument to the callback.
|
||||
|
||||
#### `ops.removexattr(path, name, cb)`
|
||||
|
||||
Called when an extended attribute is being removed.
|
||||
|
||||
#### `ops.open(path, flags, cb)`
|
||||
|
||||
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.
|
||||
|
||||
``` js
|
||||
var toFlag = function(flags) {
|
||||
flags = flags & 3
|
||||
if (flags === 0) return 'r'
|
||||
if (flags === 1) return 'w'
|
||||
return 'r+'
|
||||
}
|
||||
|
||||
ops.open = function (path, flags, cb) {
|
||||
var flag = toFlag(flags) // convert flags to a node style string
|
||||
...
|
||||
cb(0, 42) // 42 is a file descriptor
|
||||
}
|
||||
```
|
||||
|
||||
#### `ops.opendir(path, flags, cb)`
|
||||
|
||||
Same as above but for directories
|
||||
|
||||
#### `ops.read(path, fd, buffer, length, position, cb)`
|
||||
|
||||
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.
|
||||
If no bytes were written (read is complete) return 0 in the callback.
|
||||
|
||||
``` js
|
||||
var data = new Buffer('hello world')
|
||||
|
||||
ops.read = function (path, fd, buffer, length, position, cb) {
|
||||
if (position >= data.length) return cb(0) // done
|
||||
var part = data.slice(position, position + length)
|
||||
part.copy(buffer) // write the result of the read to the result buffer
|
||||
cb(part.length) // return the number of bytes read
|
||||
}
|
||||
```
|
||||
|
||||
#### `ops.write(path, fd, buffer, length, position, cb)`
|
||||
|
||||
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.
|
||||
|
||||
``` js
|
||||
ops.write = function (path, fd, buffer, length, position, cb) {
|
||||
console.log('writing', buffer.slice(0, length))
|
||||
cb(length) // we handled all the data
|
||||
}
|
||||
```
|
||||
|
||||
#### `ops.release(path, fd, cb)`
|
||||
|
||||
Called when a file descriptor is being released. Happens when a read/write is done etc.
|
||||
|
||||
#### `ops.releasedir(path, fd, cb)`
|
||||
|
||||
Same as above but for directories
|
||||
|
||||
#### `ops.create(path, mode, cb)`
|
||||
|
||||
Called when a new file is being opened.
|
||||
|
||||
#### `ops.utimens(path, atime, mtime, cb)`
|
||||
|
||||
Called when the atime/mtime of a file is being changed.
|
||||
|
||||
#### `ops.unlink(path, cb)`
|
||||
|
||||
Called when a file is being unlinked.
|
||||
|
||||
#### `ops.rename(src, dest, cb)`
|
||||
|
||||
Called when a file is being renamed.
|
||||
|
||||
#### `ops.link(src, dest, cb)`
|
||||
|
||||
Called when a new link is created.
|
||||
|
||||
#### `ops.symlink(src, dest, cb)`
|
||||
|
||||
Called when a new symlink is created
|
||||
|
||||
#### `ops.mkdir(path, mode, cb)`
|
||||
|
||||
Called when a new directory is being created
|
||||
|
||||
#### `ops.rmdir(path, cb)`
|
||||
|
||||
Called when a directory is being removed
|
||||
|
||||
## CLI
|
||||
|
||||
There is a CLI tool available to help you configure the FUSE kernel extension setup
|
||||
if you don't want to use the JavaScript API for that
|
||||
|
||||
```
|
||||
npm install -g fuse-native
|
||||
fuse-native is-configured # checks if the kernel extension is already configured
|
||||
fuse-native configure # configures the kernel extension
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
MIT for these bindings.
|
||||
|
||||
See the [OSXFUSE](https://github.com/osxfuse/osxfuse) license for MacOS and the [libfuse](https://github.com/libfuse/libfuse) license for Linux/BSD
|
||||
for the FUSE shared library licence.
|
||||
|
20
bin.js
Executable file
20
bin.js
Executable file
@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const Fuse = require('./')
|
||||
const cmd = process.argv[2]
|
||||
|
||||
if (cmd === 'configure') {
|
||||
Fuse.configure(onerror)
|
||||
} else if (cmd === 'unconfigure') {
|
||||
Fuse.unconfigure(onerror)
|
||||
} else if (cmd === 'is-configured') {
|
||||
Fuse.isConfigured(function (err, bool) {
|
||||
if (err) return onerror(err)
|
||||
console.log('' + bool)
|
||||
process.exit(bool ? 0 : 1)
|
||||
})
|
||||
}
|
||||
|
||||
function onerror (err) {
|
||||
if (err) throw err
|
||||
}
|
40
binding.gyp
40
binding.gyp
@ -1,17 +1,35 @@
|
||||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "fuse_bindings",
|
||||
"sources": [ "fuse-bindings.cc" ],
|
||||
"targets": [{
|
||||
"target_name": "fuse",
|
||||
"include_dirs": [
|
||||
"<!(node -e \"require('nan')\")",
|
||||
"<!@(pkg-config fuse --cflags-only-I | sed s/-I//g)"
|
||||
"<!(node -e \"require('napi-macros')\")",
|
||||
"<!(node -e \"require('fuse-shared-library/include')\")",
|
||||
],
|
||||
"link_settings": {
|
||||
"libraries": [
|
||||
"<!@(pkg-config --libs-only-l fuse)"
|
||||
]
|
||||
}
|
||||
}
|
||||
"<!(node -e \"require('fuse-shared-library/lib')\")",
|
||||
],
|
||||
"sources": [
|
||||
"fuse-native.c"
|
||||
],
|
||||
'xcode_settings': {
|
||||
'OTHER_CFLAGS': [
|
||||
'-g',
|
||||
'-O3',
|
||||
'-Wall'
|
||||
]
|
||||
},
|
||||
'cflags': [
|
||||
'-g',
|
||||
'-O3',
|
||||
'-Wall'
|
||||
],
|
||||
}, {
|
||||
"target_name": "postinstall",
|
||||
"type": "none",
|
||||
"dependencies": ["fuse"],
|
||||
"copies": [{
|
||||
"destination": "build/Release",
|
||||
"files": [ "<!(node -e \"require('fuse-shared-library/lib')\")" ],
|
||||
}]
|
||||
}]
|
||||
}
|
||||
|
64
example.js
64
example.js
@ -1,56 +1,82 @@
|
||||
var fuse = require('./')
|
||||
const Fuse = require('./')
|
||||
|
||||
fuse.mount('./mnt', {
|
||||
const ops = {
|
||||
readdir: function (path, cb) {
|
||||
console.log('readdir(%s)', path)
|
||||
if (path === '/') return cb(0, ['test'])
|
||||
cb(0)
|
||||
if (path === '/') return process.nextTick(cb, 0, ['test'], [
|
||||
{
|
||||
mtime: new Date(),
|
||||
atime: new Date(),
|
||||
ctime: new Date(),
|
||||
nlink: 1,
|
||||
size: 12,
|
||||
mode: 33188,
|
||||
uid: process.getuid ? process.getuid() : 0,
|
||||
gid: process.getgid ? process.getgid() : 0
|
||||
}
|
||||
])
|
||||
return process.nextTick(cb, 0)
|
||||
},
|
||||
/*
|
||||
access: function (path, cb) {
|
||||
return process.nextTick(cb, 0)
|
||||
},
|
||||
*/
|
||||
getattr: function (path, cb) {
|
||||
console.log('getattr(%s)', path)
|
||||
if (path === '/') {
|
||||
cb(0, {
|
||||
return process.nextTick(cb, 0, {
|
||||
mtime: new Date(),
|
||||
atime: new Date(),
|
||||
ctime: new Date(),
|
||||
nlink: 1,
|
||||
size: 100,
|
||||
mode: 16877,
|
||||
uid: process.getuid(),
|
||||
gid: process.getgid()
|
||||
uid: process.getuid ? process.getuid() : 0,
|
||||
gid: process.getgid ? process.getgid() : 0
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (path === '/test') {
|
||||
cb(0, {
|
||||
return process.nextTick(cb, 0, {
|
||||
mtime: new Date(),
|
||||
atime: new Date(),
|
||||
ctime: new Date(),
|
||||
nlink: 1,
|
||||
size: 12,
|
||||
mode: 33188,
|
||||
uid: process.getuid(),
|
||||
gid: process.getgid()
|
||||
uid: process.getuid ? process.getuid() : 0,
|
||||
gid: process.getgid ? process.getgid() : 0
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
cb(fuse.ENOENT)
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
},
|
||||
open: function (path, flags, cb) {
|
||||
console.log('open(%s, %d)', path, flags)
|
||||
cb(0, 42) // 42 is an fd
|
||||
return process.nextTick(cb, 0, 42) // 42 is an fd
|
||||
},
|
||||
read: function (path, fd, buf, len, pos, cb) {
|
||||
console.log('read(%s, %d, %d, %d)', path, fd, len, pos)
|
||||
var str = 'hello world\n'.slice(pos)
|
||||
if (!str) return cb(0)
|
||||
if (!str) return process.nextTick(cb, 0)
|
||||
buf.write(str)
|
||||
return cb(str.length)
|
||||
return process.nextTick(cb, str.length)
|
||||
}
|
||||
}
|
||||
|
||||
const fuse = new Fuse('./mnt', ops, { debug: true, displayFolder: true })
|
||||
fuse.mount(err => {
|
||||
if (err) throw err
|
||||
console.log('filesystem mounted on ' + fuse.mnt)
|
||||
})
|
||||
|
||||
process.on('SIGINT', function () {
|
||||
fuse.unmount('./mnt', function () {
|
||||
process.exit()
|
||||
process.once('SIGINT', function () {
|
||||
fuse.unmount(err => {
|
||||
if (err) {
|
||||
console.log('filesystem at ' + fuse.mnt + ' not unmounted', err)
|
||||
} else {
|
||||
console.log('filesystem at ' + fuse.mnt + ' unmounted')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
964
fuse-bindings.cc
964
fuse-bindings.cc
@ -1,964 +0,0 @@
|
||||
#include <nan.h>
|
||||
|
||||
#define FUSE_USE_VERSION 26
|
||||
|
||||
#include <fuse.h>
|
||||
#include <semaphore.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mount.h>
|
||||
#ifdef __APPLE__
|
||||
#include <dispatch/dispatch.h>
|
||||
#endif
|
||||
|
||||
using namespace v8;
|
||||
|
||||
enum bindings_ops_t {
|
||||
OP_INIT = 0,
|
||||
OP_ACCESS,
|
||||
OP_STATFS,
|
||||
OP_FGETATTR,
|
||||
OP_GETATTR,
|
||||
OP_FLUSH,
|
||||
OP_FSYNC,
|
||||
OP_FSYNCDIR,
|
||||
OP_READDIR,
|
||||
OP_TRUNCATE,
|
||||
OP_FTRUNCATE,
|
||||
OP_UTIMENS,
|
||||
OP_READLINK,
|
||||
OP_CHOWN,
|
||||
OP_CHMOD,
|
||||
OP_SETXATTR,
|
||||
OP_GETXATTR,
|
||||
OP_OPEN,
|
||||
OP_OPENDIR,
|
||||
OP_READ,
|
||||
OP_WRITE,
|
||||
OP_RELEASE,
|
||||
OP_RELEASEDIR,
|
||||
OP_CREATE,
|
||||
OP_UNLINK,
|
||||
OP_RENAME,
|
||||
OP_LINK,
|
||||
OP_SYMLINK,
|
||||
OP_MKDIR,
|
||||
OP_RMDIR,
|
||||
OP_DESTROY
|
||||
};
|
||||
|
||||
static Persistent<Function> buffer_constructor;
|
||||
static struct stat empty_stat;
|
||||
|
||||
// TODO support more than a single mount
|
||||
static struct {
|
||||
// fuse data
|
||||
char mnt[1024];
|
||||
char mntopts[1024];
|
||||
pthread_t thread;
|
||||
#ifdef __APPLE__
|
||||
dispatch_semaphore_t semaphore;
|
||||
#else
|
||||
sem_t semaphore;
|
||||
#endif
|
||||
uv_async_t async;
|
||||
|
||||
// methods
|
||||
NanCallback *ops_init;
|
||||
NanCallback *ops_access;
|
||||
NanCallback *ops_statfs;
|
||||
NanCallback *ops_getattr;
|
||||
NanCallback *ops_fgetattr;
|
||||
NanCallback *ops_flush;
|
||||
NanCallback *ops_fsync;
|
||||
NanCallback *ops_fsyncdir;
|
||||
NanCallback *ops_readdir;
|
||||
NanCallback *ops_truncate;
|
||||
NanCallback *ops_ftruncate;
|
||||
NanCallback *ops_readlink;
|
||||
NanCallback *ops_chown;
|
||||
NanCallback *ops_chmod;
|
||||
NanCallback *ops_setxattr;
|
||||
NanCallback *ops_getxattr;
|
||||
NanCallback *ops_open;
|
||||
NanCallback *ops_opendir;
|
||||
NanCallback *ops_read;
|
||||
NanCallback *ops_write;
|
||||
NanCallback *ops_release;
|
||||
NanCallback *ops_releasedir;
|
||||
NanCallback *ops_create;
|
||||
NanCallback *ops_utimens;
|
||||
NanCallback *ops_unlink;
|
||||
NanCallback *ops_rename;
|
||||
NanCallback *ops_link;
|
||||
NanCallback *ops_symlink;
|
||||
NanCallback *ops_mkdir;
|
||||
NanCallback *ops_rmdir;
|
||||
NanCallback *ops_destroy;
|
||||
|
||||
// method data
|
||||
bindings_ops_t op;
|
||||
NanCallback *callback;
|
||||
fuse_fill_dir_t filler; // used in readdir
|
||||
struct fuse_file_info *info;
|
||||
char *path;
|
||||
char *name;
|
||||
off_t offset;
|
||||
off_t length;
|
||||
void *data; // various structs
|
||||
int mode;
|
||||
int uid;
|
||||
int gid;
|
||||
int result;
|
||||
} bindings;
|
||||
|
||||
#ifdef __APPLE__
|
||||
static void bindings_unmount (char *path) {
|
||||
unmount(path, 0);
|
||||
}
|
||||
|
||||
static int semaphore_init (dispatch_semaphore_t *sem) {
|
||||
*sem = dispatch_semaphore_create(0);
|
||||
return *sem == NULL ? -1 : 0;
|
||||
}
|
||||
|
||||
static void semaphore_wait (dispatch_semaphore_t *sem) {
|
||||
dispatch_semaphore_wait(*sem, DISPATCH_TIME_FOREVER);
|
||||
}
|
||||
|
||||
static void semaphore_signal (dispatch_semaphore_t *sem) {
|
||||
dispatch_semaphore_signal(*sem);
|
||||
}
|
||||
#else
|
||||
static void bindings_unmount (char *path) {
|
||||
umount(path);
|
||||
}
|
||||
|
||||
static int semaphore_init (sem_t *sem) {
|
||||
return sem_init(sem, 0, 0);
|
||||
}
|
||||
|
||||
static void semaphore_wait (sem_t *sem) {
|
||||
sem_wait(sem);
|
||||
}
|
||||
|
||||
static void semaphore_signal (sem_t *sem) {
|
||||
sem_post(sem);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION)
|
||||
NAN_INLINE v8::Local<v8::Object> bindings_buffer (char *data, size_t length) {
|
||||
Local<Object> buf = NanNew(buffer_constructor)->NewInstance(0, NULL);
|
||||
buf->Set(NanNew<String>("length"), NanNew<Number>(length));
|
||||
buf->SetIndexedPropertiesToExternalArrayData((char *) data, kExternalUnsignedByteArray, length);
|
||||
return buf;
|
||||
}
|
||||
#else
|
||||
void noop (char *data, void *hint) {}
|
||||
NAN_INLINE v8::Local<v8::Object> bindings_buffer (char *data, size_t length) {
|
||||
return NanNewBufferHandle(data, length, noop, NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
NAN_INLINE static int bindings_call () {
|
||||
uv_async_send(&bindings.async);
|
||||
semaphore_wait(&bindings.semaphore);
|
||||
return bindings.result;
|
||||
}
|
||||
|
||||
static int bindings_truncate (const char *path, off_t size) {
|
||||
bindings.op = OP_TRUNCATE;
|
||||
bindings.path = (char *) path;
|
||||
bindings.length = size;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_ftruncate (const char *path, off_t size, struct fuse_file_info *info) {
|
||||
bindings.op = OP_FTRUNCATE;
|
||||
bindings.path = (char *) path;
|
||||
bindings.length = size;
|
||||
bindings.info = info;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_getattr (const char *path, struct stat *stat) {
|
||||
bindings.op = OP_GETATTR;
|
||||
bindings.path = (char *) path;
|
||||
bindings.data = stat;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_fgetattr (const char *path, struct stat *stat, struct fuse_file_info *info) {
|
||||
bindings.op = OP_FGETATTR;
|
||||
bindings.path = (char *) path;
|
||||
bindings.data = stat;
|
||||
bindings.info = info;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_flush (const char *path, struct fuse_file_info *info) {
|
||||
bindings.op = OP_FLUSH;
|
||||
bindings.path = (char *) path;
|
||||
bindings.info = info;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_fsync (const char *path, int datasync, struct fuse_file_info *info) {
|
||||
bindings.op = OP_FSYNC;
|
||||
bindings.path = (char *) path;
|
||||
bindings.mode = datasync;
|
||||
bindings.info = info;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_fsyncdir (const char *path, int datasync, struct fuse_file_info *info) {
|
||||
bindings.op = OP_FSYNCDIR;
|
||||
bindings.path = (char *) path;
|
||||
bindings.mode = datasync;
|
||||
bindings.info = info;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_readdir (const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *info) {
|
||||
bindings.op = OP_READDIR;
|
||||
bindings.path = (char *) path;
|
||||
bindings.data = buf;
|
||||
bindings.filler = filler;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_readlink (const char *path, char *buf, size_t len) {
|
||||
bindings.op = OP_READLINK;
|
||||
bindings.path = (char *) path;
|
||||
bindings.data = (void *) buf;
|
||||
bindings.length = len;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_chown (const char *path, uid_t uid, gid_t gid) {
|
||||
bindings.op = OP_CHOWN;
|
||||
bindings.path = (char *) path;
|
||||
bindings.uid = uid;
|
||||
bindings.gid = gid;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_chmod (const char *path, mode_t mode) {
|
||||
bindings.op = OP_CHMOD;
|
||||
bindings.path = (char *) path;
|
||||
bindings.mode = mode;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
static int bindings_setxattr (const char *path, const char *name, const char *value, size_t size, int flags, uint32_t position) {
|
||||
bindings.op = OP_SETXATTR;
|
||||
bindings.path = (char *) path;
|
||||
bindings.name = (char *) name;
|
||||
bindings.data = (void *) value;
|
||||
bindings.length = size;
|
||||
bindings.offset = position;
|
||||
bindings.mode = flags;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_getxattr (const char *path, const char *name, char *value, size_t size, uint32_t position) {
|
||||
bindings.op = OP_GETXATTR;
|
||||
bindings.path = (char *) path;
|
||||
bindings.name = (char *) name;
|
||||
bindings.data = (void *) value;
|
||||
bindings.length = size;
|
||||
bindings.offset = position;
|
||||
return bindings_call();
|
||||
}
|
||||
#else
|
||||
static int bindings_setxattr (const char *path, const char *name, const char *value, size_t size, int flags) {
|
||||
bindings.op = OP_SETXATTR;
|
||||
bindings.path = (char *) path;
|
||||
bindings.name = (char *) name;
|
||||
bindings.data = (void *) value;
|
||||
bindings.length = size;
|
||||
bindings.offset = 0;
|
||||
bindings.mode = flags;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_getxattr (const char *path, const char *name, char *value, size_t size) {
|
||||
bindings.op = OP_GETXATTR;
|
||||
bindings.path = (char *) path;
|
||||
bindings.name = (char *) name;
|
||||
bindings.data = (void *) value;
|
||||
bindings.length = size;
|
||||
bindings.offset = 0;
|
||||
return bindings_call();
|
||||
}
|
||||
#endif
|
||||
|
||||
static int bindings_statfs (const char *path, struct statvfs *statfs) {
|
||||
bindings.op = OP_STATFS;
|
||||
bindings.path = (char *) path;
|
||||
bindings.data = statfs;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_open (const char *path, struct fuse_file_info *info) {
|
||||
bindings.op = OP_OPEN;
|
||||
bindings.path = (char *) path;
|
||||
bindings.mode = info->flags;
|
||||
bindings.info = info;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_opendir (const char *path, struct fuse_file_info *info) {
|
||||
bindings.op = OP_OPENDIR;
|
||||
bindings.path = (char *) path;
|
||||
bindings.mode = info->flags;
|
||||
bindings.info = info;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_read (const char *path, char *buf, size_t len, off_t offset, struct fuse_file_info *info) {
|
||||
bindings.op = OP_READ;
|
||||
bindings.path = (char *) path;
|
||||
bindings.data = (void *) buf;
|
||||
bindings.offset = offset;
|
||||
bindings.length = len;
|
||||
bindings.info = info;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_write (const char *path, const char *buf, size_t len, off_t offset, struct fuse_file_info * info) {
|
||||
bindings.op = OP_WRITE;
|
||||
bindings.path = (char *) path;
|
||||
bindings.data = (void *) buf;
|
||||
bindings.offset = offset;
|
||||
bindings.length = len;
|
||||
bindings.info = info;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_release (const char *path, struct fuse_file_info *info) {
|
||||
bindings.op = OP_RELEASE;
|
||||
bindings.path = (char *) path;
|
||||
bindings.info = info;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_releasedir (const char *path, struct fuse_file_info *info) {
|
||||
bindings.op = OP_RELEASEDIR;
|
||||
bindings.path = (char *) path;
|
||||
bindings.info = info;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_access (const char *path, int mode) {
|
||||
bindings.op = OP_ACCESS;
|
||||
bindings.path = (char *) path;
|
||||
bindings.mode = mode;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_create (const char *path, mode_t mode, struct fuse_file_info *info) {
|
||||
bindings.op = OP_CREATE;
|
||||
bindings.path = (char *) path;
|
||||
bindings.mode = mode;
|
||||
bindings.info = info;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_utimens (const char *path, const struct timespec tv[2]) {
|
||||
bindings.op = OP_UTIMENS;
|
||||
bindings.path = (char *) path;
|
||||
bindings.data = (void *) tv;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_unlink (const char *path) {
|
||||
bindings.op = OP_UNLINK;
|
||||
bindings.path = (char *) path;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_rename (const char *src, const char *dest) {
|
||||
bindings.op = OP_RENAME;
|
||||
bindings.path = (char *) src;
|
||||
bindings.data = (void *) dest;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_link (const char *path, const char *dest) {
|
||||
bindings.op = OP_LINK;
|
||||
bindings.path = (char *) path;
|
||||
bindings.data = (void *) dest;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_symlink (const char *path, const char *dest) {
|
||||
bindings.op = OP_SYMLINK;
|
||||
bindings.path = (char *) path;
|
||||
bindings.data = (void *) dest;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_mkdir (const char *path, mode_t mode) {
|
||||
bindings.op = OP_MKDIR;
|
||||
bindings.path = (char *) path;
|
||||
bindings.mode = mode;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static int bindings_rmdir (const char *path) {
|
||||
bindings.op = OP_RMDIR;
|
||||
bindings.path = (char *) path;
|
||||
return bindings_call();
|
||||
}
|
||||
|
||||
static void* bindings_init (struct fuse_conn_info *conn) {
|
||||
bindings.op = OP_INIT;
|
||||
bindings_call();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void bindings_destroy (void *data) {
|
||||
bindings.op = OP_DESTROY;
|
||||
bindings_call();
|
||||
}
|
||||
|
||||
static void *bindings_thread (void *) {
|
||||
struct fuse_operations ops = { };
|
||||
|
||||
if (bindings.ops_access != NULL) ops.access = bindings_access;
|
||||
if (bindings.ops_truncate != NULL) ops.truncate = bindings_truncate;
|
||||
if (bindings.ops_ftruncate != NULL) ops.ftruncate = bindings_ftruncate;
|
||||
if (bindings.ops_getattr != NULL) ops.getattr = bindings_getattr;
|
||||
if (bindings.ops_fgetattr != NULL) ops.fgetattr = bindings_fgetattr;
|
||||
if (bindings.ops_flush != NULL) ops.flush = bindings_flush;
|
||||
if (bindings.ops_fsync != NULL) ops.fsync = bindings_fsync;
|
||||
if (bindings.ops_fsyncdir != NULL) ops.fsyncdir = bindings_fsyncdir;
|
||||
if (bindings.ops_readdir != NULL) ops.readdir = bindings_readdir;
|
||||
if (bindings.ops_readlink != NULL) ops.readlink = bindings_readlink;
|
||||
if (bindings.ops_chown != NULL) ops.chown = bindings_chown;
|
||||
if (bindings.ops_chmod != NULL) ops.chmod = bindings_chmod;
|
||||
if (bindings.ops_setxattr != NULL) ops.setxattr = bindings_setxattr;
|
||||
if (bindings.ops_getxattr != NULL) ops.getxattr = bindings_getxattr;
|
||||
if (bindings.ops_statfs != NULL) ops.statfs = bindings_statfs;
|
||||
if (bindings.ops_open != NULL) ops.open = bindings_open;
|
||||
if (bindings.ops_opendir != NULL) ops.opendir = bindings_opendir;
|
||||
if (bindings.ops_read != NULL) ops.read = bindings_read;
|
||||
if (bindings.ops_write != NULL) ops.write = bindings_write;
|
||||
if (bindings.ops_release != NULL) ops.release = bindings_release;
|
||||
if (bindings.ops_releasedir != NULL) ops.releasedir = bindings_releasedir;
|
||||
if (bindings.ops_create != NULL) ops.create = bindings_create;
|
||||
if (bindings.ops_utimens != NULL) ops.utimens = bindings_utimens;
|
||||
if (bindings.ops_unlink != NULL) ops.unlink = bindings_unlink;
|
||||
if (bindings.ops_rename != NULL) ops.rename = bindings_rename;
|
||||
if (bindings.ops_link != NULL) ops.link = bindings_link;
|
||||
if (bindings.ops_symlink != NULL) ops.symlink = bindings_symlink;
|
||||
if (bindings.ops_mkdir != NULL) ops.mkdir = bindings_mkdir;
|
||||
if (bindings.ops_rmdir != NULL) ops.rmdir = bindings_rmdir;
|
||||
if (bindings.ops_init != NULL) ops.init = bindings_init;
|
||||
if (bindings.ops_destroy != NULL) ops.destroy = bindings_destroy;
|
||||
|
||||
char *argv[] = {
|
||||
(char *) "dummy",
|
||||
(char *) "-s",
|
||||
(char *) "-f",
|
||||
(char *) bindings.mnt,
|
||||
(char *) bindings.mntopts
|
||||
};
|
||||
|
||||
// bindings_unmount(bindings.mnt); // should probably throw instead if mounted
|
||||
|
||||
if (fuse_main(!strcmp(bindings.mntopts, "-o") ? 4 : 5, argv, &ops, NULL)) {
|
||||
// TODO: error handle somehow
|
||||
printf("fuse-binding: mount failed\n");
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NAN_INLINE static void bindings_set_date (Local<Date> date, struct timespec *out) {
|
||||
double ms = date->NumberValue();
|
||||
time_t secs = (time_t)(ms / 1000.0);
|
||||
time_t rem = ms - (1000.0 * secs);
|
||||
time_t ns = rem * 1000000.0;
|
||||
out->tv_sec = secs;
|
||||
out->tv_nsec = ns;
|
||||
}
|
||||
|
||||
NAN_INLINE static void bindings_set_stat (Local<Object> obj, struct stat *stat) {
|
||||
if (obj->Has(NanNew<String>("dev"))) stat->st_dev = obj->Get(NanNew<String>("dev"))->NumberValue();
|
||||
if (obj->Has(NanNew<String>("ino"))) stat->st_ino = obj->Get(NanNew<String>("ino"))->NumberValue();
|
||||
if (obj->Has(NanNew<String>("mode"))) stat->st_mode = obj->Get(NanNew<String>("mode"))->Uint32Value();
|
||||
if (obj->Has(NanNew<String>("nlink"))) stat->st_nlink = obj->Get(NanNew<String>("nlink"))->NumberValue();
|
||||
if (obj->Has(NanNew<String>("uid"))) stat->st_uid = obj->Get(NanNew<String>("uid"))->NumberValue();
|
||||
if (obj->Has(NanNew<String>("gid"))) stat->st_gid = obj->Get(NanNew<String>("gid"))->NumberValue();
|
||||
if (obj->Has(NanNew<String>("rdev"))) stat->st_rdev = obj->Get(NanNew<String>("rdev"))->NumberValue();
|
||||
if (obj->Has(NanNew<String>("size"))) stat->st_size = obj->Get(NanNew<String>("size"))->NumberValue();
|
||||
if (obj->Has(NanNew<String>("blksize"))) stat->st_blksize = obj->Get(NanNew<String>("blksize"))->NumberValue();
|
||||
if (obj->Has(NanNew<String>("blocks"))) stat->st_blocks = obj->Get(NanNew<String>("blocks"))->NumberValue();
|
||||
#ifdef __APPLE__
|
||||
if (obj->Has(NanNew<String>("mtime"))) bindings_set_date(obj->Get(NanNew("mtime")).As<Date>(), &stat->st_mtimespec);
|
||||
if (obj->Has(NanNew<String>("ctime"))) bindings_set_date(obj->Get(NanNew("ctime")).As<Date>(), &stat->st_ctimespec);
|
||||
if (obj->Has(NanNew<String>("atime"))) bindings_set_date(obj->Get(NanNew("atime")).As<Date>(), &stat->st_atimespec);
|
||||
#else
|
||||
if (obj->Has(NanNew<String>("mtime"))) bindings_set_date(obj->Get(NanNew("mtime")).As<Date>(), &stat->st_mtim);
|
||||
if (obj->Has(NanNew<String>("ctime"))) bindings_set_date(obj->Get(NanNew("ctime")).As<Date>(), &stat->st_ctim);
|
||||
if (obj->Has(NanNew<String>("atime"))) bindings_set_date(obj->Get(NanNew("atime")).As<Date>(), &stat->st_atim);
|
||||
#endif
|
||||
}
|
||||
|
||||
NAN_INLINE static void bindings_set_utimens (Local<Object> obj, struct timespec tv[2]) {
|
||||
if (obj->Has(NanNew<String>("atime"))) bindings_set_date(obj->Get(NanNew("atime")).As<Date>(), &tv[0]);
|
||||
if (obj->Has(NanNew<String>("mtime"))) bindings_set_date(obj->Get(NanNew("mtime")).As<Date>(), &tv[1]);
|
||||
}
|
||||
|
||||
NAN_INLINE static void bindings_set_statfs (Local<Object> obj, struct statvfs *statfs) { // from http://linux.die.net/man/2/stat
|
||||
if (obj->Has(NanNew<String>("bsize"))) statfs->f_bsize = obj->Get(NanNew<String>("bsize"))->Uint32Value();
|
||||
if (obj->Has(NanNew<String>("frsize"))) statfs->f_frsize = obj->Get(NanNew<String>("frsize"))->Uint32Value();
|
||||
if (obj->Has(NanNew<String>("blocks"))) statfs->f_blocks = obj->Get(NanNew<String>("blocks"))->Uint32Value();
|
||||
if (obj->Has(NanNew<String>("bfree"))) statfs->f_bfree = obj->Get(NanNew<String>("bfree"))->Uint32Value();
|
||||
if (obj->Has(NanNew<String>("bavail"))) statfs->f_bavail = obj->Get(NanNew<String>("bavail"))->Uint32Value();
|
||||
if (obj->Has(NanNew<String>("files"))) statfs->f_files = obj->Get(NanNew<String>("files"))->Uint32Value();
|
||||
if (obj->Has(NanNew<String>("ffree"))) statfs->f_ffree = obj->Get(NanNew<String>("ffree"))->Uint32Value();
|
||||
if (obj->Has(NanNew<String>("favail"))) statfs->f_favail = obj->Get(NanNew<String>("favail"))->Uint32Value();
|
||||
if (obj->Has(NanNew<String>("fsid"))) statfs->f_fsid = obj->Get(NanNew<String>("fsid"))->Uint32Value();
|
||||
if (obj->Has(NanNew<String>("flag"))) statfs->f_flag = obj->Get(NanNew<String>("flag"))->Uint32Value();
|
||||
if (obj->Has(NanNew<String>("namemax"))) statfs->f_namemax = obj->Get(NanNew<String>("namemax"))->Uint32Value();
|
||||
}
|
||||
|
||||
NAN_INLINE static void bindings_set_dirs (Local<Array> dirs) {
|
||||
for (uint32_t i = 0; i < dirs->Length(); i++) {
|
||||
NanUtf8String dir(dirs->Get(i));
|
||||
if (bindings.filler(bindings.data, *dir, &empty_stat, 0)) break;
|
||||
}
|
||||
}
|
||||
|
||||
NAN_INLINE static void bindings_set_fd (Local<Number> fd) {
|
||||
bindings.info->fh = fd->Uint32Value();
|
||||
}
|
||||
|
||||
NAN_METHOD(OpCallback) {
|
||||
NanScope();
|
||||
bindings.result = args[0]->Uint32Value();
|
||||
|
||||
if (!bindings.result) {
|
||||
switch (bindings.op) {
|
||||
case OP_STATFS: {
|
||||
if (args.Length() > 1 && args[1]->IsObject()) bindings_set_statfs(args[1].As<Object>(), (struct statvfs *) bindings.data);
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_GETATTR:
|
||||
case OP_FGETATTR: {
|
||||
if (args.Length() > 1 && args[1]->IsObject()) bindings_set_stat(args[1].As<Object>(), (struct stat *) bindings.data);
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_READDIR: {
|
||||
if (args.Length() > 1 && args[1]->IsArray()) bindings_set_dirs(args[1].As<Array>());
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_CREATE:
|
||||
case OP_OPEN:
|
||||
case OP_OPENDIR: {
|
||||
if (args.Length() > 1 && args[1]->IsNumber()) bindings_set_fd(args[1].As<Number>());
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_UTIMENS: {
|
||||
if (args.Length() > 1 && args[1]->IsObject()) bindings_set_utimens(args[1].As<Object>(), (struct timespec *) bindings.data);
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_INIT:
|
||||
case OP_ACCESS:
|
||||
case OP_FLUSH:
|
||||
case OP_FSYNC:
|
||||
case OP_FSYNCDIR:
|
||||
case OP_TRUNCATE:
|
||||
case OP_FTRUNCATE:
|
||||
case OP_READLINK:
|
||||
case OP_CHOWN:
|
||||
case OP_CHMOD:
|
||||
case OP_SETXATTR:
|
||||
case OP_GETXATTR:
|
||||
case OP_READ:
|
||||
case OP_WRITE:
|
||||
case OP_RELEASE:
|
||||
case OP_RELEASEDIR:
|
||||
case OP_UNLINK:
|
||||
case OP_RENAME:
|
||||
case OP_LINK:
|
||||
case OP_SYMLINK:
|
||||
case OP_MKDIR:
|
||||
case OP_RMDIR:
|
||||
case OP_DESTROY:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
semaphore_signal(&bindings.semaphore);
|
||||
NanReturnUndefined();
|
||||
}
|
||||
|
||||
NAN_INLINE static void bindings_call_op (NanCallback *fn, int argc, Local<Value> *argv) {
|
||||
if (fn == NULL) semaphore_signal(&bindings.semaphore);
|
||||
else fn->Call(argc, argv);
|
||||
}
|
||||
|
||||
static void bindings_dispatch (uv_async_t* handle, int status) {
|
||||
Local<Function> callback = bindings.callback->GetFunction();
|
||||
bindings.result = -1;
|
||||
|
||||
switch (bindings.op) {
|
||||
case OP_INIT: {
|
||||
Local<Value> tmp[] = {callback};
|
||||
bindings_call_op(bindings.ops_init, 1, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_STATFS: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), callback};
|
||||
bindings_call_op(bindings.ops_statfs, 2, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_FGETATTR: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), NanNew<Number>(bindings.info->fh), callback};
|
||||
bindings_call_op(bindings.ops_fgetattr, 3, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_GETATTR: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), callback};
|
||||
bindings_call_op(bindings.ops_getattr, 2, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_READDIR: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), callback};
|
||||
bindings_call_op(bindings.ops_readdir, 2, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_CREATE: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), NanNew<Number>(bindings.mode), callback};
|
||||
bindings_call_op(bindings.ops_create, 3, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_TRUNCATE: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), NanNew<Number>(bindings.offset), callback};
|
||||
bindings_call_op(bindings.ops_truncate, 3, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_FTRUNCATE: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), NanNew<Number>(bindings.info->fh), NanNew<Number>(bindings.offset), callback};
|
||||
bindings_call_op(bindings.ops_ftruncate, 4, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_ACCESS: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), NanNew<Number>(bindings.mode), callback};
|
||||
bindings_call_op(bindings.ops_access, 3, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_OPEN: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), NanNew<Number>(bindings.mode), callback};
|
||||
bindings_call_op(bindings.ops_open, 3, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_OPENDIR: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), NanNew<Number>(bindings.mode), callback};
|
||||
bindings_call_op(bindings.ops_opendir, 3, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_WRITE: {
|
||||
Local<Value> tmp[] = {
|
||||
NanNew<String>(bindings.path),
|
||||
NanNew<Number>(bindings.info->fh),
|
||||
bindings_buffer((char *) bindings.data, bindings.length),
|
||||
NanNew<Number>(bindings.length),
|
||||
NanNew<Number>(bindings.offset),
|
||||
callback
|
||||
};
|
||||
bindings_call_op(bindings.ops_write, 6, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_READ: {
|
||||
Local<Value> tmp[] = {
|
||||
NanNew<String>(bindings.path),
|
||||
NanNew<Number>(bindings.info->fh),
|
||||
bindings_buffer((char *) bindings.data, bindings.length),
|
||||
NanNew<Number>(bindings.length),
|
||||
NanNew<Number>(bindings.offset),
|
||||
callback
|
||||
};
|
||||
bindings_call_op(bindings.ops_read, 6, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_RELEASE: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), NanNew<Number>(bindings.info->fh), callback};
|
||||
bindings_call_op(bindings.ops_release, 3, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_RELEASEDIR: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), NanNew<Number>(bindings.info->fh), callback};
|
||||
bindings_call_op(bindings.ops_releasedir, 3, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_UNLINK: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), callback};
|
||||
bindings_call_op(bindings.ops_unlink, 2, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_RENAME: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), NanNew<String>((char *) bindings.data), callback};
|
||||
bindings_call_op(bindings.ops_rename, 3, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_LINK: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), NanNew<String>((char *) bindings.data), callback};
|
||||
bindings_call_op(bindings.ops_link, 3, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_SYMLINK: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), NanNew<String>((char *) bindings.data), callback};
|
||||
bindings_call_op(bindings.ops_symlink, 3, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_CHMOD: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), NanNew<Number>(bindings.mode), callback};
|
||||
bindings_call_op(bindings.ops_chmod, 3, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_CHOWN: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), NanNew<Number>(bindings.uid), NanNew<Number>(bindings.gid), callback};
|
||||
bindings_call_op(bindings.ops_chown, 4, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_READLINK: {
|
||||
Local<Value> tmp[] = {
|
||||
NanNew<String>(bindings.path),
|
||||
bindings_buffer((char *) bindings.data, bindings.length),
|
||||
NanNew<Number>(bindings.length),
|
||||
callback
|
||||
};
|
||||
bindings_call_op(bindings.ops_readlink, 4, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_SETXATTR: {
|
||||
Local<Value> tmp[] = {
|
||||
NanNew<String>(bindings.path),
|
||||
NanNew<String>(bindings.name),
|
||||
bindings_buffer((char *) bindings.data, bindings.length),
|
||||
NanNew<Number>(bindings.length),
|
||||
NanNew<Number>(bindings.offset),
|
||||
NanNew<Number>(bindings.mode),
|
||||
callback
|
||||
};
|
||||
bindings_call_op(bindings.ops_setxattr, 7, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_GETXATTR: {
|
||||
Local<Value> tmp[] = {
|
||||
NanNew<String>(bindings.path),
|
||||
NanNew<String>(bindings.name),
|
||||
bindings_buffer((char *) bindings.data, bindings.length),
|
||||
NanNew<Number>(bindings.length),
|
||||
NanNew<Number>(bindings.offset),
|
||||
callback
|
||||
};
|
||||
bindings_call_op(bindings.ops_getxattr, 6, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_MKDIR: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), NanNew<Number>(bindings.mode), callback};
|
||||
bindings_call_op(bindings.ops_mkdir, 3, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_RMDIR: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), callback};
|
||||
bindings_call_op(bindings.ops_rmdir, 2, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_DESTROY: {
|
||||
Local<Value> tmp[] = {callback};
|
||||
bindings_call_op(bindings.ops_destroy, 1, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_UTIMENS: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), callback};
|
||||
bindings_call_op(bindings.ops_utimens, 2, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_FLUSH: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), NanNew<Number>(bindings.info->fh), callback};
|
||||
bindings_call_op(bindings.ops_flush, 3, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_FSYNC: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), NanNew<Number>(bindings.info->fh), NanNew<Number>(bindings.mode), callback};
|
||||
bindings_call_op(bindings.ops_fsync, 4, tmp);
|
||||
}
|
||||
return;
|
||||
|
||||
case OP_FSYNCDIR: {
|
||||
Local<Value> tmp[] = {NanNew<String>(bindings.path), NanNew<Number>(bindings.info->fh), NanNew<Number>(bindings.mode), callback};
|
||||
bindings_call_op(bindings.ops_fsyncdir, 4, tmp);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
semaphore_signal(&bindings.semaphore);
|
||||
}
|
||||
|
||||
static void bindings_append_opt (char *name) {
|
||||
if (strcmp(bindings.mntopts, "-o")) strcat(bindings.mntopts, ",");
|
||||
strcat(bindings.mntopts, name);
|
||||
}
|
||||
|
||||
NAN_METHOD(Mount) {
|
||||
NanScope();
|
||||
|
||||
memset(&empty_stat, 0, sizeof(empty_stat)); // zero empty stat
|
||||
|
||||
NanUtf8String path(args[0]);
|
||||
Local<Object> ops = args[1].As<Object>();
|
||||
|
||||
bindings.ops_init = ops->Has(NanNew<String>("init")) ? new NanCallback(ops->Get(NanNew<String>("init")).As<Function>()) : NULL;
|
||||
bindings.ops_access = ops->Has(NanNew<String>("access")) ? new NanCallback(ops->Get(NanNew<String>("access")).As<Function>()) : NULL;
|
||||
bindings.ops_statfs = ops->Has(NanNew<String>("statfs")) ? new NanCallback(ops->Get(NanNew<String>("statfs")).As<Function>()) : NULL;
|
||||
bindings.ops_getattr = ops->Has(NanNew<String>("getattr")) ? new NanCallback(ops->Get(NanNew<String>("getattr")).As<Function>()) : NULL;
|
||||
bindings.ops_fgetattr = ops->Has(NanNew<String>("fgetattr")) ? new NanCallback(ops->Get(NanNew<String>("fgetattr")).As<Function>()) : NULL;
|
||||
bindings.ops_flush = ops->Has(NanNew<String>("flush")) ? new NanCallback(ops->Get(NanNew<String>("flush")).As<Function>()) : NULL;
|
||||
bindings.ops_fsync = ops->Has(NanNew<String>("fsync")) ? new NanCallback(ops->Get(NanNew<String>("fsync")).As<Function>()) : NULL;
|
||||
bindings.ops_fsyncdir = ops->Has(NanNew<String>("fsyncdir")) ? new NanCallback(ops->Get(NanNew<String>("fsyncdir")).As<Function>()) : NULL;
|
||||
bindings.ops_readdir = ops->Has(NanNew<String>("readdir")) ? new NanCallback(ops->Get(NanNew<String>("readdir")).As<Function>()) : NULL;
|
||||
bindings.ops_truncate = ops->Has(NanNew<String>("truncate")) ? new NanCallback(ops->Get(NanNew<String>("truncate")).As<Function>()) : NULL;
|
||||
bindings.ops_ftruncate = ops->Has(NanNew<String>("ftruncate")) ? new NanCallback(ops->Get(NanNew<String>("ftruncate")).As<Function>()) : NULL;
|
||||
bindings.ops_readlink = ops->Has(NanNew<String>("readlink")) ? new NanCallback(ops->Get(NanNew<String>("readlink")).As<Function>()) : NULL;
|
||||
bindings.ops_chown = ops->Has(NanNew<String>("chown")) ? new NanCallback(ops->Get(NanNew<String>("chown")).As<Function>()) : NULL;
|
||||
bindings.ops_chmod = ops->Has(NanNew<String>("chmod")) ? new NanCallback(ops->Get(NanNew<String>("chmod")).As<Function>()) : NULL;
|
||||
bindings.ops_setxattr = ops->Has(NanNew<String>("setxattr")) ? new NanCallback(ops->Get(NanNew<String>("setxattr")).As<Function>()) : NULL;
|
||||
bindings.ops_getxattr = ops->Has(NanNew<String>("getxattr")) ? new NanCallback(ops->Get(NanNew<String>("getxattr")).As<Function>()) : NULL;
|
||||
bindings.ops_open = ops->Has(NanNew<String>("open")) ? new NanCallback(ops->Get(NanNew<String>("open")).As<Function>()) : NULL;
|
||||
bindings.ops_opendir = ops->Has(NanNew<String>("opendir")) ? new NanCallback(ops->Get(NanNew<String>("opendir")).As<Function>()) : NULL;
|
||||
bindings.ops_read = ops->Has(NanNew<String>("read")) ? new NanCallback(ops->Get(NanNew<String>("read")).As<Function>()) : NULL;
|
||||
bindings.ops_write = ops->Has(NanNew<String>("write")) ? new NanCallback(ops->Get(NanNew<String>("write")).As<Function>()) : NULL;
|
||||
bindings.ops_release = ops->Has(NanNew<String>("release")) ? new NanCallback(ops->Get(NanNew<String>("release")).As<Function>()) : NULL;
|
||||
bindings.ops_releasedir = ops->Has(NanNew<String>("releasedir")) ? new NanCallback(ops->Get(NanNew<String>("releasedir")).As<Function>()) : NULL;
|
||||
bindings.ops_create = ops->Has(NanNew<String>("create")) ? new NanCallback(ops->Get(NanNew<String>("create")).As<Function>()) : NULL;
|
||||
bindings.ops_utimens = ops->Has(NanNew<String>("utimens")) ? new NanCallback(ops->Get(NanNew<String>("utimens")).As<Function>()) : NULL;
|
||||
bindings.ops_unlink = ops->Has(NanNew<String>("unlink")) ? new NanCallback(ops->Get(NanNew<String>("unlink")).As<Function>()) : NULL;
|
||||
bindings.ops_rename = ops->Has(NanNew<String>("rename")) ? new NanCallback(ops->Get(NanNew<String>("rename")).As<Function>()) : NULL;
|
||||
bindings.ops_link = ops->Has(NanNew<String>("link")) ? new NanCallback(ops->Get(NanNew<String>("link")).As<Function>()) : NULL;
|
||||
bindings.ops_symlink = ops->Has(NanNew<String>("symlink")) ? new NanCallback(ops->Get(NanNew<String>("symlink")).As<Function>()) : NULL;
|
||||
bindings.ops_mkdir = ops->Has(NanNew<String>("mkdir")) ? new NanCallback(ops->Get(NanNew<String>("mkdir")).As<Function>()) : NULL;
|
||||
bindings.ops_rmdir = ops->Has(NanNew<String>("rmdir")) ? new NanCallback(ops->Get(NanNew<String>("rmdir")).As<Function>()) : NULL;
|
||||
bindings.ops_destroy = ops->Has(NanNew<String>("destroy")) ? new NanCallback(ops->Get(NanNew<String>("destroy")).As<Function>()) : NULL;
|
||||
|
||||
bindings.callback = new NanCallback(NanNew<FunctionTemplate>(OpCallback)->GetFunction());
|
||||
stpcpy(bindings.mnt, *path);
|
||||
stpcpy(bindings.mntopts, "-o");
|
||||
|
||||
Local<Array> options = ops->Get(NanNew<String>("options")).As<Array>();
|
||||
if (options->IsArray()) {
|
||||
for (uint32_t i = 0; i < options->Length(); i++) {
|
||||
NanUtf8String option(options->Get(i));
|
||||
bindings_append_opt(*option);
|
||||
}
|
||||
}
|
||||
|
||||
// yolo
|
||||
semaphore_init(&bindings.semaphore);
|
||||
uv_async_init(uv_default_loop(), &bindings.async, (uv_async_cb) bindings_dispatch);
|
||||
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_create(&bindings.thread, &attr, bindings_thread, NULL);
|
||||
|
||||
NanReturnUndefined();
|
||||
}
|
||||
|
||||
NAN_METHOD(UnmountSync) {
|
||||
NanScope();
|
||||
NanUtf8String path(args[0]);
|
||||
bindings_unmount(*path);
|
||||
NanReturnUndefined();
|
||||
}
|
||||
|
||||
class UnmountWorker : public NanAsyncWorker {
|
||||
public:
|
||||
UnmountWorker(NanCallback *callback, char *path)
|
||||
: NanAsyncWorker(callback), path(path) {}
|
||||
~UnmountWorker() {}
|
||||
|
||||
void Execute () {
|
||||
bindings_unmount(path);
|
||||
free(path);
|
||||
}
|
||||
|
||||
void HandleOKCallback () {
|
||||
NanScope();
|
||||
callback->Call(0, NULL);
|
||||
}
|
||||
|
||||
private:
|
||||
char *path;
|
||||
};
|
||||
|
||||
NAN_METHOD(SetBuffer) {
|
||||
NanScope();
|
||||
NanAssignPersistent(buffer_constructor, args[0].As<Function>());
|
||||
NanReturnUndefined();
|
||||
}
|
||||
|
||||
NAN_METHOD(Unmount) {
|
||||
NanScope();
|
||||
NanUtf8String path(args[0]);
|
||||
Local<Function> callback = args[1].As<Function>();
|
||||
|
||||
char *path_alloc = (char *) malloc(1024);
|
||||
stpcpy(path_alloc, *path);
|
||||
|
||||
NanAsyncQueueWorker(new UnmountWorker(new NanCallback(callback), path_alloc));
|
||||
NanReturnUndefined();
|
||||
}
|
||||
|
||||
void Init(Handle<Object> exports) {
|
||||
exports->Set(NanNew("setBuffer"), NanNew<FunctionTemplate>(SetBuffer)->GetFunction());
|
||||
exports->Set(NanNew("mount"), NanNew<FunctionTemplate>(Mount)->GetFunction());
|
||||
exports->Set(NanNew("unmount"), NanNew<FunctionTemplate>(Unmount)->GetFunction());
|
||||
exports->Set(NanNew("unmountSync"), NanNew<FunctionTemplate>(UnmountSync)->GetFunction());
|
||||
}
|
||||
|
||||
NODE_MODULE(fuse_bindings, Init)
|
989
fuse-native.c
Normal file
989
fuse-native.c
Normal file
@ -0,0 +1,989 @@
|
||||
#define FUSE_USE_VERSION 29
|
||||
|
||||
#include <uv.h>
|
||||
#include <node_api.h>
|
||||
#include <napi-macros.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <fuse.h>
|
||||
#include <fuse_opt.h>
|
||||
#include <fuse_common.h>
|
||||
#include <fuse_lowlevel.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <pthread.h>
|
||||
|
||||
static int IS_ARRAY_BUFFER_DETACH_SUPPORTED = 0;
|
||||
|
||||
napi_status napi_detach_arraybuffer(napi_env env, napi_value buf);
|
||||
|
||||
#define FUSE_NATIVE_CALLBACK(fn, blk)\
|
||||
napi_env env = ft->env;\
|
||||
napi_handle_scope scope;\
|
||||
napi_open_handle_scope(env, &scope);\
|
||||
napi_value ctx;\
|
||||
napi_get_reference_value(env, ft->ctx, &ctx);\
|
||||
napi_value callback;\
|
||||
napi_get_reference_value(env, fn, &callback);\
|
||||
blk\
|
||||
napi_close_handle_scope(env, scope);
|
||||
|
||||
#define FUSE_NATIVE_HANDLER(name, blk)\
|
||||
fuse_thread_locals_t *l = get_thread_locals();\
|
||||
l->op = op_##name;\
|
||||
l->op_fn = fuse_native_dispatch_##name;\
|
||||
blk\
|
||||
uv_async_send(&(l->async));\
|
||||
uv_sem_wait(&(l->sem));\
|
||||
return l->res;
|
||||
|
||||
#define FUSE_METHOD(name, callbackArgs, signalArgs, signature, callBlk, callbackBlk, signalBlk)\
|
||||
static void fuse_native_dispatch_##name (uv_async_t* handle, fuse_thread_locals_t* l, fuse_thread_t* ft) {\
|
||||
uint32_t op = op_##name;\
|
||||
FUSE_NATIVE_CALLBACK(ft->handlers[op], {\
|
||||
napi_value argv[callbackArgs + 2];\
|
||||
napi_get_reference_value(env, l->self, &(argv[0]));\
|
||||
napi_create_uint32(env, l->op, &(argv[1]));\
|
||||
callbackBlk\
|
||||
NAPI_MAKE_CALLBACK(env, NULL, ctx, callback, callbackArgs + 2, argv, NULL)\
|
||||
})\
|
||||
}\
|
||||
NAPI_METHOD(fuse_native_signal_##name) {\
|
||||
NAPI_ARGV(signalArgs + 2)\
|
||||
NAPI_ARGV_BUFFER_CAST(fuse_thread_locals_t *, l, 0);\
|
||||
NAPI_ARGV_INT32(res, 1);\
|
||||
signalBlk\
|
||||
l->res = res;\
|
||||
uv_sem_post(&(l->sem));\
|
||||
return NULL;\
|
||||
}\
|
||||
static int fuse_native_##name signature {\
|
||||
FUSE_NATIVE_HANDLER(name, callBlk)\
|
||||
}
|
||||
|
||||
#define FUSE_METHOD_VOID(name, callbackArgs, signalArgs, signature, callBlk, callbackBlk)\
|
||||
FUSE_METHOD(name, callbackArgs, signalArgs, signature, callBlk, callbackBlk, {})
|
||||
|
||||
#define FUSE_UINT64_TO_INTS_ARGV(n, pos)\
|
||||
uint32_t low##pos = n % 4294967296;\
|
||||
uint32_t high##pos = (n - low##pos) / 4294967296;\
|
||||
napi_create_uint32(env, low##pos, &(argv[pos]));\
|
||||
napi_create_uint32(env, high##pos, &(argv[pos + 1]));
|
||||
|
||||
|
||||
// Opcodes
|
||||
|
||||
static const uint32_t op_init = 0;
|
||||
static const uint32_t op_error = 1;
|
||||
static const uint32_t op_access = 2;
|
||||
static const uint32_t op_statfs = 3;
|
||||
static const uint32_t op_fgetattr = 4;
|
||||
static const uint32_t op_getattr = 5;
|
||||
static const uint32_t op_flush = 6;
|
||||
static const uint32_t op_fsync = 7;
|
||||
static const uint32_t op_fsyncdir = 8;
|
||||
static const uint32_t op_readdir = 9;
|
||||
static const uint32_t op_truncate = 10;
|
||||
static const uint32_t op_ftruncate = 11;
|
||||
static const uint32_t op_utimens = 12;
|
||||
static const uint32_t op_readlink = 13;
|
||||
static const uint32_t op_chown = 14;
|
||||
static const uint32_t op_chmod = 15;
|
||||
static const uint32_t op_mknod = 16;
|
||||
static const uint32_t op_setxattr = 17;
|
||||
static const uint32_t op_getxattr = 18;
|
||||
static const uint32_t op_listxattr = 19;
|
||||
static const uint32_t op_removexattr = 20;
|
||||
static const uint32_t op_open = 21;
|
||||
static const uint32_t op_opendir = 22;
|
||||
static const uint32_t op_read = 23;
|
||||
static const uint32_t op_write = 24;
|
||||
static const uint32_t op_release = 25;
|
||||
static const uint32_t op_releasedir = 26;
|
||||
static const uint32_t op_create = 27;
|
||||
static const uint32_t op_unlink = 28;
|
||||
static const uint32_t op_rename = 29;
|
||||
static const uint32_t op_link = 30;
|
||||
static const uint32_t op_symlink = 31;
|
||||
static const uint32_t op_mkdir = 32;
|
||||
static const uint32_t op_rmdir = 33;
|
||||
|
||||
// Data structures
|
||||
|
||||
typedef struct {
|
||||
napi_env env;
|
||||
pthread_t thread;
|
||||
pthread_attr_t attr;
|
||||
napi_ref ctx;
|
||||
napi_ref malloc;
|
||||
|
||||
// Operation handlers
|
||||
napi_ref handlers[35];
|
||||
|
||||
struct fuse *fuse;
|
||||
struct fuse_chan *ch;
|
||||
char mnt[1024];
|
||||
char mntopts[1024];
|
||||
int mounted;
|
||||
|
||||
uv_async_t async;
|
||||
uv_mutex_t mut;
|
||||
uv_sem_t sem;
|
||||
} fuse_thread_t;
|
||||
|
||||
typedef struct {
|
||||
napi_ref self;
|
||||
|
||||
// Opcode
|
||||
uint32_t op;
|
||||
void *op_fn;
|
||||
|
||||
// Payloads
|
||||
const char *path;
|
||||
const char *dest;
|
||||
char *linkname;
|
||||
struct fuse_file_info *info;
|
||||
const void *buf;
|
||||
off_t offset;
|
||||
size_t len;
|
||||
mode_t mode;
|
||||
dev_t dev;
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
size_t atime;
|
||||
size_t mtime;
|
||||
int32_t res;
|
||||
|
||||
// Extended attributes
|
||||
const char *name;
|
||||
const char *value;
|
||||
char *list;
|
||||
size_t size;
|
||||
uint32_t position;
|
||||
int flags;
|
||||
|
||||
// Stat + Statfs
|
||||
struct stat *stat;
|
||||
struct statvfs *statvfs;
|
||||
|
||||
// Readdir
|
||||
fuse_fill_dir_t readdir_filler;
|
||||
|
||||
// Internal bookkeeping
|
||||
fuse_thread_t *fuse;
|
||||
uv_sem_t sem;
|
||||
uv_async_t async;
|
||||
|
||||
} fuse_thread_locals_t;
|
||||
|
||||
static pthread_key_t thread_locals_key;
|
||||
static fuse_thread_locals_t* get_thread_locals();
|
||||
|
||||
// Helpers
|
||||
// TODO: Extract into a separate file.
|
||||
|
||||
static uint64_t uint32s_to_uint64 (uint32_t **ints) {
|
||||
uint64_t low = *((*ints)++);
|
||||
uint64_t high = *((*ints)++);
|
||||
return high * 4294967296 + low;
|
||||
}
|
||||
|
||||
static void uint32s_to_timespec (struct timespec* ts, uint32_t** ints) {
|
||||
uint64_t ms = uint32s_to_uint64(ints);
|
||||
ts->tv_sec = ms / 1000;
|
||||
ts->tv_nsec = (ms % 1000) * 1000000;
|
||||
}
|
||||
|
||||
static uint64_t timespec_to_uint64 (const struct timespec* ts) {
|
||||
uint64_t ms = (ts->tv_sec * 1000) + (ts->tv_nsec / 1000000);
|
||||
return ms;
|
||||
}
|
||||
|
||||
static void populate_stat (uint32_t *ints, struct stat* stat) {
|
||||
stat->st_mode = *ints++;
|
||||
stat->st_uid = *ints++;
|
||||
stat->st_gid = *ints++;
|
||||
stat->st_size = uint32s_to_uint64(&ints);
|
||||
stat->st_dev = *ints++;
|
||||
stat->st_nlink = *ints++;
|
||||
stat->st_ino = *ints++;
|
||||
stat->st_rdev = *ints++;
|
||||
stat->st_blksize = *ints++;
|
||||
stat->st_blocks = uint32s_to_uint64(&ints);
|
||||
#ifdef __APPLE__
|
||||
uint32s_to_timespec(&stat->st_atimespec, &ints);
|
||||
uint32s_to_timespec(&stat->st_mtimespec, &ints);
|
||||
uint32s_to_timespec(&stat->st_ctimespec, &ints);
|
||||
#else
|
||||
uint32s_to_timespec(&stat->st_atim, &ints);
|
||||
uint32s_to_timespec(&stat->st_mtim, &ints);
|
||||
uint32s_to_timespec(&stat->st_ctim, &ints);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void populate_statvfs (uint32_t *ints, struct statvfs* statvfs) {
|
||||
statvfs->f_bsize = *ints++;
|
||||
statvfs->f_frsize = *ints++;
|
||||
statvfs->f_blocks = *ints++;
|
||||
statvfs->f_bfree = *ints++;
|
||||
statvfs->f_bavail = *ints++;
|
||||
statvfs->f_files = *ints++;
|
||||
statvfs->f_ffree = *ints++;
|
||||
statvfs->f_favail = *ints++;
|
||||
statvfs->f_fsid = *ints++;
|
||||
statvfs->f_flag = *ints++;
|
||||
statvfs->f_namemax = *ints++;
|
||||
}
|
||||
|
||||
// Methods
|
||||
|
||||
FUSE_METHOD(statfs, 1, 1, (const char * path, struct statvfs *statvfs), {
|
||||
l->path = path;
|
||||
l->statvfs = statvfs;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
}, {
|
||||
NAPI_ARGV_BUFFER_CAST(uint32_t*, ints, 2)
|
||||
populate_statvfs(ints, l->statvfs);
|
||||
})
|
||||
|
||||
FUSE_METHOD(getattr, 1, 1, (const char *path, struct stat *stat), {
|
||||
l->path = path;
|
||||
l->stat = stat;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
}, {
|
||||
NAPI_ARGV_BUFFER_CAST(uint32_t*, ints, 2)
|
||||
populate_stat(ints, l->stat);
|
||||
})
|
||||
|
||||
FUSE_METHOD(fgetattr, 2, 1, (const char *path, struct stat *stat, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->stat = stat;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
if (l->info != NULL) {
|
||||
napi_create_uint32(env, l->info->fh, &(argv[3]));
|
||||
} else {
|
||||
napi_create_uint32(env, 0, &(argv[3]));
|
||||
}
|
||||
}, {
|
||||
NAPI_ARGV_BUFFER_CAST(uint32_t*, ints, 2)
|
||||
populate_stat(ints, l->stat);
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(access, 2, 0, (const char *path, int mode), {
|
||||
l->path = path;
|
||||
l->mode = mode;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_uint32(env, l->mode, &(argv[3]));
|
||||
})
|
||||
|
||||
FUSE_METHOD(open, 2, 1, (const char *path, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
if (l->info != NULL) {
|
||||
napi_create_uint32(env, l->info->flags, &(argv[3]));
|
||||
} else {
|
||||
napi_create_uint32(env, 0, &(argv[3]));
|
||||
}
|
||||
}, {
|
||||
NAPI_ARGV_INT32(fd, 2)
|
||||
if (fd != 0) {
|
||||
l->info->fh = fd;
|
||||
}
|
||||
})
|
||||
|
||||
FUSE_METHOD(opendir, 3, 1, (const char *path, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
if (l->info != NULL) {
|
||||
napi_create_uint32(env, l->info->fh, &(argv[3]));
|
||||
napi_create_uint32(env, l->info->flags, &(argv[4]));
|
||||
} else {
|
||||
napi_create_uint32(env, 0, &(argv[3]));
|
||||
napi_create_uint32(env, 0, &(argv[4]));
|
||||
}
|
||||
}, {
|
||||
NAPI_ARGV_INT32(fd, 2)
|
||||
if (fd != 0) {
|
||||
l->info->fh = fd;
|
||||
}
|
||||
})
|
||||
|
||||
FUSE_METHOD(create, 2, 1, (const char *path, mode_t mode, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->mode = mode;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_uint32(env, l->mode, &(argv[3]));
|
||||
}, {
|
||||
NAPI_ARGV_INT32(fd, 2)
|
||||
if (fd != 0) {
|
||||
l->info->fh = fd;
|
||||
}
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(utimens, 5, 0, (const char *path, const struct timespec tv[2]), {
|
||||
l->path = path;
|
||||
l->atime = timespec_to_uint64(&tv[0]);
|
||||
l->mtime = timespec_to_uint64(&tv[1]);
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
FUSE_UINT64_TO_INTS_ARGV(l->atime, 3)
|
||||
FUSE_UINT64_TO_INTS_ARGV(l->atime, 5)
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(release, 2, 0, (const char *path, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
if (l->info != NULL) {
|
||||
napi_create_uint32(env, l->info->fh, &(argv[3]));
|
||||
} else {
|
||||
napi_create_uint32(env, 0, &(argv[3]));
|
||||
}
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(releasedir, 2, 0, (const char *path, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
if (l->info != NULL) {
|
||||
napi_create_uint32(env, l->info->fh, &(argv[3]));
|
||||
} else {
|
||||
napi_create_uint32(env, 0, &(argv[3]));
|
||||
}
|
||||
})
|
||||
|
||||
FUSE_METHOD(read, 6, 2, (const char *path, char *buf, size_t len, off_t offset, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->buf = buf;
|
||||
l->len = len;
|
||||
l->offset = offset;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_uint32(env, l->info->fh, &(argv[3]));
|
||||
napi_create_external_buffer(env, l->len, (char *) l->buf, NULL, NULL, &(argv[4]));
|
||||
napi_create_uint32(env, l->len, &(argv[5]));
|
||||
FUSE_UINT64_TO_INTS_ARGV(l->offset, 6)
|
||||
}, {
|
||||
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[3]) == napi_ok);
|
||||
})
|
||||
|
||||
FUSE_METHOD(write, 6, 2, (const char *path, const char *buf, size_t len, off_t offset, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->buf = buf;
|
||||
l->len = len;
|
||||
l->offset = offset;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_uint32(env, l->info->fh, &(argv[3]));
|
||||
napi_create_external_buffer(env, l->len, (char *) l->buf, NULL, NULL, &(argv[4]));
|
||||
napi_create_uint32(env, l->len, &(argv[5]));
|
||||
FUSE_UINT64_TO_INTS_ARGV(l->offset, 6)
|
||||
}, {
|
||||
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[3]) == napi_ok);
|
||||
})
|
||||
|
||||
FUSE_METHOD(readdir, 1, 2, (const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *info), {
|
||||
l->buf = buf;
|
||||
l->path = path;
|
||||
l->offset = offset;
|
||||
l->info = info;
|
||||
l->readdir_filler = filler;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
}, {
|
||||
uint32_t stats_length;
|
||||
uint32_t names_length;
|
||||
napi_get_array_length(env, argv[3], &stats_length);
|
||||
napi_get_array_length(env, argv[2], &names_length);
|
||||
|
||||
napi_value raw_names = argv[2];
|
||||
napi_value raw_stats = argv[3];
|
||||
|
||||
if (names_length != stats_length) {
|
||||
NAPI_FOR_EACH(raw_names, raw_name) {
|
||||
NAPI_UTF8(name, 1024, raw_name)
|
||||
int err = l->readdir_filler((char *) l->buf, name, NULL, 0);
|
||||
if (err == 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NAPI_FOR_EACH(raw_names, raw_name) {
|
||||
NAPI_UTF8(name, 1024, raw_name)
|
||||
napi_value raw_stat;
|
||||
napi_get_element(env, raw_stats, i, &raw_stat);
|
||||
|
||||
NAPI_BUFFER_CAST(uint32_t*, stats_array, raw_stat);
|
||||
struct stat st;
|
||||
populate_stat(stats_array, &st);
|
||||
|
||||
// TODO: It turns out readdirplus likely won't work with FUSE 29...
|
||||
// Metadata caching between readdir/getattr will be enabled when we upgrade fuse-shared-library
|
||||
int err = l->readdir_filler((char *) l->buf, name, (struct stat *) &st, 0);
|
||||
if (err == 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
FUSE_METHOD(setxattr, 5, 1, (const char *path, const char *name, const char *value, size_t size, int flags, uint32_t position), {
|
||||
l->path = path;
|
||||
l->name = name;
|
||||
l->value = value;
|
||||
l->size = size;
|
||||
l->flags = flags;
|
||||
l->position = position;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_string_utf8(env, l->name, NAPI_AUTO_LENGTH, &(argv[3]));
|
||||
napi_create_external_buffer(env, l->size, (char *) l->value, NULL, NULL, &(argv[4]));
|
||||
napi_create_uint32(env, l->position, &(argv[5]));
|
||||
napi_create_uint32(env, l->flags, &(argv[6]));
|
||||
}, {
|
||||
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[2]) == napi_ok);
|
||||
})
|
||||
|
||||
FUSE_METHOD(getxattr, 4, 1, (const char *path, const char *name, char *value, size_t size, uint32_t position), {
|
||||
l->path = path;
|
||||
l->name = name;
|
||||
l->value = value;
|
||||
l->size = size;
|
||||
l->position = position;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_string_utf8(env, l->name, NAPI_AUTO_LENGTH, &(argv[3]));
|
||||
napi_create_external_buffer(env, l->size, (char *) l->value, NULL, NULL, &(argv[4]));
|
||||
napi_create_uint32(env, l->position, &(argv[5]));
|
||||
}, {
|
||||
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[2]) == napi_ok);
|
||||
})
|
||||
|
||||
#else
|
||||
|
||||
FUSE_METHOD(setxattr, 5, 1, (const char *path, const char *name, const char *value, size_t size, int flags), {
|
||||
l->path = path;
|
||||
l->name = name;
|
||||
l->value = value;
|
||||
l->size = size;
|
||||
l->flags = flags;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_string_utf8(env, l->name, NAPI_AUTO_LENGTH, &(argv[3]));
|
||||
napi_create_external_buffer(env, l->size, (char *) l->value, NULL, NULL, &(argv[4]));
|
||||
napi_create_uint32(env, 0, &(argv[5])); // normalize apis between mac and linux
|
||||
napi_create_uint32(env, l->flags, &(argv[6]));
|
||||
}, {
|
||||
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[2]) == napi_ok);
|
||||
})
|
||||
|
||||
FUSE_METHOD(getxattr, 4, 1, (const char *path, const char *name, char *value, size_t size), {
|
||||
l->path = path;
|
||||
l->name = name;
|
||||
l->value = value;
|
||||
l->size = size;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_string_utf8(env, l->name, NAPI_AUTO_LENGTH, &(argv[3]));
|
||||
napi_create_external_buffer(env, l->size, (char *) l->value, NULL, NULL, &(argv[4]));
|
||||
napi_create_uint32(env, 0, &(argv[5]));
|
||||
}, {
|
||||
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[2]) == napi_ok);
|
||||
})
|
||||
|
||||
#endif
|
||||
|
||||
FUSE_METHOD(listxattr, 2, 1, (const char *path, char *list, size_t size), {
|
||||
l->path = path;
|
||||
l->list = list;
|
||||
l->size = size;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_external_buffer(env, l->size, l->list, NULL, NULL, &(argv[3]));
|
||||
}, {
|
||||
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[2]) == napi_ok);
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(removexattr, 2, 0, (const char *path, const char *name), {
|
||||
l->path = path;
|
||||
l->name = name;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_string_utf8(env, l->name, NAPI_AUTO_LENGTH, &(argv[3]));
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(flush, 2, 0, (const char *path, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
if (l->info != NULL) {
|
||||
napi_create_uint32(env, l->info->fh, &(argv[3]));
|
||||
} else {
|
||||
napi_create_uint32(env, 0, &(argv[3]));
|
||||
}
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(fsync, 3, 0, (const char *path, int datasync, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->mode = datasync;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_uint32(env, l->mode, &(argv[3]));
|
||||
if (l->info != NULL) {
|
||||
napi_create_uint32(env, l->info->fh, &(argv[4]));
|
||||
} else {
|
||||
napi_create_uint32(env, 0, &(argv[4]));
|
||||
}
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(fsyncdir, 3, 0, (const char *path, int datasync, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->mode = datasync;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_uint32(env, l->mode, &(argv[3]));
|
||||
if (l->info != NULL) {
|
||||
napi_create_uint32(env, l->info->fh, &(argv[4]));
|
||||
} else {
|
||||
napi_create_uint32(env, 0, &(argv[4]));
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
FUSE_METHOD_VOID(truncate, 3, 0, (const char *path, off_t size), {
|
||||
l->path = path;
|
||||
l->offset = size;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
FUSE_UINT64_TO_INTS_ARGV(l->offset, 3)
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(ftruncate, 4, 0, (const char *path, off_t size, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->offset = size;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
if (l->info != NULL) {
|
||||
napi_create_uint32(env, l->info->fh, &(argv[3]));
|
||||
} else {
|
||||
napi_create_uint32(env, 0, &(argv[3]));
|
||||
}
|
||||
FUSE_UINT64_TO_INTS_ARGV(l->offset, 4)
|
||||
})
|
||||
|
||||
FUSE_METHOD(readlink, 1, 1, (const char *path, char *linkname, size_t len), {
|
||||
l->path = path;
|
||||
l->linkname = linkname;
|
||||
l->len = len;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
}, {
|
||||
NAPI_ARGV_UTF8(linkname, l->len, 2)
|
||||
strncpy(l->linkname, linkname, l->len);
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(chown, 3, 0, (const char *path, uid_t uid, gid_t gid), {
|
||||
l->path = path;
|
||||
l->uid = uid;
|
||||
l->gid = gid;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_uint32(env, l->uid, &(argv[3]));
|
||||
napi_create_uint32(env, l->gid, &(argv[4]));
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(chmod, 2, 0, (const char *path, mode_t mode), {
|
||||
l->path = path;
|
||||
l->mode = mode;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_uint32(env, l->mode, &(argv[3]));
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(mknod, 3, 0, (const char *path, mode_t mode, dev_t dev), {
|
||||
l->path = path;
|
||||
l->mode = mode;
|
||||
l->dev = dev;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_uint32(env, l->mode, &(argv[3]));
|
||||
napi_create_uint32(env, l->dev, &(argv[4]));
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(unlink, 1, 0, (const char *path), {
|
||||
l->path = path;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(rename, 2, 0, (const char *path, const char *dest), {
|
||||
l->path = path;
|
||||
l->dest = dest;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_string_utf8(env, l->dest, NAPI_AUTO_LENGTH, &(argv[3]));
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(link, 2, 0, (const char *path, const char *dest), {
|
||||
l->path = path;
|
||||
l->dest = dest;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_string_utf8(env, l->dest, NAPI_AUTO_LENGTH, &(argv[3]));
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(symlink, 2, 0, (const char *path, const char *dest), {
|
||||
l->path = path;
|
||||
l->dest = dest;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_string_utf8(env, l->dest, NAPI_AUTO_LENGTH, &(argv[3]));
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(mkdir, 2, 0, (const char *path, mode_t mode), {
|
||||
l->path = path;
|
||||
l->mode = mode;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_uint32(env, l->mode, &(argv[3]));
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(rmdir, 1, 0, (const char *path), {
|
||||
l->path = path;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
})
|
||||
|
||||
static void fuse_native_dispatch_init (uv_async_t* handle, fuse_thread_locals_t* l, fuse_thread_t* ft) {\
|
||||
FUSE_NATIVE_CALLBACK(ft->handlers[op_init], {
|
||||
napi_value argv[2];
|
||||
|
||||
napi_get_reference_value(env, l->self, &(argv[0]));
|
||||
napi_create_uint32(env, l->op, &(argv[1]));
|
||||
|
||||
NAPI_MAKE_CALLBACK(env, NULL, ctx, callback, 2, argv, NULL);
|
||||
})
|
||||
}
|
||||
|
||||
NAPI_METHOD(fuse_native_signal_init) {
|
||||
NAPI_ARGV(2)
|
||||
NAPI_ARGV_BUFFER_CAST(fuse_thread_locals_t *, l, 0);
|
||||
NAPI_ARGV_INT32(res, 1);
|
||||
l->res = res;
|
||||
uv_sem_post(&(l->sem));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void * fuse_native_init (struct fuse_conn_info *conn) {
|
||||
fuse_thread_locals_t *l = get_thread_locals();
|
||||
|
||||
l->op = op_init;
|
||||
l->op_fn = fuse_native_dispatch_init;
|
||||
|
||||
uv_async_send(&(l->async));
|
||||
uv_sem_wait(&(l->sem));
|
||||
|
||||
return l->fuse;
|
||||
}
|
||||
|
||||
// Top-level dispatcher
|
||||
|
||||
static void fuse_native_dispatch (uv_async_t* handle) {
|
||||
fuse_thread_locals_t *l = (fuse_thread_locals_t *) handle->data;
|
||||
fuse_thread_t *ft = l->fuse;
|
||||
void (*fn)(uv_async_t *, fuse_thread_locals_t *, fuse_thread_t *) = l->op_fn;
|
||||
|
||||
fn(handle, l, ft);
|
||||
}
|
||||
|
||||
static void fuse_native_async_init (uv_async_t* handle) {
|
||||
fuse_thread_t *ft = (fuse_thread_t *) handle->data;
|
||||
fuse_thread_locals_t *l;
|
||||
|
||||
FUSE_NATIVE_CALLBACK(ft->malloc, {
|
||||
napi_value argv[1];
|
||||
napi_create_uint32(ft->env, (uint32_t) sizeof(fuse_thread_locals_t), &(argv[0]));
|
||||
|
||||
napi_value buf;
|
||||
NAPI_MAKE_CALLBACK(ft->env, NULL, ctx, callback, 1, argv, &buf);
|
||||
|
||||
size_t l_len;
|
||||
|
||||
napi_get_buffer_info(env, buf, (void **) &l, &l_len);
|
||||
napi_create_reference(env, buf, 1, &(l->self));
|
||||
})
|
||||
|
||||
|
||||
int err = uv_async_init(uv_default_loop(), &(l->async), (uv_async_cb) fuse_native_dispatch);
|
||||
assert(err >= 0);
|
||||
|
||||
uv_unref((uv_handle_t *) &(l->async));
|
||||
|
||||
uv_sem_init(&(l->sem), 0);
|
||||
l->async.data = l;
|
||||
ft->async.data = l;
|
||||
l->fuse = ft;
|
||||
|
||||
uv_sem_post(&(ft->sem));
|
||||
}
|
||||
|
||||
static fuse_thread_locals_t* get_thread_locals () {
|
||||
struct fuse_context *ctx = fuse_get_context();
|
||||
fuse_thread_t *ft = (fuse_thread_t *) ctx->private_data;
|
||||
|
||||
void *data = pthread_getspecific(thread_locals_key);
|
||||
|
||||
if (data != NULL) {
|
||||
return (fuse_thread_locals_t *) data;
|
||||
}
|
||||
|
||||
// Need to lock the mutation of l->async.
|
||||
uv_mutex_lock(&(ft->mut));
|
||||
ft->async.data = ft;
|
||||
|
||||
// Notify the main thread to uv_async_init l->async.
|
||||
uv_async_send(&(ft->async));
|
||||
uv_sem_wait(&(ft->sem));
|
||||
|
||||
fuse_thread_locals_t *l = (fuse_thread_locals_t*) ft->async.data;
|
||||
|
||||
pthread_setspecific(thread_locals_key, (void *) l);
|
||||
uv_mutex_unlock(&(ft->mut));
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
static void* start_fuse_thread (void *data) {
|
||||
fuse_thread_t *ft = (fuse_thread_t *) data;
|
||||
fuse_loop_mt(ft->fuse);
|
||||
|
||||
fuse_unmount(ft->mnt, ft->ch);
|
||||
fuse_session_remove_chan(ft->ch);
|
||||
fuse_destroy(ft->fuse);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NAPI_METHOD(fuse_native_mount) {
|
||||
NAPI_ARGV(7)
|
||||
|
||||
NAPI_ARGV_UTF8(mnt, 1024, 0);
|
||||
NAPI_ARGV_UTF8(mntopts, 1024, 1);
|
||||
NAPI_ARGV_BUFFER_CAST(fuse_thread_t *, ft, 2);
|
||||
napi_create_reference(env, argv[3], 1, &(ft->ctx));
|
||||
napi_create_reference(env, argv[4], 1, &(ft->malloc));
|
||||
napi_value handlers = argv[5];
|
||||
NAPI_ARGV_BUFFER_CAST(uint32_t *, implemented, 6)
|
||||
|
||||
for (int i = 0; i < 35; i++) {
|
||||
ft->handlers[i] = NULL;
|
||||
}
|
||||
|
||||
NAPI_FOR_EACH(handlers, handler) {
|
||||
napi_create_reference(env, handler, 1, &ft->handlers[i]);
|
||||
}
|
||||
|
||||
ft->env = env;
|
||||
|
||||
struct fuse_operations ops = { };
|
||||
if (implemented[op_access]) ops.access = fuse_native_access;
|
||||
if (implemented[op_truncate]) ops.truncate = fuse_native_truncate;
|
||||
if (implemented[op_ftruncate]) ops.ftruncate = fuse_native_ftruncate;
|
||||
if (implemented[op_getattr]) ops.getattr = fuse_native_getattr;
|
||||
if (implemented[op_fgetattr]) ops.fgetattr = fuse_native_fgetattr;
|
||||
if (implemented[op_flush]) ops.flush = fuse_native_flush;
|
||||
if (implemented[op_fsync]) ops.fsync = fuse_native_fsync;
|
||||
if (implemented[op_fsyncdir]) ops.fsyncdir = fuse_native_fsyncdir;
|
||||
if (implemented[op_readdir]) ops.readdir = fuse_native_readdir;
|
||||
if (implemented[op_readlink]) ops.readlink = fuse_native_readlink;
|
||||
if (implemented[op_chown]) ops.chown = fuse_native_chown;
|
||||
if (implemented[op_chmod]) ops.chmod = fuse_native_chmod;
|
||||
if (implemented[op_mknod]) ops.mknod = fuse_native_mknod;
|
||||
if (implemented[op_setxattr]) ops.setxattr = fuse_native_setxattr;
|
||||
if (implemented[op_getxattr]) ops.getxattr = fuse_native_getxattr;
|
||||
if (implemented[op_listxattr]) ops.listxattr = fuse_native_listxattr;
|
||||
if (implemented[op_removexattr]) ops.removexattr = fuse_native_removexattr;
|
||||
if (implemented[op_statfs]) ops.statfs = fuse_native_statfs;
|
||||
if (implemented[op_open]) ops.open = fuse_native_open;
|
||||
if (implemented[op_opendir]) ops.opendir = fuse_native_opendir;
|
||||
if (implemented[op_read]) ops.read = fuse_native_read;
|
||||
if (implemented[op_write]) ops.write = fuse_native_write;
|
||||
if (implemented[op_release]) ops.release = fuse_native_release;
|
||||
if (implemented[op_releasedir]) ops.releasedir = fuse_native_releasedir;
|
||||
if (implemented[op_create]) ops.create = fuse_native_create;
|
||||
if (implemented[op_utimens]) ops.utimens = fuse_native_utimens;
|
||||
if (implemented[op_unlink]) ops.unlink = fuse_native_unlink;
|
||||
if (implemented[op_rename]) ops.rename = fuse_native_rename;
|
||||
if (implemented[op_link]) ops.link = fuse_native_link;
|
||||
if (implemented[op_symlink]) ops.symlink = fuse_native_symlink;
|
||||
if (implemented[op_mkdir]) ops.mkdir = fuse_native_mkdir;
|
||||
if (implemented[op_rmdir]) ops.rmdir = fuse_native_rmdir;
|
||||
if (implemented[op_init]) ops.init = fuse_native_init;
|
||||
|
||||
int _argc = (strcmp(mntopts, "-o") <= 0) ? 1 : 2;
|
||||
char *_argv[] = {
|
||||
(char *) "fuse_bindings_dummy",
|
||||
(char *) mntopts
|
||||
};
|
||||
|
||||
struct fuse_args args = FUSE_ARGS_INIT(_argc, _argv);
|
||||
struct fuse_chan *ch = fuse_mount(mnt, &args);
|
||||
|
||||
if (ch == NULL) {
|
||||
napi_throw_error(env, "fuse failed", "fuse failed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct fuse *fuse = fuse_new(ch, &args, &ops, sizeof(struct fuse_operations), ft);
|
||||
|
||||
uv_mutex_init(&(ft->mut));
|
||||
uv_sem_init(&(ft->sem), 0);
|
||||
|
||||
strncpy(ft->mnt, mnt, 1024);
|
||||
strncpy(ft->mntopts, mntopts, 1024);
|
||||
ft->fuse = fuse;
|
||||
ft->ch = ch;
|
||||
ft->mounted++;
|
||||
|
||||
int err = uv_async_init(uv_default_loop(), &(ft->async), (uv_async_cb) fuse_native_async_init);
|
||||
|
||||
if (fuse == NULL || err < 0) {
|
||||
napi_throw_error(env, "fuse failed", "fuse failed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pthread_attr_init(&(ft->attr));
|
||||
pthread_create(&(ft->thread), &(ft->attr), start_fuse_thread, ft);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NAPI_METHOD(fuse_native_unmount) {
|
||||
NAPI_ARGV(2)
|
||||
NAPI_ARGV_UTF8(mnt, 1024, 0);
|
||||
NAPI_ARGV_BUFFER_CAST(fuse_thread_t *, ft, 1);
|
||||
|
||||
if (ft != NULL && ft->mounted) {
|
||||
// TODO: Investigate why the FUSE thread is not always killed after fusermount.
|
||||
// pthread_join(ft->thread, NULL);
|
||||
}
|
||||
|
||||
// TODO: fix the async holding the loop
|
||||
uv_unref((uv_handle_t *) &(ft->async));
|
||||
ft->mounted--;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NAPI_INIT() {
|
||||
const napi_node_version* version;
|
||||
assert(napi_get_node_version(env, &version) == napi_ok);
|
||||
|
||||
if (version->major > 12 || (version->major == 12 && version->minor >= 16)) {
|
||||
IS_ARRAY_BUFFER_DETACH_SUPPORTED = 1;
|
||||
}
|
||||
|
||||
pthread_key_create(&(thread_locals_key), NULL); // TODO: add destructor
|
||||
|
||||
NAPI_EXPORT_SIZEOF(fuse_thread_t)
|
||||
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_mount)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_unmount)
|
||||
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_getattr)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_init)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_access)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_statfs)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_fgetattr)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_getattr)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_flush)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_fsync)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_fsyncdir)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_readdir)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_truncate)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_ftruncate)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_utimens)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_readlink)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_chown)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_chmod)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_mknod)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_setxattr)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_getxattr)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_listxattr)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_removexattr)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_open)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_opendir)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_read)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_write)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_release)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_releasedir)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_create)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_unlink)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_rename)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_link)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_symlink)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_mkdir)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_rmdir)
|
||||
|
||||
NAPI_EXPORT_UINT32(op_getattr)
|
||||
NAPI_EXPORT_UINT32(op_init)
|
||||
NAPI_EXPORT_UINT32(op_error)
|
||||
NAPI_EXPORT_UINT32(op_access)
|
||||
NAPI_EXPORT_UINT32(op_statfs)
|
||||
NAPI_EXPORT_UINT32(op_fgetattr)
|
||||
NAPI_EXPORT_UINT32(op_getattr)
|
||||
NAPI_EXPORT_UINT32(op_flush)
|
||||
NAPI_EXPORT_UINT32(op_fsync)
|
||||
NAPI_EXPORT_UINT32(op_fsyncdir)
|
||||
NAPI_EXPORT_UINT32(op_readdir)
|
||||
NAPI_EXPORT_UINT32(op_truncate)
|
||||
NAPI_EXPORT_UINT32(op_ftruncate)
|
||||
NAPI_EXPORT_UINT32(op_utimens)
|
||||
NAPI_EXPORT_UINT32(op_readlink)
|
||||
NAPI_EXPORT_UINT32(op_chown)
|
||||
NAPI_EXPORT_UINT32(op_chmod)
|
||||
NAPI_EXPORT_UINT32(op_mknod)
|
||||
NAPI_EXPORT_UINT32(op_setxattr)
|
||||
NAPI_EXPORT_UINT32(op_getxattr)
|
||||
NAPI_EXPORT_UINT32(op_listxattr)
|
||||
NAPI_EXPORT_UINT32(op_removexattr)
|
||||
NAPI_EXPORT_UINT32(op_open)
|
||||
NAPI_EXPORT_UINT32(op_opendir)
|
||||
NAPI_EXPORT_UINT32(op_read)
|
||||
NAPI_EXPORT_UINT32(op_write)
|
||||
NAPI_EXPORT_UINT32(op_release)
|
||||
NAPI_EXPORT_UINT32(op_releasedir)
|
||||
NAPI_EXPORT_UINT32(op_create)
|
||||
NAPI_EXPORT_UINT32(op_unlink)
|
||||
NAPI_EXPORT_UINT32(op_rename)
|
||||
NAPI_EXPORT_UINT32(op_link)
|
||||
NAPI_EXPORT_UINT32(op_symlink)
|
||||
NAPI_EXPORT_UINT32(op_mkdir)
|
||||
NAPI_EXPORT_UINT32(op_rmdir)
|
||||
}
|
967
index.js
967
index.js
@ -1,153 +1,834 @@
|
||||
var bindings = require('bindings')
|
||||
var fuse = bindings('fuse_bindings')
|
||||
var path = require('path')
|
||||
const os = require('os')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { exec } = require('child_process')
|
||||
|
||||
var noop = function () {}
|
||||
const Nanoresource = require('nanoresource')
|
||||
const { beforeMount, beforeUnmount, configure, unconfigure, isConfigured } = require('fuse-shared-library')
|
||||
|
||||
var FuseBuffer = function () {
|
||||
this.length = 0
|
||||
this.parent = undefined
|
||||
const binding = require('node-gyp-build')(__dirname)
|
||||
|
||||
const IS_OSX = os.platform() === 'darwin'
|
||||
const OSX_FOLDER_ICON = '/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericFolderIcon.icns'
|
||||
const HAS_FOLDER_ICON = IS_OSX && fs.existsSync(OSX_FOLDER_ICON)
|
||||
const DEFAULT_TIMEOUT = 15 * 1000
|
||||
const TIMEOUT_ERRNO = IS_OSX ? -60 : -110
|
||||
const ENOTCONN = IS_OSX ? -57 : -107
|
||||
|
||||
const OpcodesAndDefaults = new Map([
|
||||
['init', {
|
||||
op: binding.op_init
|
||||
}],
|
||||
['error', {
|
||||
op: binding.op_error
|
||||
}],
|
||||
['access', {
|
||||
op: binding.op_access,
|
||||
defaults: [0]
|
||||
}],
|
||||
['statfs', {
|
||||
op: binding.op_statfs,
|
||||
defaults: [getStatfsArray()]
|
||||
}],
|
||||
['fgetattr', {
|
||||
op: binding.op_fgetattr,
|
||||
defaults: [getStatArray()]
|
||||
}],
|
||||
['getattr', {
|
||||
op: binding.op_getattr,
|
||||
defaults: [getStatArray()]
|
||||
}],
|
||||
['flush', {
|
||||
op: binding.op_flush
|
||||
}],
|
||||
['fsync', {
|
||||
op: binding.op_fsync
|
||||
}],
|
||||
['fsyncdir', {
|
||||
op: binding.op_fsyncdir
|
||||
}],
|
||||
['readdir', {
|
||||
op: binding.op_readdir,
|
||||
defaults: [[], []]
|
||||
}],
|
||||
['truncate', {
|
||||
op: binding.op_truncate
|
||||
}],
|
||||
['ftruncate', {
|
||||
op: binding.op_ftruncate
|
||||
}],
|
||||
['utimens', {
|
||||
op: binding.op_utimens
|
||||
}],
|
||||
['readlink', {
|
||||
op: binding.op_readlink,
|
||||
defaults: ['']
|
||||
}],
|
||||
['chown', {
|
||||
op: binding.op_chown
|
||||
}],
|
||||
['chmod', {
|
||||
op: binding.op_chmod
|
||||
}],
|
||||
['mknod', {
|
||||
op: binding.op_mknod
|
||||
}],
|
||||
['setxattr', {
|
||||
op: binding.op_setxattr
|
||||
}],
|
||||
['getxattr', {
|
||||
op: binding.op_getxattr
|
||||
}],
|
||||
['listxattr', {
|
||||
op: binding.op_listxattr
|
||||
}],
|
||||
['removexattr', {
|
||||
op: binding.op_removexattr
|
||||
}],
|
||||
['open', {
|
||||
op: binding.op_open,
|
||||
defaults: [0]
|
||||
}],
|
||||
['opendir', {
|
||||
op: binding.op_opendir,
|
||||
defaults: [0]
|
||||
}],
|
||||
['read', {
|
||||
op: binding.op_read,
|
||||
defaults: [0]
|
||||
}],
|
||||
['write', {
|
||||
op: binding.op_write,
|
||||
defaults: [0]
|
||||
}],
|
||||
['release', {
|
||||
op: binding.op_release
|
||||
}],
|
||||
['releasedir', {
|
||||
op: binding.op_releasedir
|
||||
}],
|
||||
['create', {
|
||||
op: binding.op_create,
|
||||
defaults: [0]
|
||||
}],
|
||||
['unlink', {
|
||||
op: binding.op_unlink
|
||||
}],
|
||||
['rename', {
|
||||
op: binding.op_rename
|
||||
}],
|
||||
['link', {
|
||||
op: binding.op_link
|
||||
}],
|
||||
['symlink', {
|
||||
op: binding.op_symlink
|
||||
}],
|
||||
['mkdir', {
|
||||
op: binding.op_mkdir
|
||||
}],
|
||||
['rmdir', {
|
||||
op: binding.op_rmdir
|
||||
}]
|
||||
])
|
||||
|
||||
class Fuse extends Nanoresource {
|
||||
constructor (mnt, ops, opts = {}) {
|
||||
super()
|
||||
|
||||
this.opts = opts
|
||||
this.mnt = path.resolve(mnt)
|
||||
this.ops = ops
|
||||
this.timeout = opts.timeout === false ? 0 : (opts.timeout || DEFAULT_TIMEOUT)
|
||||
|
||||
this._force = !!opts.force
|
||||
this._mkdir = !!opts.mkdir
|
||||
this._thread = null
|
||||
this._handlers = this._makeHandlerArray()
|
||||
this._threads = new Set()
|
||||
|
||||
const implemented = [binding.op_init, binding.op_error, binding.op_getattr]
|
||||
if (ops) {
|
||||
for (const [name, { op }] of OpcodesAndDefaults) {
|
||||
if (ops[name]) implemented.push(op)
|
||||
}
|
||||
}
|
||||
this._implemented = new Set(implemented)
|
||||
|
||||
// Used to determine if the user-defined callback needs to be nextTick'd.
|
||||
this._sync = true
|
||||
}
|
||||
|
||||
FuseBuffer.prototype = Buffer.prototype
|
||||
|
||||
fuse.setBuffer(FuseBuffer)
|
||||
|
||||
exports.mount = function (mnt, ops) {
|
||||
if (!ops) ops = {}
|
||||
if (/\*|(^,)fuse-bindings(,$)/.test(process.env.DEBUG)) ops.options = ['debug'].concat(ops.options || [])
|
||||
return fuse.mount(path.resolve(mnt), ops)
|
||||
_getImplementedArray () {
|
||||
const implemented = new Uint32Array(35)
|
||||
for (const impl of this._implemented) {
|
||||
implemented[impl] = 1
|
||||
}
|
||||
return implemented
|
||||
}
|
||||
|
||||
exports.unmount = function (mnt, cb) {
|
||||
fuse.unmount(path.resolve(mnt), cb || noop)
|
||||
_fuseOptions () {
|
||||
const options = []
|
||||
|
||||
if ((/\*|(^,)fuse-bindings(,$)/.test(process.env.DEBUG)) || this.opts.debug) options.push('debug')
|
||||
if (this.opts.allowOther) options.push('allow_other')
|
||||
if (this.opts.allowRoot) options.push('allow_root')
|
||||
if (this.opts.autoUnmount) options.push('auto_unmount')
|
||||
if (this.opts.defaultPermissions) options.push('default_permissions')
|
||||
if (this.opts.blkdev) options.push('blkdev')
|
||||
if (this.opts.blksize) options.push('blksize=' + this.opts.blksize)
|
||||
if (this.opts.maxRead) options.push('max_read=' + this.opts.maxRead)
|
||||
if (this.opts.fd) options.push('fd=' + this.opts.fd)
|
||||
if (this.opts.userId) options.push('user_id=', this.opts.userId)
|
||||
if (this.opts.fsname) options.push('fsname=' + this.opts.fsname)
|
||||
if (this.opts.subtype) options.push('subtype=' + this.opts.subtype)
|
||||
if (this.opts.kernelCache) options.push('kernel_cache')
|
||||
if (this.opts.autoCache) options.push('auto_cache')
|
||||
if (this.opts.umask) options.push('umask=' + this.opts.umask)
|
||||
if (this.opts.uid) options.push('uid=' + this.opts.uid)
|
||||
if (this.opts.gid) options.push('gid=' + this.opts.gid)
|
||||
if (this.opts.entryTimeout) options.push('entry_timeout=' + this.opts.entryTimeout)
|
||||
if (this.opts.attrTimeout) options.push('attr_timeout=' + this.opts.attrTimeout)
|
||||
if (this.opts.acAttrTimeout) options.push('ac_attr_timeout=' + this.opts.acAttrTimeout)
|
||||
if (this.opts.noforget) options.push('noforget')
|
||||
if (this.opts.remember) options.push('remember=' + this.opts.remember)
|
||||
if (this.opts.modules) options.push('modules=' + this.opts.modules)
|
||||
|
||||
if (this.opts.displayFolder && IS_OSX) { // only works on osx
|
||||
options.push('volname=' + path.basename(this.opts.name || this.mnt))
|
||||
if (HAS_FOLDER_ICON) options.push('volicon=' + OSX_FOLDER_ICON)
|
||||
}
|
||||
|
||||
exports.unmountSync = function (mnt) {
|
||||
return fuse.unmountSync(path.resolve(mnt))
|
||||
return options.length ? '-o' + options.join(',') : ''
|
||||
}
|
||||
|
||||
exports.EPERM = -1
|
||||
exports.ENOENT = -2
|
||||
exports.ESRCH = -3
|
||||
exports.EINTR = -4
|
||||
exports.EIO = -5
|
||||
exports.ENXIO = -6
|
||||
exports.E2BIG = -7
|
||||
exports.ENOEXEC = -8
|
||||
exports.EBADF = -9
|
||||
exports.ECHILD = -10
|
||||
exports.EAGAIN = -11
|
||||
exports.ENOMEM = -12
|
||||
exports.EACCES = -13
|
||||
exports.EFAULT = -14
|
||||
exports.ENOTBLK = -15
|
||||
exports.EBUSY = -16
|
||||
exports.EEXIST = -17
|
||||
exports.EXDEV = -18
|
||||
exports.ENODEV = -19
|
||||
exports.ENOTDIR = -20
|
||||
exports.EISDIR = -21
|
||||
exports.EINVAL = -22
|
||||
exports.ENFILE = -23
|
||||
exports.EMFILE = -24
|
||||
exports.ENOTTY = -25
|
||||
exports.ETXTBSY = -26
|
||||
exports.EFBIG = -27
|
||||
exports.ENOSPC = -28
|
||||
exports.ESPIPE = -29
|
||||
exports.EROFS = -30
|
||||
exports.EMLINK = -31
|
||||
exports.EPIPE = -32
|
||||
exports.EDOM = -33
|
||||
exports.ERANGE = -34
|
||||
exports.EDEADLK = -35
|
||||
exports.ENAMETOOLONG = -36
|
||||
exports.ENOLCK = -37
|
||||
exports.ENOSYS = -38
|
||||
exports.ENOTEMPTY = -39
|
||||
exports.ELOOP = -40
|
||||
exports.EWOULDBLOCK = -11
|
||||
exports.ENOMSG = -42
|
||||
exports.EIDRM = -43
|
||||
exports.ECHRNG = -44
|
||||
exports.EL2NSYNC = -45
|
||||
exports.EL3HLT = -46
|
||||
exports.EL3RST = -47
|
||||
exports.ELNRNG = -48
|
||||
exports.EUNATCH = -49
|
||||
exports.ENOCSI = -50
|
||||
exports.EL2HLT = -51
|
||||
exports.EBADE = -52
|
||||
exports.EBADR = -53
|
||||
exports.EXFULL = -54
|
||||
exports.ENOANO = -55
|
||||
exports.EBADRQC = -56
|
||||
exports.EBADSLT = -57
|
||||
exports.EDEADLOCK = -35
|
||||
exports.EBFONT = -59
|
||||
exports.ENOSTR = -60
|
||||
exports.ENODATA = -61
|
||||
exports.ETIME = -62
|
||||
exports.ENOSR = -63
|
||||
exports.ENONET = -64
|
||||
exports.ENOPKG = -65
|
||||
exports.EREMOTE = -66
|
||||
exports.ENOLINK = -67
|
||||
exports.EADV = -68
|
||||
exports.ESRMNT = -69
|
||||
exports.ECOMM = -70
|
||||
exports.EPROTO = -71
|
||||
exports.EMULTIHOP = -72
|
||||
exports.EDOTDOT = -73
|
||||
exports.EBADMSG = -74
|
||||
exports.EOVERFLOW = -75
|
||||
exports.ENOTUNIQ = -76
|
||||
exports.EBADFD = -77
|
||||
exports.EREMCHG = -78
|
||||
exports.ELIBACC = -79
|
||||
exports.ELIBBAD = -80
|
||||
exports.ELIBSCN = -81
|
||||
exports.ELIBMAX = -82
|
||||
exports.ELIBEXEC = -83
|
||||
exports.EILSEQ = -84
|
||||
exports.ERESTART = -85
|
||||
exports.ESTRPIPE = -86
|
||||
exports.EUSERS = -87
|
||||
exports.ENOTSOCK = -88
|
||||
exports.EDESTADDRREQ = -89
|
||||
exports.EMSGSIZE = -90
|
||||
exports.EPROTOTYPE = -91
|
||||
exports.ENOPROTOOPT = -92
|
||||
exports.EPROTONOSUPPORT = -93
|
||||
exports.ESOCKTNOSUPPORT = -94
|
||||
exports.EOPNOTSUPP = -95
|
||||
exports.EPFNOSUPPORT = -96
|
||||
exports.EAFNOSUPPORT = -97
|
||||
exports.EADDRINUSE = -98
|
||||
exports.EADDRNOTAVAIL = -99
|
||||
exports.ENETDOWN = -100
|
||||
exports.ENETUNREACH = -101
|
||||
exports.ENETRESET = -102
|
||||
exports.ECONNABORTED = -103
|
||||
exports.ECONNRESET = -104
|
||||
exports.ENOBUFS = -105
|
||||
exports.EISCONN = -106
|
||||
exports.ENOTCONN = -107
|
||||
exports.ESHUTDOWN = -108
|
||||
exports.ETOOMANYREFS = -109
|
||||
exports.ETIMEDOUT = -110
|
||||
exports.ECONNREFUSED = -111
|
||||
exports.EHOSTDOWN = -112
|
||||
exports.EHOSTUNREACH = -113
|
||||
exports.EALREADY = -114
|
||||
exports.EINPROGRESS = -115
|
||||
exports.ESTALE = -116
|
||||
exports.EUCLEAN = -117
|
||||
exports.ENOTNAM = -118
|
||||
exports.ENAVAIL = -119
|
||||
exports.EISNAM = -120
|
||||
exports.EREMOTEIO = -121
|
||||
exports.EDQUOT = -122
|
||||
exports.ENOMEDIUM = -123
|
||||
exports.EMEDIUMTYPE = -124
|
||||
_malloc (size) {
|
||||
const buf = Buffer.alloc(size)
|
||||
this._threads.add(buf)
|
||||
return buf
|
||||
}
|
||||
|
||||
_makeHandlerArray () {
|
||||
const self = this
|
||||
const handlers = new Array(OpcodesAndDefaults.size)
|
||||
|
||||
for (const [name, { op, defaults }] of OpcodesAndDefaults) {
|
||||
const nativeSignal = binding[`fuse_native_signal_${name}`]
|
||||
if (!nativeSignal) continue
|
||||
|
||||
handlers[op] = makeHandler(name, op, defaults, nativeSignal)
|
||||
}
|
||||
|
||||
return handlers
|
||||
|
||||
function makeHandler (name, op, defaults, nativeSignal) {
|
||||
let to = self.timeout
|
||||
if (typeof to === 'object' && to) {
|
||||
const defaultTimeout = to.default || DEFAULT_TIMEOUT
|
||||
to = to[name]
|
||||
if (!to && to !== false) to = defaultTimeout
|
||||
}
|
||||
|
||||
return function (nativeHandler, opCode, ...args) {
|
||||
const sig = signal.bind(null, nativeHandler)
|
||||
const input = [...args]
|
||||
const boundSignal = to ? autoTimeout(sig, input) : sig
|
||||
const funcName = `_op_${name}`
|
||||
if (!self[funcName] || !self._implemented.has(op)) return boundSignal(-1, ...defaults)
|
||||
return self[funcName].apply(self, [boundSignal, ...args])
|
||||
}
|
||||
|
||||
function signal (nativeHandler, err, ...args) {
|
||||
var arr = [nativeHandler, err, ...args]
|
||||
|
||||
if (defaults) {
|
||||
while (arr.length > 2 && arr[arr.length - 1] === undefined) arr.pop()
|
||||
if (arr.length === 2) arr = arr.concat(defaults)
|
||||
}
|
||||
|
||||
return process.nextTick(nativeSignal, ...arr)
|
||||
}
|
||||
|
||||
function autoTimeout (cb, input) {
|
||||
let called = false
|
||||
const timeout = setTimeout(timeoutWrap, to, TIMEOUT_ERRNO)
|
||||
return timeoutWrap
|
||||
|
||||
function timeoutWrap (err, ...args) {
|
||||
if (called) return
|
||||
called = true
|
||||
|
||||
clearTimeout(timeout)
|
||||
|
||||
if (err === TIMEOUT_ERRNO) {
|
||||
switch (name) {
|
||||
case 'write':
|
||||
case 'read':
|
||||
return cb(TIMEOUT_ERRNO, 0, input[2].buffer)
|
||||
case 'setxattr':
|
||||
return cb(TIMEOUT_ERRNO, input[2].buffer)
|
||||
case 'getxattr':
|
||||
return cb(TIMEOUT_ERRNO, input[2].buffer)
|
||||
case 'listxattr':
|
||||
return cb(TIMEOUT_ERRNO, input[1].buffer)
|
||||
}
|
||||
}
|
||||
|
||||
cb(err, ...args)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Static methods
|
||||
|
||||
static unmount (mnt, cb) {
|
||||
mnt = JSON.stringify(mnt)
|
||||
const cmd = IS_OSX ? `diskutil unmount force ${mnt}` : `fusermount -uz ${mnt}`
|
||||
exec(cmd, err => {
|
||||
if (err) return cb(err)
|
||||
return cb(null)
|
||||
})
|
||||
}
|
||||
|
||||
// Debugging methods
|
||||
|
||||
// Lifecycle methods
|
||||
|
||||
_open (cb) {
|
||||
const self = this
|
||||
|
||||
if (this._force) {
|
||||
return fs.stat(path.join(this.mnt, 'test'), (err, st) => {
|
||||
if (err && (err.errno === ENOTCONN || err.errno === Fuse.ENXIO)) return Fuse.unmount(this.mnt, open)
|
||||
return open()
|
||||
})
|
||||
}
|
||||
return open()
|
||||
|
||||
function open () {
|
||||
// If there was an unmount error, continue attempting to mount (this is the best we can do)
|
||||
self._thread = Buffer.alloc(binding.sizeof_fuse_thread_t)
|
||||
self._openCallback = cb
|
||||
|
||||
const opts = self._fuseOptions()
|
||||
const implemented = self._getImplementedArray()
|
||||
|
||||
return fs.stat(self.mnt, (err, stat) => {
|
||||
if (err && err.errno !== -2) return cb(err)
|
||||
if (err) {
|
||||
if (!self._mkdir) return cb(new Error('Mountpoint does not exist'))
|
||||
return fs.mkdir(self.mnt, { recursive: true }, err => {
|
||||
if (err) return cb(err)
|
||||
fs.stat(self.mnt, (err, stat) => {
|
||||
if (err) return cb(err)
|
||||
return onexists(stat)
|
||||
})
|
||||
})
|
||||
}
|
||||
if (!stat.isDirectory()) return cb(new Error('Mountpoint is not a directory'))
|
||||
return onexists(stat)
|
||||
})
|
||||
|
||||
function onexists (stat) {
|
||||
fs.stat(path.join(self.mnt, '..'), (_, parent) => {
|
||||
if (parent && parent.dev !== stat.dev) return cb(new Error('Mountpoint in use'))
|
||||
try {
|
||||
// TODO: asyncify
|
||||
binding.fuse_native_mount(self.mnt, opts, self._thread, self, self._malloc, self._handlers, implemented)
|
||||
} catch (err) {
|
||||
return cb(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_close (cb) {
|
||||
const self = this
|
||||
|
||||
Fuse.unmount(this.mnt, err => {
|
||||
if (err) {
|
||||
err.unmountFailure = true
|
||||
return cb(err)
|
||||
}
|
||||
nativeUnmount()
|
||||
})
|
||||
|
||||
function nativeUnmount () {
|
||||
try {
|
||||
binding.fuse_native_unmount(self.mnt, self._thread)
|
||||
} catch (err) {
|
||||
return cb(err)
|
||||
}
|
||||
return cb(null)
|
||||
}
|
||||
}
|
||||
|
||||
// Handlers
|
||||
|
||||
_op_init (signal) {
|
||||
if (this._openCallback) {
|
||||
process.nextTick(this._openCallback, null)
|
||||
this._openCallback = null
|
||||
}
|
||||
if (!this.ops.init) {
|
||||
signal(0)
|
||||
return
|
||||
}
|
||||
this.ops.init(err => {
|
||||
return signal(err)
|
||||
})
|
||||
}
|
||||
|
||||
_op_error (signal) {
|
||||
if (!this.ops.error) {
|
||||
signal(0)
|
||||
return
|
||||
}
|
||||
this.ops.error(err => {
|
||||
return signal(err)
|
||||
})
|
||||
}
|
||||
|
||||
_op_statfs (signal, path) {
|
||||
this.ops.statfs(path, (err, statfs) => {
|
||||
if (err) return signal(err)
|
||||
const arr = getStatfsArray(statfs)
|
||||
return signal(0, arr)
|
||||
})
|
||||
}
|
||||
|
||||
_op_getattr (signal, path) {
|
||||
if (!this.ops.getattr) {
|
||||
if (path !== '/') {
|
||||
signal(Fuse.EPERM)
|
||||
} else {
|
||||
signal(0, getStatArray({ mtime: new Date(0), atime: new Date(0), ctime: new Date(0), mode: 16877, size: 4096 }))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
this.ops.getattr(path, (err, stat) => {
|
||||
if (err) return signal(err, getStatArray())
|
||||
return signal(0, getStatArray(stat))
|
||||
})
|
||||
}
|
||||
|
||||
_op_fgetattr (signal, path, fd) {
|
||||
if (!this.ops.fgetattr) {
|
||||
if (path !== '/') {
|
||||
signal(Fuse.EPERM)
|
||||
} else {
|
||||
signal(0, getStatArray({ mtime: new Date(0), atime: new Date(0), ctime: new Date(0), mode: 16877, size: 4096 }))
|
||||
}
|
||||
return
|
||||
}
|
||||
this.ops.getattr(path, (err, stat) => {
|
||||
if (err) return signal(err)
|
||||
return signal(0, getStatArray(stat))
|
||||
})
|
||||
}
|
||||
|
||||
_op_access (signal, path, mode) {
|
||||
this.ops.access(path, mode, err => {
|
||||
return signal(err)
|
||||
})
|
||||
}
|
||||
|
||||
_op_open (signal, path, flags) {
|
||||
this.ops.open(path, flags, (err, fd) => {
|
||||
return signal(err, fd)
|
||||
})
|
||||
}
|
||||
|
||||
_op_opendir (signal, path, flags) {
|
||||
this.ops.opendir(path, flags, (err, fd) => {
|
||||
return signal(err, fd)
|
||||
})
|
||||
}
|
||||
|
||||
_op_create (signal, path, mode) {
|
||||
this.ops.create(path, mode, (err, fd) => {
|
||||
return signal(err, fd)
|
||||
})
|
||||
}
|
||||
|
||||
_op_utimens (signal, path, atimeLow, atimeHigh, mtimeLow, mtimeHigh) {
|
||||
const atime = getDoubleArg(atimeLow, atimeHigh)
|
||||
const mtime = getDoubleArg(mtimeLow, mtimeHigh)
|
||||
this.ops.utimens(path, atime, mtime, err => {
|
||||
return signal(err)
|
||||
})
|
||||
}
|
||||
|
||||
_op_release (signal, path, fd) {
|
||||
this.ops.release(path, fd, err => {
|
||||
return signal(err)
|
||||
})
|
||||
}
|
||||
|
||||
_op_releasedir (signal, path, fd) {
|
||||
this.ops.releasedir(path, fd, err => {
|
||||
return signal(err)
|
||||
})
|
||||
}
|
||||
|
||||
_op_read (signal, path, fd, buf, len, offsetLow, offsetHigh) {
|
||||
this.ops.read(path, fd, buf, len, getDoubleArg(offsetLow, offsetHigh), (err, bytesRead) => {
|
||||
return signal(err, bytesRead || 0, buf.buffer)
|
||||
})
|
||||
}
|
||||
|
||||
_op_write (signal, path, fd, buf, len, offsetLow, offsetHigh) {
|
||||
this.ops.write(path, fd, buf, len, getDoubleArg(offsetLow, offsetHigh), (err, bytesWritten) => {
|
||||
return signal(err, bytesWritten || 0, buf.buffer)
|
||||
})
|
||||
}
|
||||
|
||||
_op_readdir (signal, path) {
|
||||
this.ops.readdir(path, (err, names, stats) => {
|
||||
if (err) return signal(err)
|
||||
if (stats) stats = stats.map(getStatArray)
|
||||
return signal(0, names, stats || [])
|
||||
})
|
||||
}
|
||||
|
||||
_op_setxattr (signal, path, name, value, position, flags) {
|
||||
this.ops.setxattr(path, name, value, position, flags, err => {
|
||||
return signal(err, value.buffer)
|
||||
})
|
||||
}
|
||||
|
||||
_op_getxattr (signal, path, name, valueBuf, position) {
|
||||
this.ops.getxattr(path, name, position, (err, value) => {
|
||||
if (!err) {
|
||||
if (!value) return signal(IS_OSX ? -93 : -61, valueBuf.buffer)
|
||||
value.copy(valueBuf)
|
||||
return signal(value.length, valueBuf.buffer)
|
||||
}
|
||||
return signal(err, valueBuf.buffer)
|
||||
})
|
||||
}
|
||||
|
||||
_op_listxattr (signal, path, listBuf) {
|
||||
this.ops.listxattr(path, (err, list) => {
|
||||
if (list && !err) {
|
||||
if (!listBuf.length) {
|
||||
let size = 0
|
||||
for (const name of list) size += Buffer.byteLength(name) + 1
|
||||
size += 128 // fuse yells if we do not signal room for some mac stuff also
|
||||
return signal(size, listBuf.buffer)
|
||||
}
|
||||
|
||||
let ptr = 0
|
||||
for (const name of list) {
|
||||
listBuf.write(name, ptr)
|
||||
ptr += Buffer.byteLength(name)
|
||||
listBuf[ptr++] = 0
|
||||
}
|
||||
|
||||
return signal(ptr, listBuf.buffer)
|
||||
}
|
||||
return signal(err, listBuf.buffer)
|
||||
})
|
||||
}
|
||||
|
||||
_op_removexattr (signal, path, name) {
|
||||
this.ops.removexattr(path, name, err => {
|
||||
return signal(err)
|
||||
})
|
||||
}
|
||||
|
||||
_op_flush (signal, path, fd) {
|
||||
this.ops.flush(path, fd, err => {
|
||||
return signal(err)
|
||||
})
|
||||
}
|
||||
|
||||
_op_fsync (signal, path, datasync, fd) {
|
||||
this.ops.fsync(path, datasync, fd, err => {
|
||||
return signal(err)
|
||||
})
|
||||
}
|
||||
|
||||
_op_fsyncdir (signal, path, datasync, fd) {
|
||||
this.ops.fsyncdir(path, datasync, fd, err => {
|
||||
return signal(err)
|
||||
})
|
||||
}
|
||||
|
||||
_op_truncate (signal, path, sizeLow, sizeHigh) {
|
||||
const size = getDoubleArg(sizeLow, sizeHigh)
|
||||
this.ops.truncate(path, size, err => {
|
||||
return signal(err)
|
||||
})
|
||||
}
|
||||
|
||||
_op_ftruncate (signal, path, fd, sizeLow, sizeHigh) {
|
||||
const size = getDoubleArg(sizeLow, sizeHigh)
|
||||
this.ops.ftruncate(path, fd, size, err => {
|
||||
return signal(err)
|
||||
})
|
||||
}
|
||||
|
||||
_op_readlink (signal, path) {
|
||||
this.ops.readlink(path, (err, linkname) => {
|
||||
return signal(err, linkname)
|
||||
})
|
||||
}
|
||||
|
||||
_op_chown (signal, path, uid, gid) {
|
||||
this.ops.chown(path, uid, gid, err => {
|
||||
return signal(err)
|
||||
})
|
||||
}
|
||||
|
||||
_op_chmod (signal, path, mode) {
|
||||
this.ops.chmod(path, mode, err => {
|
||||
return signal(err)
|
||||
})
|
||||
}
|
||||
|
||||
_op_mknod (signal, path, mode, dev) {
|
||||
this.ops.mknod(path, mode, dev, err => {
|
||||
return signal(err)
|
||||
})
|
||||
}
|
||||
|
||||
_op_unlink (signal, path) {
|
||||
this.ops.unlink(path, err => {
|
||||
return signal(err)
|
||||
})
|
||||
}
|
||||
|
||||
_op_rename (signal, src, dest) {
|
||||
this.ops.rename(src, dest, err => {
|
||||
return signal(err)
|
||||
})
|
||||
}
|
||||
|
||||
_op_link (signal, src, dest) {
|
||||
this.ops.link(src, dest, err => {
|
||||
return signal(err)
|
||||
})
|
||||
}
|
||||
|
||||
_op_symlink (signal, src, dest) {
|
||||
this.ops.symlink(src, dest, err => {
|
||||
return signal(err)
|
||||
})
|
||||
}
|
||||
|
||||
_op_mkdir (signal, path, mode) {
|
||||
this.ops.mkdir(path, mode, err => {
|
||||
return signal(err)
|
||||
})
|
||||
}
|
||||
|
||||
_op_rmdir (signal, path) {
|
||||
this.ops.rmdir(path, err => {
|
||||
return signal(err)
|
||||
})
|
||||
}
|
||||
|
||||
// Public API
|
||||
|
||||
mount (cb) {
|
||||
return this.open(cb)
|
||||
}
|
||||
|
||||
unmount (cb) {
|
||||
return this.close(cb)
|
||||
}
|
||||
|
||||
errno (code) {
|
||||
return (code && Fuse[code.toUpperCase()]) || -1
|
||||
}
|
||||
}
|
||||
|
||||
Fuse.EPERM = -1
|
||||
Fuse.ENOENT = -2
|
||||
Fuse.ESRCH = -3
|
||||
Fuse.EINTR = -4
|
||||
Fuse.EIO = -5
|
||||
Fuse.ENXIO = -6
|
||||
Fuse.E2BIG = -7
|
||||
Fuse.ENOEXEC = -8
|
||||
Fuse.EBADF = -9
|
||||
Fuse.ECHILD = -10
|
||||
Fuse.EAGAIN = -11
|
||||
Fuse.ENOMEM = -12
|
||||
Fuse.EACCES = -13
|
||||
Fuse.EFAULT = -14
|
||||
Fuse.ENOTBLK = -15
|
||||
Fuse.EBUSY = -16
|
||||
Fuse.EEXIST = -17
|
||||
Fuse.EXDEV = -18
|
||||
Fuse.ENODEV = -19
|
||||
Fuse.ENOTDIR = -20
|
||||
Fuse.EISDIR = -21
|
||||
Fuse.EINVAL = -22
|
||||
Fuse.ENFILE = -23
|
||||
Fuse.EMFILE = -24
|
||||
Fuse.ENOTTY = -25
|
||||
Fuse.ETXTBSY = -26
|
||||
Fuse.EFBIG = -27
|
||||
Fuse.ENOSPC = -28
|
||||
Fuse.ESPIPE = -29
|
||||
Fuse.EROFS = -30
|
||||
Fuse.EMLINK = -31
|
||||
Fuse.EPIPE = -32
|
||||
Fuse.EDOM = -33
|
||||
Fuse.ERANGE = -34
|
||||
Fuse.EDEADLK = -35
|
||||
Fuse.ENAMETOOLONG = -36
|
||||
Fuse.ENOLCK = -37
|
||||
Fuse.ENOSYS = -38
|
||||
Fuse.ENOTEMPTY = -39
|
||||
Fuse.ELOOP = -40
|
||||
Fuse.EWOULDBLOCK = -11
|
||||
Fuse.ENOMSG = -42
|
||||
Fuse.EIDRM = -43
|
||||
Fuse.ECHRNG = -44
|
||||
Fuse.EL2NSYNC = -45
|
||||
Fuse.EL3HLT = -46
|
||||
Fuse.EL3RST = -47
|
||||
Fuse.ELNRNG = -48
|
||||
Fuse.EUNATCH = -49
|
||||
Fuse.ENOCSI = -50
|
||||
Fuse.EL2HLT = -51
|
||||
Fuse.EBADE = -52
|
||||
Fuse.EBADR = -53
|
||||
Fuse.EXFULL = -54
|
||||
Fuse.ENOANO = -55
|
||||
Fuse.EBADRQC = -56
|
||||
Fuse.EBADSLT = -57
|
||||
Fuse.EDEADLOCK = -35
|
||||
Fuse.EBFONT = -59
|
||||
Fuse.ENOSTR = -60
|
||||
Fuse.ENODATA = -61
|
||||
Fuse.ETIME = -62
|
||||
Fuse.ENOSR = -63
|
||||
Fuse.ENONET = -64
|
||||
Fuse.ENOPKG = -65
|
||||
Fuse.EREMOTE = -66
|
||||
Fuse.ENOLINK = -67
|
||||
Fuse.EADV = -68
|
||||
Fuse.ESRMNT = -69
|
||||
Fuse.ECOMM = -70
|
||||
Fuse.EPROTO = -71
|
||||
Fuse.EMULTIHOP = -72
|
||||
Fuse.EDOTDOT = -73
|
||||
Fuse.EBADMSG = -74
|
||||
Fuse.EOVERFLOW = -75
|
||||
Fuse.ENOTUNIQ = -76
|
||||
Fuse.EBADFD = -77
|
||||
Fuse.EREMCHG = -78
|
||||
Fuse.ELIBACC = -79
|
||||
Fuse.ELIBBAD = -80
|
||||
Fuse.ELIBSCN = -81
|
||||
Fuse.ELIBMAX = -82
|
||||
Fuse.ELIBEXEC = -83
|
||||
Fuse.EILSEQ = -84
|
||||
Fuse.ERESTART = -85
|
||||
Fuse.ESTRPIPE = -86
|
||||
Fuse.EUSERS = -87
|
||||
Fuse.ENOTSOCK = -88
|
||||
Fuse.EDESTADDRREQ = -89
|
||||
Fuse.EMSGSIZE = -90
|
||||
Fuse.EPROTOTYPE = -91
|
||||
Fuse.ENOPROTOOPT = -92
|
||||
Fuse.EPROTONOSUPPORT = -93
|
||||
Fuse.ESOCKTNOSUPPORT = -94
|
||||
Fuse.EOPNOTSUPP = -95
|
||||
Fuse.EPFNOSUPPORT = -96
|
||||
Fuse.EAFNOSUPPORT = -97
|
||||
Fuse.EADDRINUSE = -98
|
||||
Fuse.EADDRNOTAVAIL = -99
|
||||
Fuse.ENETDOWN = -100
|
||||
Fuse.ENETUNREACH = -101
|
||||
Fuse.ENETRESET = -102
|
||||
Fuse.ECONNABORTED = -103
|
||||
Fuse.ECONNRESET = -104
|
||||
Fuse.ENOBUFS = -105
|
||||
Fuse.EISCONN = -106
|
||||
Fuse.ENOTCONN = -107
|
||||
Fuse.ESHUTDOWN = -108
|
||||
Fuse.ETOOMANYREFS = -109
|
||||
Fuse.ETIMEDOUT = -110
|
||||
Fuse.ECONNREFUSED = -111
|
||||
Fuse.EHOSTDOWN = -112
|
||||
Fuse.EHOSTUNREACH = -113
|
||||
Fuse.EALREADY = -114
|
||||
Fuse.EINPROGRESS = -115
|
||||
Fuse.ESTALE = -116
|
||||
Fuse.EUCLEAN = -117
|
||||
Fuse.ENOTNAM = -118
|
||||
Fuse.ENAVAIL = -119
|
||||
Fuse.EISNAM = -120
|
||||
Fuse.EREMOTEIO = -121
|
||||
Fuse.EDQUOT = -122
|
||||
Fuse.ENOMEDIUM = -123
|
||||
Fuse.EMEDIUMTYPE = -124
|
||||
|
||||
// Forward configuration functions through the exported class.
|
||||
Fuse.beforeMount = beforeMount
|
||||
Fuse.beforeUnmount = beforeUnmount
|
||||
Fuse.configure = configure
|
||||
Fuse.unconfigure = unconfigure
|
||||
Fuse.isConfigured = isConfigured
|
||||
|
||||
module.exports = Fuse
|
||||
|
||||
function getStatfsArray (statfs) {
|
||||
const ints = new Uint32Array(11)
|
||||
|
||||
ints[0] = (statfs && statfs.bsize) || 0
|
||||
ints[1] = (statfs && statfs.frsize) || 0
|
||||
ints[2] = (statfs && statfs.blocks) || 0
|
||||
ints[3] = (statfs && statfs.bfree) || 0
|
||||
ints[4] = (statfs && statfs.bavail) || 0
|
||||
ints[5] = (statfs && statfs.files) || 0
|
||||
ints[6] = (statfs && statfs.ffree) || 0
|
||||
ints[7] = (statfs && statfs.favail) || 0
|
||||
ints[8] = (statfs && statfs.fsid) || 0
|
||||
ints[9] = (statfs && statfs.flag) || 0
|
||||
ints[10] = (statfs && statfs.namemax) || 0
|
||||
|
||||
return ints
|
||||
}
|
||||
|
||||
function setDoubleInt (arr, idx, num) {
|
||||
arr[idx] = num % 4294967296
|
||||
arr[idx + 1] = (num - arr[idx]) / 4294967296
|
||||
}
|
||||
|
||||
function getDoubleArg (a, b) {
|
||||
return a + b * 4294967296
|
||||
}
|
||||
|
||||
function toDateMS (st) {
|
||||
if (typeof st === 'number') return st
|
||||
if (!st) return Date.now()
|
||||
return st.getTime()
|
||||
}
|
||||
|
||||
function getStatArray (stat) {
|
||||
const ints = new Uint32Array(18)
|
||||
|
||||
ints[0] = (stat && stat.mode) || 0
|
||||
ints[1] = (stat && stat.uid) || 0
|
||||
ints[2] = (stat && stat.gid) || 0
|
||||
setDoubleInt(ints, 3, (stat && stat.size) || 0)
|
||||
ints[5] = (stat && stat.dev) || 0
|
||||
ints[6] = (stat && stat.nlink) || 1
|
||||
ints[7] = (stat && stat.ino) || 0
|
||||
ints[8] = (stat && stat.rdev) || 0
|
||||
ints[9] = (stat && stat.blksize) || 0
|
||||
setDoubleInt(ints, 10, (stat && stat.blocks) || 0)
|
||||
setDoubleInt(ints, 12, toDateMS(stat && stat.atime))
|
||||
setDoubleInt(ints, 14, toDateMS(stat && stat.mtime))
|
||||
setDoubleInt(ints, 16, toDateMS(stat && stat.ctime))
|
||||
|
||||
return ints
|
||||
}
|
||||
|
30
package.json
30
package.json
@ -1,27 +1,39 @@
|
||||
{
|
||||
"name": "fuse-bindings",
|
||||
"version": "1.1.0",
|
||||
"name": "fuse-native",
|
||||
"version": "2.2.6",
|
||||
"description": "Fully maintained fuse bindings for Node that aims to cover the entire FUSE api",
|
||||
"main": "index.js",
|
||||
"bin": {
|
||||
"fuse-native": "./bin.js"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "standard"
|
||||
"install": "node-gyp-build",
|
||||
"test": "tape test/*.js",
|
||||
"prebuild": "prebuildify --napi --strip",
|
||||
"prebuild-ia32": "prebuildify --napi --strip --arch=ia32",
|
||||
"configure": "NODE=$(which node) && sudo -E $NODE ./bin.js configure || true"
|
||||
},
|
||||
"gypfile": true,
|
||||
"dependencies": {
|
||||
"bindings": "^1.2.1",
|
||||
"nan": "^1.7.0"
|
||||
"fuse-shared-library": "^1.0.2",
|
||||
"nanoresource": "^1.3.0",
|
||||
"napi-macros": "^2.0.0",
|
||||
"node-gyp-build": "^4.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"standard": "^3.0.0-beta3"
|
||||
"concat-stream": "^2.0.0",
|
||||
"prebuildify": "^3.0.4",
|
||||
"standard": "^13.1.0",
|
||||
"tape": "^4.12.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mafintosh/fuse-bindings.git"
|
||||
"url": "https://github.com/fuse-friends/fuse-native.git"
|
||||
},
|
||||
"author": "Mathias Buus (@mafintosh)",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/mafintosh/fuse-bindings/issues"
|
||||
"url": "https://github.com/fuse-friends/fuse-native/issues"
|
||||
},
|
||||
"homepage": "https://github.com/mafintosh/fuse-bindings"
|
||||
"homepage": "https://github.com/fuse-friends/fuse-native"
|
||||
}
|
||||
|
40
semaphore.h
Normal file
40
semaphore.h
Normal file
@ -0,0 +1,40 @@
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
#include <semaphore.h>
|
||||
#include <dispatch/dispatch.h>
|
||||
|
||||
typedef dispatch_semaphore_t fuse_native_semaphore_t;
|
||||
|
||||
static int fuse_native_semaphore_init (dispatch_semaphore_t *sem) {
|
||||
*sem = dispatch_semaphore_create(0);
|
||||
return *sem == NULL ? -1 : 0;
|
||||
}
|
||||
|
||||
static void fuse_native_semaphore_wait (dispatch_semaphore_t *sem) {
|
||||
dispatch_semaphore_wait(*sem, DISPATCH_TIME_FOREVER);
|
||||
}
|
||||
|
||||
static void fuse_native_semaphore_signal (dispatch_semaphore_t *sem) {
|
||||
dispatch_semaphore_signal(*sem);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <semaphore.h>
|
||||
|
||||
typedef sem_t fuse_native_semaphore_t;
|
||||
|
||||
static int fuse_native_semaphore_init (sem_t *sem) {
|
||||
return sem_init(sem, 0, 0);
|
||||
}
|
||||
|
||||
static void fuse_native_semaphore_wait (sem_t *sem) {
|
||||
sem_wait(sem);
|
||||
}
|
||||
|
||||
static void fuse_native_semaphore_signal (sem_t *sem) {
|
||||
sem_post(sem);
|
||||
}
|
||||
|
||||
#endif
|
116
test/big.js
Normal file
116
test/big.js
Normal file
@ -0,0 +1,116 @@
|
||||
const tape = require('tape')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const concat = require('concat-stream')
|
||||
|
||||
const Fuse = require('../')
|
||||
const createMountpoint = require('./fixtures/mnt')
|
||||
const stat = require('./fixtures/stat')
|
||||
const { unmount } = require('./helpers')
|
||||
|
||||
const mnt = createMountpoint()
|
||||
|
||||
tape('read and write big file', function (t) {
|
||||
let size = 0
|
||||
const reads = [0, 4 * 1024 * 1024 * 1024, 6 * 1024 * 1024 * 1024]
|
||||
const writes = [0, 4 * 1024 * 1024 * 1024, 6 * 1024 * 1024 * 1024]
|
||||
|
||||
var ops = {
|
||||
force: true,
|
||||
readdir (path, cb) {
|
||||
if (path === '/') return process.nextTick(cb, null, ['test'])
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
},
|
||||
getattr (path, cb) {
|
||||
if (path === '/') return process.nextTick(cb, null, stat({ mode: 'dir', size: 4096 }))
|
||||
if (path === '/test') return process.nextTick(cb, null, stat({ mode: 'file', size, mtime: new Date() }))
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
},
|
||||
open (path, flags, cb) {
|
||||
return process.nextTick(cb, 0, 42)
|
||||
},
|
||||
release (path, fd, cb) {
|
||||
t.same(fd, 42, 'fd was passed to release')
|
||||
return process.nextTick(cb, 0)
|
||||
},
|
||||
read (path, fd, buf, len, pos, cb) {
|
||||
t.same(pos, reads.shift(), 'read is expected')
|
||||
buf.fill(0)
|
||||
if (pos + len > size) return cb(Math.max(size - pos, 0))
|
||||
cb(len)
|
||||
},
|
||||
ftruncate (path, fd, len, cb) {
|
||||
size = len
|
||||
cb(0)
|
||||
},
|
||||
truncate (path, len, cb) {
|
||||
size = len
|
||||
cb(0)
|
||||
},
|
||||
write (path, fd, buf, len, pos, cb) {
|
||||
if (!writes.length) return cb(-1)
|
||||
t.same(pos, writes.shift(), 'write is expected')
|
||||
size = Math.max(pos + len, size)
|
||||
cb(len)
|
||||
}
|
||||
}
|
||||
|
||||
const fuse = new Fuse(mnt, ops, { debug: !true, autoCache: true })
|
||||
let fd = 0
|
||||
|
||||
run(
|
||||
(_, cb) => fuse.mount(cb),
|
||||
open('w+'),
|
||||
(_, cb) => fs.fstat(fd, cb),
|
||||
checkSize(0),
|
||||
(_, cb) => fs.ftruncate(fd, 4 * 1024 * 1024 * 1024 + 1, cb),
|
||||
(_, cb) => fs.fstat(fd, cb),
|
||||
checkSize(4 * 1024 * 1024 * 1024 + 1),
|
||||
(_, cb) => fs.truncate(path.join(mnt, 'test'), 6 * 1024 * 1024 * 1024 + 2, cb),
|
||||
(_, cb) => fs.fstat(fd, cb),
|
||||
checkSize(6 * 1024 * 1024 * 1024 + 2),
|
||||
(_, cb) => fs.write(fd, Buffer.alloc(4096), 0, 4096, 0, cb),
|
||||
(_, cb) => fs.write(fd, Buffer.alloc(4096), 0, 4096, 4 * 1024 * 1024 * 1024, cb),
|
||||
(_, cb) => fs.write(fd, Buffer.alloc(4096), 0, 4096, 6 * 1024 * 1024 * 1024, cb),
|
||||
(_, cb) => fs.fstat(fd, cb),
|
||||
checkSize(6 * 1024 * 1024 * 1024 + 4096),
|
||||
(_, cb) => fs.close(fd, cb),
|
||||
open('a+'),
|
||||
(_, cb) => fs.read(fd, Buffer.alloc(4096), 0, 4096, 0, cb),
|
||||
(_, cb) => fs.read(fd, Buffer.alloc(4096), 0, 4096, 4 * 1024 * 1024 * 1024, cb),
|
||||
(_, cb) => fs.read(fd, Buffer.alloc(4096), 0, 4096, 6 * 1024 * 1024 * 1024, cb),
|
||||
(_, cb) => fs.close(fd, cb),
|
||||
(_, cb) => unmount(fuse, cb),
|
||||
() => {
|
||||
t.same(writes.length, 0)
|
||||
t.same(reads.length, 0)
|
||||
t.end()
|
||||
}
|
||||
)
|
||||
|
||||
function open (mode) {
|
||||
return (_, cb) => {
|
||||
fs.open(path.join(mnt, 'test'), mode, function (_, res) {
|
||||
fd = res
|
||||
cb()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function checkSize (n) {
|
||||
return ({ size}, cb) => {
|
||||
t.same(size, n)
|
||||
cb()
|
||||
}
|
||||
}
|
||||
|
||||
function run (...fns) {
|
||||
const all = [...fns]
|
||||
tick()
|
||||
function tick (err, val) {
|
||||
t.error(err, 'no error')
|
||||
const next = all.shift()
|
||||
if (next) next(val, tick)
|
||||
}
|
||||
}
|
||||
})
|
19
test/fixtures/mnt.js
vendored
Normal file
19
test/fixtures/mnt.js
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
var os = require('os')
|
||||
var path = require('path')
|
||||
var fs = require('fs')
|
||||
|
||||
function create (opts = {}) {
|
||||
var mnt = path.join(os.tmpdir(), 'fuse-bindings-' + process.pid + '-' + Date.now())
|
||||
|
||||
if (!opts.doNotCreate) {
|
||||
try {
|
||||
fs.mkdirSync(mnt)
|
||||
} catch (err) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
return mnt
|
||||
}
|
||||
|
||||
module.exports = create
|
34
test/fixtures/simple-fs.js
vendored
Normal file
34
test/fixtures/simple-fs.js
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
const stat = require('./stat')
|
||||
const Fuse = require('../../')
|
||||
|
||||
module.exports = function (tests = {}) {
|
||||
return {
|
||||
readdir: function (path, cb) {
|
||||
if (tests.readdir) tests.readdir(path)
|
||||
if (path === '/') return process.nextTick(cb, null, ['test'])
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
},
|
||||
getattr: function (path, cb) {
|
||||
if (tests.getattr) tests.getattr(path)
|
||||
if (path === '/') return process.nextTick(cb, null, stat({ mode: 'dir', size: 4096 }))
|
||||
if (path === '/test') return process.nextTick(cb, null, stat({ mode: 'file', size: 11 }))
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
},
|
||||
open: function (path, flags, cb) {
|
||||
if (tests.open) tests.open(path, flags)
|
||||
return process.nextTick(cb, 0, 42)
|
||||
},
|
||||
release: function (path, fd, cb) {
|
||||
if (tests.release) tests.release(path, fd)
|
||||
return process.nextTick(cb, 0)
|
||||
},
|
||||
read: function (path, fd, buf, len, pos, cb) {
|
||||
if (tests.read) tests.read(path, fd, buf, len, pos)
|
||||
var str = 'hello world'.slice(pos, pos + len)
|
||||
if (!str) return process.nextTick(cb, 0)
|
||||
buf.write(str)
|
||||
return process.nextTick(cb, str.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
11
test/fixtures/stat.js
vendored
Normal file
11
test/fixtures/stat.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
module.exports = function (st) {
|
||||
return {
|
||||
mtime: st.mtime || new Date(),
|
||||
atime: st.atime || new Date(),
|
||||
ctime: st.ctime || new Date(),
|
||||
size: st.size !== undefined ? st.size : 0,
|
||||
mode: st.mode === 'dir' ? 16877 : (st.mode === 'file' ? 33188 : (st.mode === 'link' ? 41453 : st.mode)),
|
||||
uid: st.uid !== undefined ? st.uid : process.getuid(),
|
||||
gid: st.gid !== undefined ? st.gid : process.getgid()
|
||||
}
|
||||
}
|
6
test/helpers/index.js
Normal file
6
test/helpers/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
exports.unmount = function (fuse, cb) { // This only seems to be nessesary an the ancient osx we use on travis so ... yolo
|
||||
fuse.unmount(function (err) {
|
||||
if (err) return cb(err)
|
||||
setTimeout(cb, 1000)
|
||||
})
|
||||
}
|
67
test/links.js
Normal file
67
test/links.js
Normal file
@ -0,0 +1,67 @@
|
||||
const tape = require('tape')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { unmount } = require('./helpers')
|
||||
|
||||
const Fuse = require('../')
|
||||
const createMountpoint = require('./fixtures/mnt')
|
||||
const stat = require('./fixtures/stat')
|
||||
|
||||
const mnt = createMountpoint()
|
||||
|
||||
tape('readlink', function (t) {
|
||||
var ops = {
|
||||
force: true,
|
||||
readdir: function (path, cb) {
|
||||
if (path === '/') return process.nextTick(cb, null, ['hello', 'link'])
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
},
|
||||
readlink: function (path, cb) {
|
||||
process.nextTick(cb, 0, 'hello')
|
||||
},
|
||||
getattr: function (path, cb) {
|
||||
if (path === '/') return process.nextTick(cb, null, stat({ mode: 'dir', size: 4096 }))
|
||||
if (path === '/hello') return process.nextTick(cb, null, stat({ mode: 'file', size: 11 }))
|
||||
if (path === '/link') return process.nextTick(cb, null, stat({ mode: 'link', size: 5 }))
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
},
|
||||
open: function (path, flags, cb) {
|
||||
process.nextTick(cb, 0, 42)
|
||||
},
|
||||
read: function (path, fd, buf, len, pos, cb) {
|
||||
var str = 'hello world'.slice(pos, pos + len)
|
||||
if (!str) return process.nextTick(cb, 0)
|
||||
buf.write(str)
|
||||
return process.nextTick(cb, str.length)
|
||||
}
|
||||
}
|
||||
|
||||
const fuse = new Fuse(mnt, ops, { debug: true })
|
||||
fuse.mount(function (err) {
|
||||
t.error(err, 'no error')
|
||||
|
||||
fs.lstat(path.join(mnt, 'link'), function (err, stat) {
|
||||
t.error(err, 'no error')
|
||||
t.same(stat.size, 5, 'correct size')
|
||||
|
||||
fs.stat(path.join(mnt, 'hello'), function (err, stat) {
|
||||
t.error(err, 'no error')
|
||||
t.same(stat.size, 11, 'correct size')
|
||||
|
||||
fs.readlink(path.join(mnt, 'link'), function (err, dest) {
|
||||
t.error(err, 'no error')
|
||||
t.same(dest, 'hello', 'link resolves')
|
||||
|
||||
fs.readFile(path.join(mnt, 'link'), function (err, buf) {
|
||||
t.error(err, 'no error')
|
||||
t.same(buf, Buffer.from('hello world'), 'can read link content')
|
||||
|
||||
unmount(fuse, function () {
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
208
test/misc.js
Normal file
208
test/misc.js
Normal file
@ -0,0 +1,208 @@
|
||||
const os = require('os')
|
||||
const fs = require('fs')
|
||||
const tape = require('tape')
|
||||
const { spawnSync, exec } = require('child_process')
|
||||
|
||||
const createMountpoint = require('./fixtures/mnt')
|
||||
|
||||
const Fuse = require('../')
|
||||
const { unmount } = require('./helpers')
|
||||
const simpleFS = require('./fixtures/simple-fs')
|
||||
|
||||
const mnt = createMountpoint()
|
||||
|
||||
tape('mount', function (t) {
|
||||
const fuse = new Fuse(mnt, {}, { force: true })
|
||||
fuse.mount(function (err) {
|
||||
t.error(err, 'no error')
|
||||
t.ok(true, 'works')
|
||||
unmount(fuse, function () {
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
tape('mount + unmount + mount', function (t) {
|
||||
const fuse1 = new Fuse(mnt, {}, { force: true, debug: false })
|
||||
const fuse2 = new Fuse(mnt, {}, { force: true, debug: false })
|
||||
|
||||
fuse1.mount(function (err) {
|
||||
t.error(err, 'no error')
|
||||
t.ok(true, 'works')
|
||||
unmount(fuse1, function () {
|
||||
fuse2.mount(function (err) {
|
||||
t.error(err, 'no error')
|
||||
t.ok(true, 'works')
|
||||
unmount(fuse2, function () {
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
tape('mount + unmount + mount with same instance fails', function (t) {
|
||||
const fuse = new Fuse(mnt, {}, { force: true, debug: false })
|
||||
|
||||
fuse.mount(function (err) {
|
||||
t.error(err, 'no error')
|
||||
t.pass('works')
|
||||
unmount(fuse, function () {
|
||||
fuse.mount(function (err) {
|
||||
t.ok(err, 'had error')
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
tape('mnt point must exist', function (t) {
|
||||
const fuse = new Fuse('.does-not-exist', {}, { debug: false })
|
||||
fuse.mount(function (err) {
|
||||
t.ok(err, 'had error')
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
|
||||
tape('mnt point must be directory', function (t) {
|
||||
const fuse = new Fuse(__filename, {}, { debug: false })
|
||||
fuse.mount(function (err) {
|
||||
t.ok(err, 'had error')
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
|
||||
tape('mounting twice without force fails', function (t) {
|
||||
const fuse1 = new Fuse(mnt, {}, { force: true, debug: false })
|
||||
const fuse2 = new Fuse(mnt, {}, { force: false, debug: false })
|
||||
|
||||
fuse1.mount(function (err) {
|
||||
t.error(err, 'no error')
|
||||
t.pass('works')
|
||||
fuse2.mount(function (err) {
|
||||
t.true(err, 'cannot mount over existing mountpoint')
|
||||
unmount(fuse1, function () {
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
tape('mounting twice with force fail if mountpoint is not broken', function (t) {
|
||||
const fuse1 = new Fuse(mnt, {}, { force: true, debug: false })
|
||||
const fuse2 = new Fuse(mnt, {}, { force: true, debug: false })
|
||||
|
||||
fuse1.mount(function (err) {
|
||||
t.error(err, 'no error')
|
||||
t.pass('works')
|
||||
fuse2.mount(function (err) {
|
||||
t.true(err, 'cannot mount over existing mountpoint')
|
||||
unmount(fuse1, function () {
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
tape('mounting over a broken mountpoint with force succeeds', function (t) {
|
||||
createBrokenMountpoint(mnt)
|
||||
|
||||
const fuse = new Fuse(mnt, {}, { force: true, debug: false })
|
||||
fuse.mount(function (err) {
|
||||
t.error(err, 'no error')
|
||||
t.pass('works')
|
||||
unmount(fuse, function (err) {
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
tape('mounting without mkdir option and a nonexistent mountpoint fails', function (t) {
|
||||
const nonexistentMnt = createMountpoint({ doNotCreate: true })
|
||||
|
||||
const fuse = new Fuse(nonexistentMnt, {}, { debug: false })
|
||||
fuse.mount(function (err) {
|
||||
t.true(err, 'could not mount')
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
|
||||
tape('mounting with mkdir option and a nonexistent mountpoint succeeds', function (t) {
|
||||
const nonexistentMnt = createMountpoint({ doNotCreate: true })
|
||||
|
||||
const fuse = new Fuse(nonexistentMnt, {}, { debug: false, mkdir: true })
|
||||
fuse.mount(function (err) {
|
||||
t.error(err, 'no error')
|
||||
unmount(fuse, function (err) {
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
tape('(osx only) unmount with Finder open succeeds', function (t) {
|
||||
if (os.platform() !== 'darwin') return t.end()
|
||||
const fuse = new Fuse(mnt, simpleFS(), { force: true, debug: false })
|
||||
fuse.mount(function (err) {
|
||||
t.error(err, 'no error')
|
||||
exec(`open ${mnt}`, err => {
|
||||
t.error(err, 'no error')
|
||||
setTimeout(() => {
|
||||
fs.readdir(mnt, (err, list) => {
|
||||
t.error(err, 'no error')
|
||||
t.same(list, ['test'])
|
||||
unmount(fuse, err => {
|
||||
t.error(err, 'no error')
|
||||
fs.readdir(mnt, (err, list) => {
|
||||
t.error(err, 'no error')
|
||||
t.same(list, [])
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
}, 1000)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
tape('(osx only) unmount with Terminal open succeeds', function (t) {
|
||||
if (os.platform() !== 'darwin') return t.end()
|
||||
const fuse = new Fuse(mnt, simpleFS(), { force: true, debug: false })
|
||||
fuse.mount(function (err) {
|
||||
t.error(err, 'no error')
|
||||
exec(`open -a Terminal ${mnt}`, err => {
|
||||
t.error(err, 'no error')
|
||||
setTimeout(() => {
|
||||
fs.readdir(mnt, (err, list) => {
|
||||
t.error(err, 'no error')
|
||||
t.same(list, ['test'])
|
||||
unmount(fuse, err => {
|
||||
t.error(err, 'no error')
|
||||
fs.readdir(mnt, (err, list) => {
|
||||
t.error(err, 'no error')
|
||||
t.same(list, [])
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
}, 1000)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
tape('static unmounting', function (t) {
|
||||
t.end()
|
||||
})
|
||||
|
||||
function createBrokenMountpoint (mnt) {
|
||||
spawnSync(process.execPath, ['-e', `
|
||||
const Fuse = require('..')
|
||||
const mnt = ${JSON.stringify(mnt)}
|
||||
const fuse = new Fuse(mnt, {}, { force: true, debug: false })
|
||||
fuse.mount(() => {
|
||||
process.exit(0)
|
||||
})
|
||||
`], {
|
||||
cwd: __dirname,
|
||||
stdio: 'inherit'
|
||||
})
|
||||
}
|
116
test/read.js
Normal file
116
test/read.js
Normal file
@ -0,0 +1,116 @@
|
||||
const tape = require('tape')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const concat = require('concat-stream')
|
||||
|
||||
const Fuse = require('../')
|
||||
const createMountpoint = require('./fixtures/mnt')
|
||||
const stat = require('./fixtures/stat')
|
||||
const simpleFS = require('./fixtures/simple-fs')
|
||||
|
||||
const { unmount } = require('./helpers')
|
||||
const mnt = createMountpoint()
|
||||
|
||||
tape('read', function (t) {
|
||||
const testFS = simpleFS({
|
||||
release: function (path, fd) {
|
||||
t.same(fd, 42, 'fd was passed to release')
|
||||
}
|
||||
})
|
||||
const fuse = new Fuse(mnt, testFS, { debug: true })
|
||||
fuse.mount(function (err) {
|
||||
t.error(err, 'no error')
|
||||
|
||||
fs.readFile(path.join(mnt, 'test'), function (err, buf) {
|
||||
t.error(err, 'no error')
|
||||
t.same(buf, Buffer.from('hello world'), 'read file')
|
||||
|
||||
fs.readFile(path.join(mnt, 'test'), function (err, buf) {
|
||||
t.error(err, 'no error')
|
||||
t.same(buf, Buffer.from('hello world'), 'read file again')
|
||||
|
||||
fs.createReadStream(path.join(mnt, 'test'), { start: 0, end: 4 }).pipe(concat(function (buf) {
|
||||
t.same(buf, Buffer.from('hello'), 'partial read file')
|
||||
|
||||
fs.createReadStream(path.join(mnt, 'test'), { start: 6, end: 10 }).pipe(concat(function (buf) {
|
||||
t.same(buf, Buffer.from('world'), 'partial read file + start offset')
|
||||
|
||||
unmount(fuse, function () {
|
||||
t.end()
|
||||
})
|
||||
}))
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Skipped because this test takes 2 minutes to run.
|
||||
tape.skip('read timeout does not force unmount', function (t) {
|
||||
var ops = {
|
||||
force: true,
|
||||
readdir: function (path, cb) {
|
||||
if (path === '/') return process.nextTick(cb, null, ['test'])
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
},
|
||||
getattr: function (path, cb) {
|
||||
if (path === '/') return process.nextTick(cb, null, stat({ mode: 'dir', size: 4096 }))
|
||||
if (path === '/test') return process.nextTick(cb, null, stat({ mode: 'file', size: 11 }))
|
||||
if (path === '/timeout') return process.nextTick(cb, null, stat({ mode: 'file', size: 11 }))
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
},
|
||||
open: function (path, flags, cb) {
|
||||
return process.nextTick(cb, 0, 42)
|
||||
},
|
||||
release: function (path, fd, cb) {
|
||||
t.same(fd, 42, 'fd was passed to release')
|
||||
return process.nextTick(cb, 0)
|
||||
},
|
||||
read: function (path, fd, buf, len, pos, cb) {
|
||||
if (path === '/test') {
|
||||
var str = 'hello world'.slice(pos, pos + len)
|
||||
if (!str) return process.nextTick(cb, 0)
|
||||
buf.write(str)
|
||||
return process.nextTick(cb, str.length)
|
||||
} else if (path === '/timeout') {
|
||||
console.log('read is gonna time out')
|
||||
// Just let this one timeout
|
||||
setTimeout(cb, 20 * 1000, -2)
|
||||
return
|
||||
}
|
||||
return cb(-2)
|
||||
}
|
||||
}
|
||||
|
||||
const fuse = new Fuse(mnt, ops, { debug: false })
|
||||
fuse.mount(function (err) {
|
||||
t.error(err, 'no error')
|
||||
|
||||
fs.readFile(path.join(mnt, 'test'), function (err, buf) {
|
||||
t.error(err, 'no error')
|
||||
t.same(buf, Buffer.from('hello world'), 'read file')
|
||||
|
||||
// Start the read that will time out, wait a bit, then ensure that the second read works.
|
||||
console.time('timeout')
|
||||
fs.readFile(path.join(mnt, 'timeout'), function (err, buf) {
|
||||
console.timeEnd('timeout')
|
||||
console.log('the read timed out')
|
||||
t.true(err)
|
||||
})
|
||||
|
||||
// The default FUSE timeout is 2 minutes, so wait another second after the timeout.
|
||||
setTimeout(function () {
|
||||
console.log('reading from test')
|
||||
fs.readFile(path.join(mnt, 'test'), function (err, buf) {
|
||||
t.error(err, 'no error')
|
||||
t.same(buf, Buffer.from('hello world'), 'read file')
|
||||
unmount(fuse, function () {
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
}, 1000 * 121)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
40
test/statfs.js
Normal file
40
test/statfs.js
Normal file
@ -0,0 +1,40 @@
|
||||
const { exec } = require('child_process')
|
||||
const { unmount } = require('./helpers')
|
||||
const tape = require('tape')
|
||||
|
||||
const Fuse = require('../')
|
||||
const createMountpoint = require('./fixtures/mnt')
|
||||
const stat = require('./fixtures/stat')
|
||||
|
||||
const mnt = createMountpoint()
|
||||
|
||||
tape('statfs', function (t) {
|
||||
const ops = {
|
||||
force: true,
|
||||
statfs: function (path, cb) {
|
||||
return cb(0, {
|
||||
bsize: 1000000,
|
||||
frsize: 1000000,
|
||||
blocks: 1000000,
|
||||
bfree: 1000000,
|
||||
bavail: 1000000,
|
||||
files: 1000000,
|
||||
ffree: 1000000,
|
||||
favail: 1000000,
|
||||
fsid: 1000000,
|
||||
flag: 1000000,
|
||||
namemax: 1000000
|
||||
})
|
||||
},
|
||||
}
|
||||
const fuse = new Fuse(mnt, ops, { debug: true })
|
||||
fuse.mount(function (err) {
|
||||
t.error(err, 'no error')
|
||||
exec(`df ${mnt}`, (err) => {
|
||||
t.error(err, 'no error')
|
||||
unmount(fuse, function () {
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
59
test/write.js
Normal file
59
test/write.js
Normal file
@ -0,0 +1,59 @@
|
||||
const tape = require('tape')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const Fuse = require('../')
|
||||
const createMountpoint = require('./fixtures/mnt')
|
||||
const stat = require('./fixtures/stat')
|
||||
const { unmount } = require('./helpers')
|
||||
|
||||
const mnt = createMountpoint()
|
||||
|
||||
tape('write', function (t) {
|
||||
var created = false
|
||||
var data = Buffer.alloc(1024)
|
||||
var size = 0
|
||||
|
||||
var ops = {
|
||||
force: true,
|
||||
readdir: function (path, cb) {
|
||||
if (path === '/') return process.nextTick(cb, null, created ? ['hello'] : [], [])
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
},
|
||||
truncate: function (path, size, cb) {
|
||||
process.nextTick(cb, 0)
|
||||
},
|
||||
getattr: function (path, cb) {
|
||||
if (path === '/') return process.nextTick(cb, null, stat({ mode: 'dir', size: 4096 }))
|
||||
if (path === '/hello' && created) return process.nextTick(cb, 0, stat({ mode: 'file', size: size }))
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
},
|
||||
create: function (path, flags, cb) {
|
||||
t.ok(!created, 'file not created yet')
|
||||
created = true
|
||||
process.nextTick(cb, 0, 42)
|
||||
},
|
||||
release: function (path, fd, cb) {
|
||||
process.nextTick(cb, 0)
|
||||
},
|
||||
write: function (path, fd, buf, len, pos, cb) {
|
||||
buf.slice(0, len).copy(data, pos)
|
||||
size = Math.max(pos + len, size)
|
||||
process.nextTick(cb, buf.length)
|
||||
}
|
||||
}
|
||||
|
||||
const fuse = new Fuse(mnt, ops, { debug: true })
|
||||
fuse.mount(function (err) {
|
||||
t.error(err, 'no error')
|
||||
|
||||
fs.writeFile(path.join(mnt, 'hello'), 'hello world', function (err) {
|
||||
t.error(err, 'no error')
|
||||
t.same(data.slice(0, size), Buffer.from('hello world'), 'data was written')
|
||||
|
||||
unmount(fuse, function () {
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue
Block a user