1
0
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.

22 changed files with 2996 additions and 1402 deletions

1
.gitignore vendored
View File

@ -1,3 +1,2 @@
node_modules
.nadconfig.mk
build

51
.travis.yml Normal file
View 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
View File

@ -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.

537
README.md
View File

@ -1,36 +1,139 @@
# fuse-bindings
# fuse-native
[![Build Status](https://travis-ci.org/fuse-friends/fuse-native.svg?branch=master)](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
``` js
var fuse = require('fuse-bindings')
fuse.mount('./mnt', {
## Example
```js
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
View 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
}

View File

@ -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')\")" ],
}]
}]
}

View File

@ -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')
}
})
})

View File

@ -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
View 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)
}

963
index.js
View File

@ -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
}
_getImplementedArray () {
const implemented = new Uint32Array(35)
for (const impl of this._implemented) {
implemented[impl] = 1
}
return implemented
}
_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)
}
return options.length ? '-o' + options.join(',') : ''
}
_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
}
}
FuseBuffer.prototype = Buffer.prototype
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
fuse.setBuffer(FuseBuffer)
// Forward configuration functions through the exported class.
Fuse.beforeMount = beforeMount
Fuse.beforeUnmount = beforeUnmount
Fuse.configure = configure
Fuse.unconfigure = unconfigure
Fuse.isConfigured = isConfigured
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)
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
}
exports.unmount = function (mnt, cb) {
fuse.unmount(path.resolve(mnt), cb || noop)
function setDoubleInt (arr, idx, num) {
arr[idx] = num % 4294967296
arr[idx + 1] = (num - arr[idx]) / 4294967296
}
exports.unmountSync = function (mnt) {
return fuse.unmountSync(path.resolve(mnt))
function getDoubleArg (a, b) {
return a + b * 4294967296
}
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
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
}

View File

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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()
})
})
})
})