mirror of
https://github.com/fuse-friends/fuse-native
synced 2024-10-27 18:34:01 +00:00
Compare commits
No commits in common. "v1.1.1" and "master" have entirely different histories.
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,5 +1,2 @@
|
||||
node_modules
|
||||
.nadconfig.mk
|
||||
build
|
||||
deps/
|
||||
npm-debug.log
|
||||
|
51
.travis.yml
Normal file
51
.travis.yml
Normal file
@ -0,0 +1,51 @@
|
||||
language: node_js
|
||||
|
||||
sudo: required
|
||||
|
||||
osx_image: xcode8.3
|
||||
|
||||
node_js:
|
||||
- node
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
||||
- gcc-4.8-multilib
|
||||
- g++-4.8-multilib
|
||||
- gcc-multilib
|
||||
- g++-multilib
|
||||
|
||||
os:
|
||||
- osx
|
||||
- linux
|
||||
|
||||
before_install:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then cinst -y python2; fi
|
||||
|
||||
install:
|
||||
- npm install
|
||||
- npm run configure
|
||||
|
||||
before_deploy:
|
||||
- ARCHIVE_NAME="${TRAVIS_TAG:-latest}-$TRAVIS_OS_NAME.tar"
|
||||
- npm run prebuild
|
||||
- if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then PREBUILD_ARCH=ia32 npm run prebuild; fi
|
||||
- cd prebuilds && tar cvf "../$ARCHIVE_NAME" . && cd ..
|
||||
|
||||
cache:
|
||||
npm: false
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
draft: false
|
||||
prerelease: true
|
||||
api_key:
|
||||
secure: "KPn3xR4LWcg/H259aSZh26XX0eapR88xSNUkBmEri/sCJSyZ0+asLZSv/HDD3KJP4HeuIKsQc0v8fcebD83fkvaSvlUzSppMQgniwuGC1cAefbrgZDwmJJ/n+lE8Wr9x4adOBTgICS5Uc8LlZ1PuJGm4mmequVs29BEw9738LzN4+3NpoCoWd0FAgGF0tDTsaYL1tJqERAyNIxHS+adUPe0F2r0d2UJ7mOrW7s8Ai6e6QryFsFvA2m0Xn/pQmNO/mcq+LPcw57pWuI3Hm3Cu3W8VPJXYp/yJaFqTAn3D9Fwz4tkmbfmca4ETwZYOS3lvL/rjLQ+69SJlRRu/QfPECkZven+wwsLX/DmyGHgEWqeGWjKj/NxYOIKUKEZZCVrF8cy4j9mac+LK6bAeDZERKSxUJ9GT5WsjvV3RNKgp3MZF7mtmj4IWXfgcuwFX49oIqhzSJsucBBXlB74J7Qua5VJPEAo/7X7Q+Y9IT9JHwxXsXVF5ZNj1PMlJicVD6oKi4XCFOVxSE9wdzlBwMOlUyBGhAIzS6lmxHOELYO9C7l8t/8Zvi4a+YGvOwn0dzLb9zuA1bzqJmEB1fkQMZXHvcEY1o5jSTQ0cNn1Wx4Ck9zyLyhnZ5KRXKzGQ1du55iVOThcbl/8j6zT218SiZMMtv8ZwPy4pJt4skMGsoOZtYlE="
|
||||
file: "$ARCHIVE_NAME"
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
node: 'node'
|
21
LICENSE
21
LICENSE
@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Mathias Buus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
344
README.md
344
README.md
@ -1,147 +1,101 @@
|
||||
# fuse-bindings
|
||||
# fuse-native
|
||||
[](https://travis-ci.org/fuse-friends/fuse-native)
|
||||
|
||||
Fully maintained fuse bindings for Node that aims to cover the entire FUSE api
|
||||
Multithreaded FUSE bindings for Node JS.
|
||||
|
||||
## Features
|
||||
|
||||
* N-API support means we ship prebuilds and in general works on new Node.js releases.
|
||||
* Multithreading support means multiple calls to FUSE can run in parallel.
|
||||
* Close to feature complete in terms of the the FUSE API.
|
||||
* Embedded shared library support means users do not have to install FUSE from a 3rd party.
|
||||
* API support for initial FUSE kernel extension configuration so you can control the user experience.
|
||||
|
||||
## Installation
|
||||
```
|
||||
npm install fuse-bindings
|
||||
npm i fuse-native --save
|
||||
```
|
||||
|
||||
Compared to [fuse4js](https://github.com/bcle/fuse4js) these bindings cover almost the entire FUSE api (except for locking) and doesn't do
|
||||
any buffer copys in read/write. It also supports unmount and mouting of multiple fuse drives.
|
||||
|
||||
## Requirements
|
||||
|
||||
You need to have FUSE installed (or Dokany on Windows)
|
||||
|
||||
* On Linux/Ubuntu `sudo apt-get install libfuse-dev`
|
||||
* On OSX
|
||||
* if you use Brew, install [OSXFuse](http://osxfuse.github.com/) and `brew install pkg-config`
|
||||
* if you use MacPorts, `sudo port install osxfuse +devel`
|
||||
* On Windows install [Dokany](https://github.com/dokan-dev/dokany)
|
||||
|
||||
### Windows
|
||||
**WARNING**: Dokany is still not quite stable. It can cause BSODs. Be careful.
|
||||
|
||||
~~Using this on Windows is slightly more complicated. You need to install [Dokany](https://github.com/dokan-dev/dokany) (for `dokanfuse.lib`, `dokanctl.exe`, driver and service) **and** clone its repo (for the headers).~~
|
||||
|
||||
~~Once the Dokany repo is cloned, you also need to set environment variable `DOKAN_INSTALL_DIR` to the path to `DokenLibrary` of your Dokany installaton, and `DOKAN_FUSE_INCLUDE` to the path to `*dokany repo*\dokan_fuse\include`.~~
|
||||
|
||||
**EDIT**: Dokany now includes needed headers and sets proper environment variables when installing! Just install Dokany and this module should install and work just fine! (Drop an issue otherwise)
|
||||
|
||||
## Usage
|
||||
|
||||
Try creating an empty folder called `mnt` and run the below example
|
||||
|
||||
``` js
|
||||
var fuse = require('fuse-bindings')
|
||||
|
||||
var mountPath = process.platform !== 'win32' ? './mnt' : 'M:\\'
|
||||
|
||||
fuse.mount(mountPath, {
|
||||
## 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 === '/') {
|
||||
cb(0, {
|
||||
mtime: new Date(),
|
||||
atime: new Date(),
|
||||
ctime: new Date(),
|
||||
nlink: 1,
|
||||
size: 100,
|
||||
mode: 16877,
|
||||
uid: process.getuid ? process.getuid() : 0,
|
||||
gid: process.getgid ? process.getgid() : 0
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (path === '/test') {
|
||||
cb(0, {
|
||||
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
|
||||
}
|
||||
|
||||
cb(fuse.ENOENT)
|
||||
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) {
|
||||
console.log('open(%s, %d)', path, flags)
|
||||
cb(0, 42) // 42 is an fd
|
||||
return cb(0, 42)
|
||||
},
|
||||
release: function (path, fd, cb) {
|
||||
return cb(0)
|
||||
},
|
||||
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, pos + len)
|
||||
var str = 'hello world'.slice(pos, pos + len)
|
||||
if (!str) return cb(0)
|
||||
buf.write(str)
|
||||
return cb(str.length)
|
||||
}
|
||||
}, function (err) {
|
||||
if (err) throw err
|
||||
console.log('filesystem mounted on ' + mountPath)
|
||||
})
|
||||
}
|
||||
|
||||
process.on('SIGINT', function () {
|
||||
fuse.unmount(mountPath, function (err) {
|
||||
if (err) {
|
||||
console.log('filesystem at ' + mountPath + ' not unmounted', err)
|
||||
} else {
|
||||
console.log('filesystem at ' + mountPath + ' unmounted')
|
||||
}
|
||||
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'
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
[fs-fuse](https://github.com/piranna/fs-fuse) is a wrapper module build on top of `fuse-bindings` that allow you to export and mount any `fs`-like object as a FUSE filesystem.
|
||||
|
||||
## 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:
|
||||
|
||||
#### `fuse.mount(mnt, ops, [cb])`
|
||||
#### `const fuse = new Fuse(mnt, handlers, opts = {})`
|
||||
Create a new `Fuse` object.
|
||||
|
||||
Mount a new filesystem on `mnt`.
|
||||
Pass the FUSE operations you want to support as the `ops` argument.
|
||||
`mnt` is the string path of your desired mountpoint.
|
||||
|
||||
#### `fuse.unmount(mnt, [cb])`
|
||||
|
||||
Unmount a filesystem
|
||||
|
||||
#### `fuse.context()`
|
||||
|
||||
Returns the current fuse context (pid, uid, gid).
|
||||
Must be called inside a fuse callback.
|
||||
|
||||
## Mount options
|
||||
|
||||
#### `ops.options`
|
||||
|
||||
Set [mount options](http://blog.woralelandia.com/2012/07/16/fuse-mount-options/)
|
||||
|
||||
``` js
|
||||
ops.options = ['direct_io'] // set the direct_io option
|
||||
`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)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `ops.displayFolder`
|
||||
`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).
|
||||
|
||||
Set to `true` to make OSX display a folder icon and the folder name as the mount point in finder
|
||||
#### `Fuse.isConfigured(cb)`
|
||||
|
||||
#### `ops.force`
|
||||
Returns `true` if FUSE has been configured on your machine and ready to be used, `false` otherwise.
|
||||
|
||||
Set to `true` to force mount the filesystem (will do an unmount first)
|
||||
#### `Fuse.configure(cb)`
|
||||
|
||||
## FUSE operations
|
||||
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)`
|
||||
@ -248,21 +202,30 @@ Called when the mode of a path is being changed
|
||||
|
||||
Called when the a new device file is being made.
|
||||
|
||||
#### `ops.setxattr(path, name, buffer, length, offset, flags, cb)`
|
||||
#### `ops.setxattr(path, name, value, position, flags, cb)`
|
||||
|
||||
Called when extended attributes is being set (see the extended docs for your platform).
|
||||
Currently you can read the attribute value being set in `buffer` at `offset`.
|
||||
|
||||
#### `ops.getxattr(path, name, buffer, length, offset, cb)`
|
||||
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.
|
||||
Currently you have to write the result to the provided `buffer` at `offset`.
|
||||
|
||||
#### `ops.listxattr(path, buffer, length, cb)`
|
||||
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.
|
||||
`buffer` should be filled with the extended attribute names as *null-terminated* strings, one after the other, up to a total of `length` in length. (`ERANGE` should be passed to the callback if `length` is insufficient.)
|
||||
The size of buffer required to hold all the names should be passed to the callback either on success, or if the supplied `length` was zero.
|
||||
|
||||
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)`
|
||||
|
||||
@ -358,139 +321,20 @@ Called when a new directory is being created
|
||||
|
||||
Called when a directory is being removed
|
||||
|
||||
#### `ops.destroy(cb)`
|
||||
## CLI
|
||||
|
||||
Both `read` and `write` passes the underlying fuse buffer without copying them to be as fast as possible.
|
||||
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
|
||||
|
||||
## Error codes
|
||||
|
||||
The available error codes are exposes as well as properties. These include
|
||||
|
||||
* `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`
|
||||
```
|
||||
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.
|
||||
|
121
abstractions.cc
121
abstractions.cc
@ -1,121 +0,0 @@
|
||||
#include "abstractions.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
int execute_command_and_wait (char* argv[]) {
|
||||
// Fork our running process.
|
||||
pid_t cpid = vfork();
|
||||
|
||||
// Check if we are the observer or the new process.
|
||||
if (cpid > 0) {
|
||||
int status = 0;
|
||||
waitpid(cpid, &status, 0);
|
||||
return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
|
||||
} else {
|
||||
// At this point we are on our child process.
|
||||
execvp(argv[0], argv);
|
||||
exit(1);
|
||||
|
||||
// Something failed.
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
void thread_create (abstr_thread_t* thread, thread_fn fn, void* data) {
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_create(thread, &attr, fn, data);
|
||||
}
|
||||
|
||||
void thread_join (abstr_thread_t thread) {
|
||||
pthread_join(thread, NULL);
|
||||
}
|
||||
|
||||
int fusermount (char *path) {
|
||||
char *argv[] = {(char *) "umount", path, NULL};
|
||||
|
||||
return execute_command_and_wait(argv);
|
||||
}
|
||||
|
||||
#elif defined(_WIN32)
|
||||
|
||||
HANDLE mutex = CreateMutex(NULL, false, NULL);
|
||||
|
||||
void thread_create (HANDLE* thread, thread_fn fn, void* data) {
|
||||
*thread = CreateThread(NULL, 0, fn, data, 0, NULL);
|
||||
}
|
||||
|
||||
void thread_join (HANDLE thread) {
|
||||
WaitForSingleObject(thread, INFINITE);
|
||||
}
|
||||
|
||||
int fusermount (char *path) {
|
||||
char* dokanPath = getenv("DokanLibrary1");
|
||||
char cmdLine[MAX_PATH];
|
||||
|
||||
if(dokanPath) {
|
||||
// Let's make sure there aren't no double slashes
|
||||
const char* dokanPathLast = dokanPath + strlen(dokanPath) - 1;
|
||||
|
||||
const char* potentialEndSlash =
|
||||
(*dokanPathLast == '/' || *dokanPathLast == '\\') ? "" : "\\";
|
||||
|
||||
sprintf(cmdLine, "\"%s%sdokanctl.exe\" /u %s", dokanPath, potentialEndSlash, path);
|
||||
}
|
||||
else sprintf(cmdLine, "dokanctl.exe /u %s", path);
|
||||
|
||||
STARTUPINFO info = {sizeof(info)};
|
||||
PROCESS_INFORMATION procInfo;
|
||||
CreateProcess(NULL, cmdLine, NULL, NULL, false, CREATE_NO_WINDOW, NULL, NULL, &info, &procInfo);
|
||||
|
||||
WaitForSingleObject(procInfo.hProcess, INFINITE);
|
||||
|
||||
DWORD exitCode = -1;
|
||||
GetExitCodeProcess(procInfo.hProcess, &exitCode);
|
||||
|
||||
CloseHandle(procInfo.hProcess);
|
||||
CloseHandle(procInfo.hThread);
|
||||
|
||||
return exitCode;
|
||||
|
||||
// dokanctl.exe requires admin permissions for some reason, so if node is not run as admin,
|
||||
// it'll fail to create the process for unmounting. The path will be unmounted once
|
||||
// the process is killed, however, so there's that!
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
void thread_create (abstr_thread_t* thread, thread_fn fn, void* data) {
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_create(thread, &attr, fn, data);
|
||||
}
|
||||
|
||||
void thread_join (abstr_thread_t thread) {
|
||||
pthread_join(thread, NULL);
|
||||
}
|
||||
|
||||
int fusermount (char *path) {
|
||||
char *argv[] = {(char *) "fusermount", (char *) "-q", (char *) "-u", path, NULL};
|
||||
|
||||
return execute_command_and_wait(argv);
|
||||
}
|
||||
|
||||
#endif
|
126
abstractions.h
126
abstractions.h
@ -1,126 +0,0 @@
|
||||
#include <nan.h>
|
||||
|
||||
#define FUSE_USE_VERSION 29
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
// OS X
|
||||
#include <sys/mount.h>
|
||||
|
||||
#include <semaphore.h>
|
||||
#include <dispatch/dispatch.h>
|
||||
|
||||
#include <fuse_lowlevel.h>
|
||||
|
||||
#define FUSE_OFF_T off_t
|
||||
|
||||
typedef dispatch_semaphore_t bindings_sem_t;
|
||||
|
||||
NAN_INLINE static int semaphore_init (dispatch_semaphore_t *sem) {
|
||||
*sem = dispatch_semaphore_create(0);
|
||||
return *sem == NULL ? -1 : 0;
|
||||
}
|
||||
|
||||
NAN_INLINE static void semaphore_wait (dispatch_semaphore_t *sem) {
|
||||
dispatch_semaphore_wait(*sem, DISPATCH_TIME_FOREVER);
|
||||
}
|
||||
|
||||
NAN_INLINE static void semaphore_signal (dispatch_semaphore_t *sem) {
|
||||
dispatch_semaphore_signal(*sem);
|
||||
}
|
||||
|
||||
extern pthread_mutex_t mutex;
|
||||
|
||||
NAN_INLINE static void mutex_lock (pthread_mutex_t *mutex) {
|
||||
pthread_mutex_lock(mutex);
|
||||
}
|
||||
|
||||
NAN_INLINE static void mutex_unlock (pthread_mutex_t *mutex) {
|
||||
pthread_mutex_unlock(mutex);
|
||||
}
|
||||
|
||||
typedef pthread_t abstr_thread_t;
|
||||
typedef void* thread_fn_rtn_t;
|
||||
|
||||
#elif defined(_WIN32)
|
||||
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
#include <winsock2.h>
|
||||
|
||||
typedef HANDLE bindings_sem_t;
|
||||
|
||||
NAN_INLINE static int semaphore_init (HANDLE *sem) {
|
||||
*sem = CreateSemaphore(NULL, 0, 10, NULL);
|
||||
return *sem == NULL ? -1 : 0;
|
||||
}
|
||||
|
||||
NAN_INLINE static void semaphore_wait (HANDLE *sem) {
|
||||
WaitForSingleObject(*sem, INFINITE);
|
||||
}
|
||||
|
||||
NAN_INLINE static void semaphore_signal (HANDLE *sem) {
|
||||
ReleaseSemaphore(*sem, 1, NULL);
|
||||
}
|
||||
|
||||
extern HANDLE mutex;
|
||||
|
||||
NAN_INLINE static void mutex_lock (HANDLE *mutex) {
|
||||
WaitForSingleObject(*mutex, INFINITE);
|
||||
}
|
||||
|
||||
NAN_INLINE static void mutex_unlock (HANDLE *mutex) {
|
||||
ReleaseMutex(*mutex);
|
||||
}
|
||||
|
||||
typedef HANDLE abstr_thread_t;
|
||||
typedef DWORD thread_fn_rtn_t;
|
||||
|
||||
#define fuse_session_remove_chan(x)
|
||||
#define stat _stati64
|
||||
|
||||
#else
|
||||
|
||||
// Linux and whatnot
|
||||
#include <sys/mount.h>
|
||||
|
||||
#include <semaphore.h>
|
||||
#include <fuse_lowlevel.h>
|
||||
|
||||
#define FUSE_OFF_T off_t
|
||||
|
||||
typedef sem_t bindings_sem_t;
|
||||
|
||||
NAN_INLINE static int semaphore_init (sem_t *sem) {
|
||||
return sem_init(sem, 0, 0);
|
||||
}
|
||||
|
||||
NAN_INLINE static void semaphore_wait (sem_t *sem) {
|
||||
sem_wait(sem);
|
||||
}
|
||||
|
||||
NAN_INLINE static void semaphore_signal (sem_t *sem) {
|
||||
sem_post(sem);
|
||||
}
|
||||
|
||||
extern pthread_mutex_t mutex;
|
||||
|
||||
NAN_INLINE static void mutex_lock (pthread_mutex_t *mutex) {
|
||||
pthread_mutex_lock(mutex);
|
||||
}
|
||||
|
||||
NAN_INLINE static void mutex_unlock (pthread_mutex_t *mutex) {
|
||||
pthread_mutex_unlock(mutex);
|
||||
}
|
||||
|
||||
typedef pthread_t abstr_thread_t;
|
||||
typedef void* thread_fn_rtn_t;
|
||||
|
||||
#endif
|
||||
|
||||
typedef thread_fn_rtn_t(*thread_fn)(void*);
|
||||
|
||||
void thread_create (abstr_thread_t*, thread_fn, void*);
|
||||
void thread_join (abstr_thread_t);
|
||||
|
||||
int fusermount (char*);
|
20
bin.js
Executable file
20
bin.js
Executable file
@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const Fuse = require('./')
|
||||
const cmd = process.argv[2]
|
||||
|
||||
if (cmd === 'configure') {
|
||||
Fuse.configure(onerror)
|
||||
} else if (cmd === 'unconfigure') {
|
||||
Fuse.unconfigure(onerror)
|
||||
} else if (cmd === 'is-configured') {
|
||||
Fuse.isConfigured(function (err, bool) {
|
||||
if (err) return onerror(err)
|
||||
console.log('' + bool)
|
||||
process.exit(bool ? 0 : 1)
|
||||
})
|
||||
}
|
||||
|
||||
function onerror (err) {
|
||||
if (err) throw err
|
||||
}
|
18
binding.gyp
18
binding.gyp
@ -3,16 +3,26 @@
|
||||
"target_name": "fuse",
|
||||
"include_dirs": [
|
||||
"<!(node -e \"require('napi-macros')\")",
|
||||
"<!(node -e \"require('nan')\")",
|
||||
"<!(node -e \"require('fuse-shared-library/include')\")",
|
||||
],
|
||||
"libraries": [
|
||||
"<!(node -e \"require('fuse-shared-library/lib')\")",
|
||||
],
|
||||
"sources": [
|
||||
"fuse-bindings.cc",
|
||||
"abstractions.cc"
|
||||
]
|
||||
"fuse-native.c"
|
||||
],
|
||||
'xcode_settings': {
|
||||
'OTHER_CFLAGS': [
|
||||
'-g',
|
||||
'-O3',
|
||||
'-Wall'
|
||||
]
|
||||
},
|
||||
'cflags': [
|
||||
'-g',
|
||||
'-O3',
|
||||
'-Wall'
|
||||
],
|
||||
}, {
|
||||
"target_name": "postinstall",
|
||||
"type": "none",
|
||||
|
55
example.js
55
example.js
@ -1,17 +1,31 @@
|
||||
var fuse = require('./')
|
||||
const Fuse = require('./')
|
||||
|
||||
var mountPath = process.platform !== 'win32' ? './mnt' : 'M:\\'
|
||||
|
||||
fuse.mount(mountPath, {
|
||||
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(),
|
||||
@ -21,11 +35,10 @@ fuse.mount(mountPath, {
|
||||
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(),
|
||||
@ -35,33 +48,35 @@ fuse.mount(mountPath, {
|
||||
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)
|
||||
}
|
||||
}, function (err) {
|
||||
}
|
||||
|
||||
const fuse = new Fuse('./mnt', ops, { debug: true, displayFolder: true })
|
||||
fuse.mount(err => {
|
||||
if (err) throw err
|
||||
console.log('filesystem mounted on ' + mountPath)
|
||||
console.log('filesystem mounted on ' + fuse.mnt)
|
||||
})
|
||||
|
||||
process.on('SIGINT', function () {
|
||||
fuse.unmount(mountPath, function (err) {
|
||||
process.once('SIGINT', function () {
|
||||
fuse.unmount(err => {
|
||||
if (err) {
|
||||
console.log('filesystem at ' + mountPath + ' not unmounted', err)
|
||||
console.log('filesystem at ' + fuse.mnt + ' not unmounted', err)
|
||||
} else {
|
||||
console.log('filesystem at ' + mountPath + ' unmounted')
|
||||
console.log('filesystem at ' + fuse.mnt + ' unmounted')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
1337
fuse-bindings.cc
1337
fuse-bindings.cc
File diff suppressed because it is too large
Load Diff
989
fuse-native.c
Normal file
989
fuse-native.c
Normal file
@ -0,0 +1,989 @@
|
||||
#define FUSE_USE_VERSION 29
|
||||
|
||||
#include <uv.h>
|
||||
#include <node_api.h>
|
||||
#include <napi-macros.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <fuse.h>
|
||||
#include <fuse_opt.h>
|
||||
#include <fuse_common.h>
|
||||
#include <fuse_lowlevel.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <pthread.h>
|
||||
|
||||
static int IS_ARRAY_BUFFER_DETACH_SUPPORTED = 0;
|
||||
|
||||
napi_status napi_detach_arraybuffer(napi_env env, napi_value buf);
|
||||
|
||||
#define FUSE_NATIVE_CALLBACK(fn, blk)\
|
||||
napi_env env = ft->env;\
|
||||
napi_handle_scope scope;\
|
||||
napi_open_handle_scope(env, &scope);\
|
||||
napi_value ctx;\
|
||||
napi_get_reference_value(env, ft->ctx, &ctx);\
|
||||
napi_value callback;\
|
||||
napi_get_reference_value(env, fn, &callback);\
|
||||
blk\
|
||||
napi_close_handle_scope(env, scope);
|
||||
|
||||
#define FUSE_NATIVE_HANDLER(name, blk)\
|
||||
fuse_thread_locals_t *l = get_thread_locals();\
|
||||
l->op = op_##name;\
|
||||
l->op_fn = fuse_native_dispatch_##name;\
|
||||
blk\
|
||||
uv_async_send(&(l->async));\
|
||||
uv_sem_wait(&(l->sem));\
|
||||
return l->res;
|
||||
|
||||
#define FUSE_METHOD(name, callbackArgs, signalArgs, signature, callBlk, callbackBlk, signalBlk)\
|
||||
static void fuse_native_dispatch_##name (uv_async_t* handle, fuse_thread_locals_t* l, fuse_thread_t* ft) {\
|
||||
uint32_t op = op_##name;\
|
||||
FUSE_NATIVE_CALLBACK(ft->handlers[op], {\
|
||||
napi_value argv[callbackArgs + 2];\
|
||||
napi_get_reference_value(env, l->self, &(argv[0]));\
|
||||
napi_create_uint32(env, l->op, &(argv[1]));\
|
||||
callbackBlk\
|
||||
NAPI_MAKE_CALLBACK(env, NULL, ctx, callback, callbackArgs + 2, argv, NULL)\
|
||||
})\
|
||||
}\
|
||||
NAPI_METHOD(fuse_native_signal_##name) {\
|
||||
NAPI_ARGV(signalArgs + 2)\
|
||||
NAPI_ARGV_BUFFER_CAST(fuse_thread_locals_t *, l, 0);\
|
||||
NAPI_ARGV_INT32(res, 1);\
|
||||
signalBlk\
|
||||
l->res = res;\
|
||||
uv_sem_post(&(l->sem));\
|
||||
return NULL;\
|
||||
}\
|
||||
static int fuse_native_##name signature {\
|
||||
FUSE_NATIVE_HANDLER(name, callBlk)\
|
||||
}
|
||||
|
||||
#define FUSE_METHOD_VOID(name, callbackArgs, signalArgs, signature, callBlk, callbackBlk)\
|
||||
FUSE_METHOD(name, callbackArgs, signalArgs, signature, callBlk, callbackBlk, {})
|
||||
|
||||
#define FUSE_UINT64_TO_INTS_ARGV(n, pos)\
|
||||
uint32_t low##pos = n % 4294967296;\
|
||||
uint32_t high##pos = (n - low##pos) / 4294967296;\
|
||||
napi_create_uint32(env, low##pos, &(argv[pos]));\
|
||||
napi_create_uint32(env, high##pos, &(argv[pos + 1]));
|
||||
|
||||
|
||||
// Opcodes
|
||||
|
||||
static const uint32_t op_init = 0;
|
||||
static const uint32_t op_error = 1;
|
||||
static const uint32_t op_access = 2;
|
||||
static const uint32_t op_statfs = 3;
|
||||
static const uint32_t op_fgetattr = 4;
|
||||
static const uint32_t op_getattr = 5;
|
||||
static const uint32_t op_flush = 6;
|
||||
static const uint32_t op_fsync = 7;
|
||||
static const uint32_t op_fsyncdir = 8;
|
||||
static const uint32_t op_readdir = 9;
|
||||
static const uint32_t op_truncate = 10;
|
||||
static const uint32_t op_ftruncate = 11;
|
||||
static const uint32_t op_utimens = 12;
|
||||
static const uint32_t op_readlink = 13;
|
||||
static const uint32_t op_chown = 14;
|
||||
static const uint32_t op_chmod = 15;
|
||||
static const uint32_t op_mknod = 16;
|
||||
static const uint32_t op_setxattr = 17;
|
||||
static const uint32_t op_getxattr = 18;
|
||||
static const uint32_t op_listxattr = 19;
|
||||
static const uint32_t op_removexattr = 20;
|
||||
static const uint32_t op_open = 21;
|
||||
static const uint32_t op_opendir = 22;
|
||||
static const uint32_t op_read = 23;
|
||||
static const uint32_t op_write = 24;
|
||||
static const uint32_t op_release = 25;
|
||||
static const uint32_t op_releasedir = 26;
|
||||
static const uint32_t op_create = 27;
|
||||
static const uint32_t op_unlink = 28;
|
||||
static const uint32_t op_rename = 29;
|
||||
static const uint32_t op_link = 30;
|
||||
static const uint32_t op_symlink = 31;
|
||||
static const uint32_t op_mkdir = 32;
|
||||
static const uint32_t op_rmdir = 33;
|
||||
|
||||
// Data structures
|
||||
|
||||
typedef struct {
|
||||
napi_env env;
|
||||
pthread_t thread;
|
||||
pthread_attr_t attr;
|
||||
napi_ref ctx;
|
||||
napi_ref malloc;
|
||||
|
||||
// Operation handlers
|
||||
napi_ref handlers[35];
|
||||
|
||||
struct fuse *fuse;
|
||||
struct fuse_chan *ch;
|
||||
char mnt[1024];
|
||||
char mntopts[1024];
|
||||
int mounted;
|
||||
|
||||
uv_async_t async;
|
||||
uv_mutex_t mut;
|
||||
uv_sem_t sem;
|
||||
} fuse_thread_t;
|
||||
|
||||
typedef struct {
|
||||
napi_ref self;
|
||||
|
||||
// Opcode
|
||||
uint32_t op;
|
||||
void *op_fn;
|
||||
|
||||
// Payloads
|
||||
const char *path;
|
||||
const char *dest;
|
||||
char *linkname;
|
||||
struct fuse_file_info *info;
|
||||
const void *buf;
|
||||
off_t offset;
|
||||
size_t len;
|
||||
mode_t mode;
|
||||
dev_t dev;
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
size_t atime;
|
||||
size_t mtime;
|
||||
int32_t res;
|
||||
|
||||
// Extended attributes
|
||||
const char *name;
|
||||
const char *value;
|
||||
char *list;
|
||||
size_t size;
|
||||
uint32_t position;
|
||||
int flags;
|
||||
|
||||
// Stat + Statfs
|
||||
struct stat *stat;
|
||||
struct statvfs *statvfs;
|
||||
|
||||
// Readdir
|
||||
fuse_fill_dir_t readdir_filler;
|
||||
|
||||
// Internal bookkeeping
|
||||
fuse_thread_t *fuse;
|
||||
uv_sem_t sem;
|
||||
uv_async_t async;
|
||||
|
||||
} fuse_thread_locals_t;
|
||||
|
||||
static pthread_key_t thread_locals_key;
|
||||
static fuse_thread_locals_t* get_thread_locals();
|
||||
|
||||
// Helpers
|
||||
// TODO: Extract into a separate file.
|
||||
|
||||
static uint64_t uint32s_to_uint64 (uint32_t **ints) {
|
||||
uint64_t low = *((*ints)++);
|
||||
uint64_t high = *((*ints)++);
|
||||
return high * 4294967296 + low;
|
||||
}
|
||||
|
||||
static void uint32s_to_timespec (struct timespec* ts, uint32_t** ints) {
|
||||
uint64_t ms = uint32s_to_uint64(ints);
|
||||
ts->tv_sec = ms / 1000;
|
||||
ts->tv_nsec = (ms % 1000) * 1000000;
|
||||
}
|
||||
|
||||
static uint64_t timespec_to_uint64 (const struct timespec* ts) {
|
||||
uint64_t ms = (ts->tv_sec * 1000) + (ts->tv_nsec / 1000000);
|
||||
return ms;
|
||||
}
|
||||
|
||||
static void populate_stat (uint32_t *ints, struct stat* stat) {
|
||||
stat->st_mode = *ints++;
|
||||
stat->st_uid = *ints++;
|
||||
stat->st_gid = *ints++;
|
||||
stat->st_size = uint32s_to_uint64(&ints);
|
||||
stat->st_dev = *ints++;
|
||||
stat->st_nlink = *ints++;
|
||||
stat->st_ino = *ints++;
|
||||
stat->st_rdev = *ints++;
|
||||
stat->st_blksize = *ints++;
|
||||
stat->st_blocks = uint32s_to_uint64(&ints);
|
||||
#ifdef __APPLE__
|
||||
uint32s_to_timespec(&stat->st_atimespec, &ints);
|
||||
uint32s_to_timespec(&stat->st_mtimespec, &ints);
|
||||
uint32s_to_timespec(&stat->st_ctimespec, &ints);
|
||||
#else
|
||||
uint32s_to_timespec(&stat->st_atim, &ints);
|
||||
uint32s_to_timespec(&stat->st_mtim, &ints);
|
||||
uint32s_to_timespec(&stat->st_ctim, &ints);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void populate_statvfs (uint32_t *ints, struct statvfs* statvfs) {
|
||||
statvfs->f_bsize = *ints++;
|
||||
statvfs->f_frsize = *ints++;
|
||||
statvfs->f_blocks = *ints++;
|
||||
statvfs->f_bfree = *ints++;
|
||||
statvfs->f_bavail = *ints++;
|
||||
statvfs->f_files = *ints++;
|
||||
statvfs->f_ffree = *ints++;
|
||||
statvfs->f_favail = *ints++;
|
||||
statvfs->f_fsid = *ints++;
|
||||
statvfs->f_flag = *ints++;
|
||||
statvfs->f_namemax = *ints++;
|
||||
}
|
||||
|
||||
// Methods
|
||||
|
||||
FUSE_METHOD(statfs, 1, 1, (const char * path, struct statvfs *statvfs), {
|
||||
l->path = path;
|
||||
l->statvfs = statvfs;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
}, {
|
||||
NAPI_ARGV_BUFFER_CAST(uint32_t*, ints, 2)
|
||||
populate_statvfs(ints, l->statvfs);
|
||||
})
|
||||
|
||||
FUSE_METHOD(getattr, 1, 1, (const char *path, struct stat *stat), {
|
||||
l->path = path;
|
||||
l->stat = stat;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
}, {
|
||||
NAPI_ARGV_BUFFER_CAST(uint32_t*, ints, 2)
|
||||
populate_stat(ints, l->stat);
|
||||
})
|
||||
|
||||
FUSE_METHOD(fgetattr, 2, 1, (const char *path, struct stat *stat, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->stat = stat;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
if (l->info != NULL) {
|
||||
napi_create_uint32(env, l->info->fh, &(argv[3]));
|
||||
} else {
|
||||
napi_create_uint32(env, 0, &(argv[3]));
|
||||
}
|
||||
}, {
|
||||
NAPI_ARGV_BUFFER_CAST(uint32_t*, ints, 2)
|
||||
populate_stat(ints, l->stat);
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(access, 2, 0, (const char *path, int mode), {
|
||||
l->path = path;
|
||||
l->mode = mode;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_uint32(env, l->mode, &(argv[3]));
|
||||
})
|
||||
|
||||
FUSE_METHOD(open, 2, 1, (const char *path, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
if (l->info != NULL) {
|
||||
napi_create_uint32(env, l->info->flags, &(argv[3]));
|
||||
} else {
|
||||
napi_create_uint32(env, 0, &(argv[3]));
|
||||
}
|
||||
}, {
|
||||
NAPI_ARGV_INT32(fd, 2)
|
||||
if (fd != 0) {
|
||||
l->info->fh = fd;
|
||||
}
|
||||
})
|
||||
|
||||
FUSE_METHOD(opendir, 3, 1, (const char *path, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
if (l->info != NULL) {
|
||||
napi_create_uint32(env, l->info->fh, &(argv[3]));
|
||||
napi_create_uint32(env, l->info->flags, &(argv[4]));
|
||||
} else {
|
||||
napi_create_uint32(env, 0, &(argv[3]));
|
||||
napi_create_uint32(env, 0, &(argv[4]));
|
||||
}
|
||||
}, {
|
||||
NAPI_ARGV_INT32(fd, 2)
|
||||
if (fd != 0) {
|
||||
l->info->fh = fd;
|
||||
}
|
||||
})
|
||||
|
||||
FUSE_METHOD(create, 2, 1, (const char *path, mode_t mode, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->mode = mode;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_uint32(env, l->mode, &(argv[3]));
|
||||
}, {
|
||||
NAPI_ARGV_INT32(fd, 2)
|
||||
if (fd != 0) {
|
||||
l->info->fh = fd;
|
||||
}
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(utimens, 5, 0, (const char *path, const struct timespec tv[2]), {
|
||||
l->path = path;
|
||||
l->atime = timespec_to_uint64(&tv[0]);
|
||||
l->mtime = timespec_to_uint64(&tv[1]);
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
FUSE_UINT64_TO_INTS_ARGV(l->atime, 3)
|
||||
FUSE_UINT64_TO_INTS_ARGV(l->atime, 5)
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(release, 2, 0, (const char *path, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
if (l->info != NULL) {
|
||||
napi_create_uint32(env, l->info->fh, &(argv[3]));
|
||||
} else {
|
||||
napi_create_uint32(env, 0, &(argv[3]));
|
||||
}
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(releasedir, 2, 0, (const char *path, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
if (l->info != NULL) {
|
||||
napi_create_uint32(env, l->info->fh, &(argv[3]));
|
||||
} else {
|
||||
napi_create_uint32(env, 0, &(argv[3]));
|
||||
}
|
||||
})
|
||||
|
||||
FUSE_METHOD(read, 6, 2, (const char *path, char *buf, size_t len, off_t offset, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->buf = buf;
|
||||
l->len = len;
|
||||
l->offset = offset;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_uint32(env, l->info->fh, &(argv[3]));
|
||||
napi_create_external_buffer(env, l->len, (char *) l->buf, NULL, NULL, &(argv[4]));
|
||||
napi_create_uint32(env, l->len, &(argv[5]));
|
||||
FUSE_UINT64_TO_INTS_ARGV(l->offset, 6)
|
||||
}, {
|
||||
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[3]) == napi_ok);
|
||||
})
|
||||
|
||||
FUSE_METHOD(write, 6, 2, (const char *path, const char *buf, size_t len, off_t offset, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->buf = buf;
|
||||
l->len = len;
|
||||
l->offset = offset;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_uint32(env, l->info->fh, &(argv[3]));
|
||||
napi_create_external_buffer(env, l->len, (char *) l->buf, NULL, NULL, &(argv[4]));
|
||||
napi_create_uint32(env, l->len, &(argv[5]));
|
||||
FUSE_UINT64_TO_INTS_ARGV(l->offset, 6)
|
||||
}, {
|
||||
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[3]) == napi_ok);
|
||||
})
|
||||
|
||||
FUSE_METHOD(readdir, 1, 2, (const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *info), {
|
||||
l->buf = buf;
|
||||
l->path = path;
|
||||
l->offset = offset;
|
||||
l->info = info;
|
||||
l->readdir_filler = filler;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
}, {
|
||||
uint32_t stats_length;
|
||||
uint32_t names_length;
|
||||
napi_get_array_length(env, argv[3], &stats_length);
|
||||
napi_get_array_length(env, argv[2], &names_length);
|
||||
|
||||
napi_value raw_names = argv[2];
|
||||
napi_value raw_stats = argv[3];
|
||||
|
||||
if (names_length != stats_length) {
|
||||
NAPI_FOR_EACH(raw_names, raw_name) {
|
||||
NAPI_UTF8(name, 1024, raw_name)
|
||||
int err = l->readdir_filler((char *) l->buf, name, NULL, 0);
|
||||
if (err == 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NAPI_FOR_EACH(raw_names, raw_name) {
|
||||
NAPI_UTF8(name, 1024, raw_name)
|
||||
napi_value raw_stat;
|
||||
napi_get_element(env, raw_stats, i, &raw_stat);
|
||||
|
||||
NAPI_BUFFER_CAST(uint32_t*, stats_array, raw_stat);
|
||||
struct stat st;
|
||||
populate_stat(stats_array, &st);
|
||||
|
||||
// TODO: It turns out readdirplus likely won't work with FUSE 29...
|
||||
// Metadata caching between readdir/getattr will be enabled when we upgrade fuse-shared-library
|
||||
int err = l->readdir_filler((char *) l->buf, name, (struct stat *) &st, 0);
|
||||
if (err == 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
FUSE_METHOD(setxattr, 5, 1, (const char *path, const char *name, const char *value, size_t size, int flags, uint32_t position), {
|
||||
l->path = path;
|
||||
l->name = name;
|
||||
l->value = value;
|
||||
l->size = size;
|
||||
l->flags = flags;
|
||||
l->position = position;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_string_utf8(env, l->name, NAPI_AUTO_LENGTH, &(argv[3]));
|
||||
napi_create_external_buffer(env, l->size, (char *) l->value, NULL, NULL, &(argv[4]));
|
||||
napi_create_uint32(env, l->position, &(argv[5]));
|
||||
napi_create_uint32(env, l->flags, &(argv[6]));
|
||||
}, {
|
||||
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[2]) == napi_ok);
|
||||
})
|
||||
|
||||
FUSE_METHOD(getxattr, 4, 1, (const char *path, const char *name, char *value, size_t size, uint32_t position), {
|
||||
l->path = path;
|
||||
l->name = name;
|
||||
l->value = value;
|
||||
l->size = size;
|
||||
l->position = position;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_string_utf8(env, l->name, NAPI_AUTO_LENGTH, &(argv[3]));
|
||||
napi_create_external_buffer(env, l->size, (char *) l->value, NULL, NULL, &(argv[4]));
|
||||
napi_create_uint32(env, l->position, &(argv[5]));
|
||||
}, {
|
||||
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[2]) == napi_ok);
|
||||
})
|
||||
|
||||
#else
|
||||
|
||||
FUSE_METHOD(setxattr, 5, 1, (const char *path, const char *name, const char *value, size_t size, int flags), {
|
||||
l->path = path;
|
||||
l->name = name;
|
||||
l->value = value;
|
||||
l->size = size;
|
||||
l->flags = flags;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_string_utf8(env, l->name, NAPI_AUTO_LENGTH, &(argv[3]));
|
||||
napi_create_external_buffer(env, l->size, (char *) l->value, NULL, NULL, &(argv[4]));
|
||||
napi_create_uint32(env, 0, &(argv[5])); // normalize apis between mac and linux
|
||||
napi_create_uint32(env, l->flags, &(argv[6]));
|
||||
}, {
|
||||
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[2]) == napi_ok);
|
||||
})
|
||||
|
||||
FUSE_METHOD(getxattr, 4, 1, (const char *path, const char *name, char *value, size_t size), {
|
||||
l->path = path;
|
||||
l->name = name;
|
||||
l->value = value;
|
||||
l->size = size;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_string_utf8(env, l->name, NAPI_AUTO_LENGTH, &(argv[3]));
|
||||
napi_create_external_buffer(env, l->size, (char *) l->value, NULL, NULL, &(argv[4]));
|
||||
napi_create_uint32(env, 0, &(argv[5]));
|
||||
}, {
|
||||
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[2]) == napi_ok);
|
||||
})
|
||||
|
||||
#endif
|
||||
|
||||
FUSE_METHOD(listxattr, 2, 1, (const char *path, char *list, size_t size), {
|
||||
l->path = path;
|
||||
l->list = list;
|
||||
l->size = size;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_external_buffer(env, l->size, l->list, NULL, NULL, &(argv[3]));
|
||||
}, {
|
||||
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[2]) == napi_ok);
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(removexattr, 2, 0, (const char *path, const char *name), {
|
||||
l->path = path;
|
||||
l->name = name;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_string_utf8(env, l->name, NAPI_AUTO_LENGTH, &(argv[3]));
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(flush, 2, 0, (const char *path, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
if (l->info != NULL) {
|
||||
napi_create_uint32(env, l->info->fh, &(argv[3]));
|
||||
} else {
|
||||
napi_create_uint32(env, 0, &(argv[3]));
|
||||
}
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(fsync, 3, 0, (const char *path, int datasync, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->mode = datasync;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_uint32(env, l->mode, &(argv[3]));
|
||||
if (l->info != NULL) {
|
||||
napi_create_uint32(env, l->info->fh, &(argv[4]));
|
||||
} else {
|
||||
napi_create_uint32(env, 0, &(argv[4]));
|
||||
}
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(fsyncdir, 3, 0, (const char *path, int datasync, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->mode = datasync;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_uint32(env, l->mode, &(argv[3]));
|
||||
if (l->info != NULL) {
|
||||
napi_create_uint32(env, l->info->fh, &(argv[4]));
|
||||
} else {
|
||||
napi_create_uint32(env, 0, &(argv[4]));
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
FUSE_METHOD_VOID(truncate, 3, 0, (const char *path, off_t size), {
|
||||
l->path = path;
|
||||
l->offset = size;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
FUSE_UINT64_TO_INTS_ARGV(l->offset, 3)
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(ftruncate, 4, 0, (const char *path, off_t size, struct fuse_file_info *info), {
|
||||
l->path = path;
|
||||
l->offset = size;
|
||||
l->info = info;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
if (l->info != NULL) {
|
||||
napi_create_uint32(env, l->info->fh, &(argv[3]));
|
||||
} else {
|
||||
napi_create_uint32(env, 0, &(argv[3]));
|
||||
}
|
||||
FUSE_UINT64_TO_INTS_ARGV(l->offset, 4)
|
||||
})
|
||||
|
||||
FUSE_METHOD(readlink, 1, 1, (const char *path, char *linkname, size_t len), {
|
||||
l->path = path;
|
||||
l->linkname = linkname;
|
||||
l->len = len;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
}, {
|
||||
NAPI_ARGV_UTF8(linkname, l->len, 2)
|
||||
strncpy(l->linkname, linkname, l->len);
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(chown, 3, 0, (const char *path, uid_t uid, gid_t gid), {
|
||||
l->path = path;
|
||||
l->uid = uid;
|
||||
l->gid = gid;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_uint32(env, l->uid, &(argv[3]));
|
||||
napi_create_uint32(env, l->gid, &(argv[4]));
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(chmod, 2, 0, (const char *path, mode_t mode), {
|
||||
l->path = path;
|
||||
l->mode = mode;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_uint32(env, l->mode, &(argv[3]));
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(mknod, 3, 0, (const char *path, mode_t mode, dev_t dev), {
|
||||
l->path = path;
|
||||
l->mode = mode;
|
||||
l->dev = dev;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_uint32(env, l->mode, &(argv[3]));
|
||||
napi_create_uint32(env, l->dev, &(argv[4]));
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(unlink, 1, 0, (const char *path), {
|
||||
l->path = path;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(rename, 2, 0, (const char *path, const char *dest), {
|
||||
l->path = path;
|
||||
l->dest = dest;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_string_utf8(env, l->dest, NAPI_AUTO_LENGTH, &(argv[3]));
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(link, 2, 0, (const char *path, const char *dest), {
|
||||
l->path = path;
|
||||
l->dest = dest;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_string_utf8(env, l->dest, NAPI_AUTO_LENGTH, &(argv[3]));
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(symlink, 2, 0, (const char *path, const char *dest), {
|
||||
l->path = path;
|
||||
l->dest = dest;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_string_utf8(env, l->dest, NAPI_AUTO_LENGTH, &(argv[3]));
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(mkdir, 2, 0, (const char *path, mode_t mode), {
|
||||
l->path = path;
|
||||
l->mode = mode;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
napi_create_uint32(env, l->mode, &(argv[3]));
|
||||
})
|
||||
|
||||
FUSE_METHOD_VOID(rmdir, 1, 0, (const char *path), {
|
||||
l->path = path;
|
||||
}, {
|
||||
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
|
||||
})
|
||||
|
||||
static void fuse_native_dispatch_init (uv_async_t* handle, fuse_thread_locals_t* l, fuse_thread_t* ft) {\
|
||||
FUSE_NATIVE_CALLBACK(ft->handlers[op_init], {
|
||||
napi_value argv[2];
|
||||
|
||||
napi_get_reference_value(env, l->self, &(argv[0]));
|
||||
napi_create_uint32(env, l->op, &(argv[1]));
|
||||
|
||||
NAPI_MAKE_CALLBACK(env, NULL, ctx, callback, 2, argv, NULL);
|
||||
})
|
||||
}
|
||||
|
||||
NAPI_METHOD(fuse_native_signal_init) {
|
||||
NAPI_ARGV(2)
|
||||
NAPI_ARGV_BUFFER_CAST(fuse_thread_locals_t *, l, 0);
|
||||
NAPI_ARGV_INT32(res, 1);
|
||||
l->res = res;
|
||||
uv_sem_post(&(l->sem));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void * fuse_native_init (struct fuse_conn_info *conn) {
|
||||
fuse_thread_locals_t *l = get_thread_locals();
|
||||
|
||||
l->op = op_init;
|
||||
l->op_fn = fuse_native_dispatch_init;
|
||||
|
||||
uv_async_send(&(l->async));
|
||||
uv_sem_wait(&(l->sem));
|
||||
|
||||
return l->fuse;
|
||||
}
|
||||
|
||||
// Top-level dispatcher
|
||||
|
||||
static void fuse_native_dispatch (uv_async_t* handle) {
|
||||
fuse_thread_locals_t *l = (fuse_thread_locals_t *) handle->data;
|
||||
fuse_thread_t *ft = l->fuse;
|
||||
void (*fn)(uv_async_t *, fuse_thread_locals_t *, fuse_thread_t *) = l->op_fn;
|
||||
|
||||
fn(handle, l, ft);
|
||||
}
|
||||
|
||||
static void fuse_native_async_init (uv_async_t* handle) {
|
||||
fuse_thread_t *ft = (fuse_thread_t *) handle->data;
|
||||
fuse_thread_locals_t *l;
|
||||
|
||||
FUSE_NATIVE_CALLBACK(ft->malloc, {
|
||||
napi_value argv[1];
|
||||
napi_create_uint32(ft->env, (uint32_t) sizeof(fuse_thread_locals_t), &(argv[0]));
|
||||
|
||||
napi_value buf;
|
||||
NAPI_MAKE_CALLBACK(ft->env, NULL, ctx, callback, 1, argv, &buf);
|
||||
|
||||
size_t l_len;
|
||||
|
||||
napi_get_buffer_info(env, buf, (void **) &l, &l_len);
|
||||
napi_create_reference(env, buf, 1, &(l->self));
|
||||
})
|
||||
|
||||
|
||||
int err = uv_async_init(uv_default_loop(), &(l->async), (uv_async_cb) fuse_native_dispatch);
|
||||
assert(err >= 0);
|
||||
|
||||
uv_unref((uv_handle_t *) &(l->async));
|
||||
|
||||
uv_sem_init(&(l->sem), 0);
|
||||
l->async.data = l;
|
||||
ft->async.data = l;
|
||||
l->fuse = ft;
|
||||
|
||||
uv_sem_post(&(ft->sem));
|
||||
}
|
||||
|
||||
static fuse_thread_locals_t* get_thread_locals () {
|
||||
struct fuse_context *ctx = fuse_get_context();
|
||||
fuse_thread_t *ft = (fuse_thread_t *) ctx->private_data;
|
||||
|
||||
void *data = pthread_getspecific(thread_locals_key);
|
||||
|
||||
if (data != NULL) {
|
||||
return (fuse_thread_locals_t *) data;
|
||||
}
|
||||
|
||||
// Need to lock the mutation of l->async.
|
||||
uv_mutex_lock(&(ft->mut));
|
||||
ft->async.data = ft;
|
||||
|
||||
// Notify the main thread to uv_async_init l->async.
|
||||
uv_async_send(&(ft->async));
|
||||
uv_sem_wait(&(ft->sem));
|
||||
|
||||
fuse_thread_locals_t *l = (fuse_thread_locals_t*) ft->async.data;
|
||||
|
||||
pthread_setspecific(thread_locals_key, (void *) l);
|
||||
uv_mutex_unlock(&(ft->mut));
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
static void* start_fuse_thread (void *data) {
|
||||
fuse_thread_t *ft = (fuse_thread_t *) data;
|
||||
fuse_loop_mt(ft->fuse);
|
||||
|
||||
fuse_unmount(ft->mnt, ft->ch);
|
||||
fuse_session_remove_chan(ft->ch);
|
||||
fuse_destroy(ft->fuse);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NAPI_METHOD(fuse_native_mount) {
|
||||
NAPI_ARGV(7)
|
||||
|
||||
NAPI_ARGV_UTF8(mnt, 1024, 0);
|
||||
NAPI_ARGV_UTF8(mntopts, 1024, 1);
|
||||
NAPI_ARGV_BUFFER_CAST(fuse_thread_t *, ft, 2);
|
||||
napi_create_reference(env, argv[3], 1, &(ft->ctx));
|
||||
napi_create_reference(env, argv[4], 1, &(ft->malloc));
|
||||
napi_value handlers = argv[5];
|
||||
NAPI_ARGV_BUFFER_CAST(uint32_t *, implemented, 6)
|
||||
|
||||
for (int i = 0; i < 35; i++) {
|
||||
ft->handlers[i] = NULL;
|
||||
}
|
||||
|
||||
NAPI_FOR_EACH(handlers, handler) {
|
||||
napi_create_reference(env, handler, 1, &ft->handlers[i]);
|
||||
}
|
||||
|
||||
ft->env = env;
|
||||
|
||||
struct fuse_operations ops = { };
|
||||
if (implemented[op_access]) ops.access = fuse_native_access;
|
||||
if (implemented[op_truncate]) ops.truncate = fuse_native_truncate;
|
||||
if (implemented[op_ftruncate]) ops.ftruncate = fuse_native_ftruncate;
|
||||
if (implemented[op_getattr]) ops.getattr = fuse_native_getattr;
|
||||
if (implemented[op_fgetattr]) ops.fgetattr = fuse_native_fgetattr;
|
||||
if (implemented[op_flush]) ops.flush = fuse_native_flush;
|
||||
if (implemented[op_fsync]) ops.fsync = fuse_native_fsync;
|
||||
if (implemented[op_fsyncdir]) ops.fsyncdir = fuse_native_fsyncdir;
|
||||
if (implemented[op_readdir]) ops.readdir = fuse_native_readdir;
|
||||
if (implemented[op_readlink]) ops.readlink = fuse_native_readlink;
|
||||
if (implemented[op_chown]) ops.chown = fuse_native_chown;
|
||||
if (implemented[op_chmod]) ops.chmod = fuse_native_chmod;
|
||||
if (implemented[op_mknod]) ops.mknod = fuse_native_mknod;
|
||||
if (implemented[op_setxattr]) ops.setxattr = fuse_native_setxattr;
|
||||
if (implemented[op_getxattr]) ops.getxattr = fuse_native_getxattr;
|
||||
if (implemented[op_listxattr]) ops.listxattr = fuse_native_listxattr;
|
||||
if (implemented[op_removexattr]) ops.removexattr = fuse_native_removexattr;
|
||||
if (implemented[op_statfs]) ops.statfs = fuse_native_statfs;
|
||||
if (implemented[op_open]) ops.open = fuse_native_open;
|
||||
if (implemented[op_opendir]) ops.opendir = fuse_native_opendir;
|
||||
if (implemented[op_read]) ops.read = fuse_native_read;
|
||||
if (implemented[op_write]) ops.write = fuse_native_write;
|
||||
if (implemented[op_release]) ops.release = fuse_native_release;
|
||||
if (implemented[op_releasedir]) ops.releasedir = fuse_native_releasedir;
|
||||
if (implemented[op_create]) ops.create = fuse_native_create;
|
||||
if (implemented[op_utimens]) ops.utimens = fuse_native_utimens;
|
||||
if (implemented[op_unlink]) ops.unlink = fuse_native_unlink;
|
||||
if (implemented[op_rename]) ops.rename = fuse_native_rename;
|
||||
if (implemented[op_link]) ops.link = fuse_native_link;
|
||||
if (implemented[op_symlink]) ops.symlink = fuse_native_symlink;
|
||||
if (implemented[op_mkdir]) ops.mkdir = fuse_native_mkdir;
|
||||
if (implemented[op_rmdir]) ops.rmdir = fuse_native_rmdir;
|
||||
if (implemented[op_init]) ops.init = fuse_native_init;
|
||||
|
||||
int _argc = (strcmp(mntopts, "-o") <= 0) ? 1 : 2;
|
||||
char *_argv[] = {
|
||||
(char *) "fuse_bindings_dummy",
|
||||
(char *) mntopts
|
||||
};
|
||||
|
||||
struct fuse_args args = FUSE_ARGS_INIT(_argc, _argv);
|
||||
struct fuse_chan *ch = fuse_mount(mnt, &args);
|
||||
|
||||
if (ch == NULL) {
|
||||
napi_throw_error(env, "fuse failed", "fuse failed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct fuse *fuse = fuse_new(ch, &args, &ops, sizeof(struct fuse_operations), ft);
|
||||
|
||||
uv_mutex_init(&(ft->mut));
|
||||
uv_sem_init(&(ft->sem), 0);
|
||||
|
||||
strncpy(ft->mnt, mnt, 1024);
|
||||
strncpy(ft->mntopts, mntopts, 1024);
|
||||
ft->fuse = fuse;
|
||||
ft->ch = ch;
|
||||
ft->mounted++;
|
||||
|
||||
int err = uv_async_init(uv_default_loop(), &(ft->async), (uv_async_cb) fuse_native_async_init);
|
||||
|
||||
if (fuse == NULL || err < 0) {
|
||||
napi_throw_error(env, "fuse failed", "fuse failed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pthread_attr_init(&(ft->attr));
|
||||
pthread_create(&(ft->thread), &(ft->attr), start_fuse_thread, ft);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NAPI_METHOD(fuse_native_unmount) {
|
||||
NAPI_ARGV(2)
|
||||
NAPI_ARGV_UTF8(mnt, 1024, 0);
|
||||
NAPI_ARGV_BUFFER_CAST(fuse_thread_t *, ft, 1);
|
||||
|
||||
if (ft != NULL && ft->mounted) {
|
||||
// TODO: Investigate why the FUSE thread is not always killed after fusermount.
|
||||
// pthread_join(ft->thread, NULL);
|
||||
}
|
||||
|
||||
// TODO: fix the async holding the loop
|
||||
uv_unref((uv_handle_t *) &(ft->async));
|
||||
ft->mounted--;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NAPI_INIT() {
|
||||
const napi_node_version* version;
|
||||
assert(napi_get_node_version(env, &version) == napi_ok);
|
||||
|
||||
if (version->major > 12 || (version->major == 12 && version->minor >= 16)) {
|
||||
IS_ARRAY_BUFFER_DETACH_SUPPORTED = 1;
|
||||
}
|
||||
|
||||
pthread_key_create(&(thread_locals_key), NULL); // TODO: add destructor
|
||||
|
||||
NAPI_EXPORT_SIZEOF(fuse_thread_t)
|
||||
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_mount)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_unmount)
|
||||
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_getattr)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_init)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_access)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_statfs)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_fgetattr)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_getattr)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_flush)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_fsync)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_fsyncdir)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_readdir)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_truncate)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_ftruncate)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_utimens)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_readlink)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_chown)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_chmod)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_mknod)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_setxattr)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_getxattr)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_listxattr)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_removexattr)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_open)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_opendir)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_read)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_write)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_release)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_releasedir)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_create)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_unlink)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_rename)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_link)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_symlink)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_mkdir)
|
||||
NAPI_EXPORT_FUNCTION(fuse_native_signal_rmdir)
|
||||
|
||||
NAPI_EXPORT_UINT32(op_getattr)
|
||||
NAPI_EXPORT_UINT32(op_init)
|
||||
NAPI_EXPORT_UINT32(op_error)
|
||||
NAPI_EXPORT_UINT32(op_access)
|
||||
NAPI_EXPORT_UINT32(op_statfs)
|
||||
NAPI_EXPORT_UINT32(op_fgetattr)
|
||||
NAPI_EXPORT_UINT32(op_getattr)
|
||||
NAPI_EXPORT_UINT32(op_flush)
|
||||
NAPI_EXPORT_UINT32(op_fsync)
|
||||
NAPI_EXPORT_UINT32(op_fsyncdir)
|
||||
NAPI_EXPORT_UINT32(op_readdir)
|
||||
NAPI_EXPORT_UINT32(op_truncate)
|
||||
NAPI_EXPORT_UINT32(op_ftruncate)
|
||||
NAPI_EXPORT_UINT32(op_utimens)
|
||||
NAPI_EXPORT_UINT32(op_readlink)
|
||||
NAPI_EXPORT_UINT32(op_chown)
|
||||
NAPI_EXPORT_UINT32(op_chmod)
|
||||
NAPI_EXPORT_UINT32(op_mknod)
|
||||
NAPI_EXPORT_UINT32(op_setxattr)
|
||||
NAPI_EXPORT_UINT32(op_getxattr)
|
||||
NAPI_EXPORT_UINT32(op_listxattr)
|
||||
NAPI_EXPORT_UINT32(op_removexattr)
|
||||
NAPI_EXPORT_UINT32(op_open)
|
||||
NAPI_EXPORT_UINT32(op_opendir)
|
||||
NAPI_EXPORT_UINT32(op_read)
|
||||
NAPI_EXPORT_UINT32(op_write)
|
||||
NAPI_EXPORT_UINT32(op_release)
|
||||
NAPI_EXPORT_UINT32(op_releasedir)
|
||||
NAPI_EXPORT_UINT32(op_create)
|
||||
NAPI_EXPORT_UINT32(op_unlink)
|
||||
NAPI_EXPORT_UINT32(op_rename)
|
||||
NAPI_EXPORT_UINT32(op_link)
|
||||
NAPI_EXPORT_UINT32(op_symlink)
|
||||
NAPI_EXPORT_UINT32(op_mkdir)
|
||||
NAPI_EXPORT_UINT32(op_rmdir)
|
||||
}
|
1546
package-lock.json
generated
1546
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
29
package.json
29
package.json
@ -1,27 +1,30 @@
|
||||
{
|
||||
"name": "fuse-native",
|
||||
"version": "1.1.1",
|
||||
"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": {
|
||||
"install": "node-gyp-build",
|
||||
"test": "standard && tape test/*.js",
|
||||
"prebuild": "prebuildify -a --strip",
|
||||
"prebuild-ia32": "prebuildify -a --strip --arch=ia32"
|
||||
"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": {
|
||||
"fuse-shared-library": "^1.0.1",
|
||||
"nan": "^2.13.2",
|
||||
"napi-macros": "^1.8.2",
|
||||
"node-gyp-build": "^3.2.2",
|
||||
"xtend": "^4.0.1"
|
||||
"fuse-shared-library": "^1.0.2",
|
||||
"nanoresource": "^1.3.0",
|
||||
"napi-macros": "^2.0.0",
|
||||
"node-gyp-build": "^4.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concat-stream": "^1.4.7",
|
||||
"prebuildify": "^2.4.3",
|
||||
"standard": "^7.1.2",
|
||||
"tape": "^4.6.0"
|
||||
"concat-stream": "^2.0.0",
|
||||
"prebuildify": "^3.0.4",
|
||||
"standard": "^13.1.0",
|
||||
"tape": "^4.12.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
40
semaphore.h
Normal file
40
semaphore.h
Normal file
@ -0,0 +1,40 @@
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
#include <semaphore.h>
|
||||
#include <dispatch/dispatch.h>
|
||||
|
||||
typedef dispatch_semaphore_t fuse_native_semaphore_t;
|
||||
|
||||
static int fuse_native_semaphore_init (dispatch_semaphore_t *sem) {
|
||||
*sem = dispatch_semaphore_create(0);
|
||||
return *sem == NULL ? -1 : 0;
|
||||
}
|
||||
|
||||
static void fuse_native_semaphore_wait (dispatch_semaphore_t *sem) {
|
||||
dispatch_semaphore_wait(*sem, DISPATCH_TIME_FOREVER);
|
||||
}
|
||||
|
||||
static void fuse_native_semaphore_signal (dispatch_semaphore_t *sem) {
|
||||
dispatch_semaphore_signal(*sem);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <semaphore.h>
|
||||
|
||||
typedef sem_t fuse_native_semaphore_t;
|
||||
|
||||
static int fuse_native_semaphore_init (sem_t *sem) {
|
||||
return sem_init(sem, 0, 0);
|
||||
}
|
||||
|
||||
static void fuse_native_semaphore_wait (sem_t *sem) {
|
||||
sem_wait(sem);
|
||||
}
|
||||
|
||||
static void fuse_native_semaphore_signal (sem_t *sem) {
|
||||
sem_post(sem);
|
||||
}
|
||||
|
||||
#endif
|
116
test/big.js
Normal file
116
test/big.js
Normal file
@ -0,0 +1,116 @@
|
||||
const tape = require('tape')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const concat = require('concat-stream')
|
||||
|
||||
const Fuse = require('../')
|
||||
const createMountpoint = require('./fixtures/mnt')
|
||||
const stat = require('./fixtures/stat')
|
||||
const { unmount } = require('./helpers')
|
||||
|
||||
const mnt = createMountpoint()
|
||||
|
||||
tape('read and write big file', function (t) {
|
||||
let size = 0
|
||||
const reads = [0, 4 * 1024 * 1024 * 1024, 6 * 1024 * 1024 * 1024]
|
||||
const writes = [0, 4 * 1024 * 1024 * 1024, 6 * 1024 * 1024 * 1024]
|
||||
|
||||
var ops = {
|
||||
force: true,
|
||||
readdir (path, cb) {
|
||||
if (path === '/') return process.nextTick(cb, null, ['test'])
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
},
|
||||
getattr (path, cb) {
|
||||
if (path === '/') return process.nextTick(cb, null, stat({ mode: 'dir', size: 4096 }))
|
||||
if (path === '/test') return process.nextTick(cb, null, stat({ mode: 'file', size, mtime: new Date() }))
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
},
|
||||
open (path, flags, cb) {
|
||||
return process.nextTick(cb, 0, 42)
|
||||
},
|
||||
release (path, fd, cb) {
|
||||
t.same(fd, 42, 'fd was passed to release')
|
||||
return process.nextTick(cb, 0)
|
||||
},
|
||||
read (path, fd, buf, len, pos, cb) {
|
||||
t.same(pos, reads.shift(), 'read is expected')
|
||||
buf.fill(0)
|
||||
if (pos + len > size) return cb(Math.max(size - pos, 0))
|
||||
cb(len)
|
||||
},
|
||||
ftruncate (path, fd, len, cb) {
|
||||
size = len
|
||||
cb(0)
|
||||
},
|
||||
truncate (path, len, cb) {
|
||||
size = len
|
||||
cb(0)
|
||||
},
|
||||
write (path, fd, buf, len, pos, cb) {
|
||||
if (!writes.length) return cb(-1)
|
||||
t.same(pos, writes.shift(), 'write is expected')
|
||||
size = Math.max(pos + len, size)
|
||||
cb(len)
|
||||
}
|
||||
}
|
||||
|
||||
const fuse = new Fuse(mnt, ops, { debug: !true, autoCache: true })
|
||||
let fd = 0
|
||||
|
||||
run(
|
||||
(_, cb) => fuse.mount(cb),
|
||||
open('w+'),
|
||||
(_, cb) => fs.fstat(fd, cb),
|
||||
checkSize(0),
|
||||
(_, cb) => fs.ftruncate(fd, 4 * 1024 * 1024 * 1024 + 1, cb),
|
||||
(_, cb) => fs.fstat(fd, cb),
|
||||
checkSize(4 * 1024 * 1024 * 1024 + 1),
|
||||
(_, cb) => fs.truncate(path.join(mnt, 'test'), 6 * 1024 * 1024 * 1024 + 2, cb),
|
||||
(_, cb) => fs.fstat(fd, cb),
|
||||
checkSize(6 * 1024 * 1024 * 1024 + 2),
|
||||
(_, cb) => fs.write(fd, Buffer.alloc(4096), 0, 4096, 0, cb),
|
||||
(_, cb) => fs.write(fd, Buffer.alloc(4096), 0, 4096, 4 * 1024 * 1024 * 1024, cb),
|
||||
(_, cb) => fs.write(fd, Buffer.alloc(4096), 0, 4096, 6 * 1024 * 1024 * 1024, cb),
|
||||
(_, cb) => fs.fstat(fd, cb),
|
||||
checkSize(6 * 1024 * 1024 * 1024 + 4096),
|
||||
(_, cb) => fs.close(fd, cb),
|
||||
open('a+'),
|
||||
(_, cb) => fs.read(fd, Buffer.alloc(4096), 0, 4096, 0, cb),
|
||||
(_, cb) => fs.read(fd, Buffer.alloc(4096), 0, 4096, 4 * 1024 * 1024 * 1024, cb),
|
||||
(_, cb) => fs.read(fd, Buffer.alloc(4096), 0, 4096, 6 * 1024 * 1024 * 1024, cb),
|
||||
(_, cb) => fs.close(fd, cb),
|
||||
(_, cb) => unmount(fuse, cb),
|
||||
() => {
|
||||
t.same(writes.length, 0)
|
||||
t.same(reads.length, 0)
|
||||
t.end()
|
||||
}
|
||||
)
|
||||
|
||||
function open (mode) {
|
||||
return (_, cb) => {
|
||||
fs.open(path.join(mnt, 'test'), mode, function (_, res) {
|
||||
fd = res
|
||||
cb()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function checkSize (n) {
|
||||
return ({ size}, cb) => {
|
||||
t.same(size, n)
|
||||
cb()
|
||||
}
|
||||
}
|
||||
|
||||
function run (...fns) {
|
||||
const all = [...fns]
|
||||
tick()
|
||||
function tick (err, val) {
|
||||
t.error(err, 'no error')
|
||||
const next = all.shift()
|
||||
if (next) next(val, tick)
|
||||
}
|
||||
}
|
||||
})
|
18
test/fixtures/mnt.js
vendored
18
test/fixtures/mnt.js
vendored
@ -2,12 +2,18 @@ var os = require('os')
|
||||
var path = require('path')
|
||||
var fs = require('fs')
|
||||
|
||||
var mnt = path.join(os.tmpdir(), 'fuse-bindings-' + process.pid + '-' + Date.now())
|
||||
function create (opts = {}) {
|
||||
var mnt = path.join(os.tmpdir(), 'fuse-bindings-' + process.pid + '-' + Date.now())
|
||||
|
||||
try {
|
||||
fs.mkdirSync(mnt)
|
||||
} catch (err) {
|
||||
// do nothing
|
||||
if (!opts.doNotCreate) {
|
||||
try {
|
||||
fs.mkdirSync(mnt)
|
||||
} catch (err) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
return mnt
|
||||
}
|
||||
|
||||
module.exports = mnt
|
||||
module.exports = create
|
||||
|
34
test/fixtures/simple-fs.js
vendored
Normal file
34
test/fixtures/simple-fs.js
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
const stat = require('./stat')
|
||||
const Fuse = require('../../')
|
||||
|
||||
module.exports = function (tests = {}) {
|
||||
return {
|
||||
readdir: function (path, cb) {
|
||||
if (tests.readdir) tests.readdir(path)
|
||||
if (path === '/') return process.nextTick(cb, null, ['test'])
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
},
|
||||
getattr: function (path, cb) {
|
||||
if (tests.getattr) tests.getattr(path)
|
||||
if (path === '/') return process.nextTick(cb, null, stat({ mode: 'dir', size: 4096 }))
|
||||
if (path === '/test') return process.nextTick(cb, null, stat({ mode: 'file', size: 11 }))
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
},
|
||||
open: function (path, flags, cb) {
|
||||
if (tests.open) tests.open(path, flags)
|
||||
return process.nextTick(cb, 0, 42)
|
||||
},
|
||||
release: function (path, fd, cb) {
|
||||
if (tests.release) tests.release(path, fd)
|
||||
return process.nextTick(cb, 0)
|
||||
},
|
||||
read: function (path, fd, buf, len, pos, cb) {
|
||||
if (tests.read) tests.read(path, fd, buf, len, pos)
|
||||
var str = 'hello world'.slice(pos, pos + len)
|
||||
if (!str) return process.nextTick(cb, 0)
|
||||
buf.write(str)
|
||||
return process.nextTick(cb, str.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
6
test/helpers/index.js
Normal file
6
test/helpers/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
exports.unmount = function (fuse, cb) { // This only seems to be nessesary an the ancient osx we use on travis so ... yolo
|
||||
fuse.unmount(function (err) {
|
||||
if (err) return cb(err)
|
||||
setTimeout(cb, 1000)
|
||||
})
|
||||
}
|
@ -1,38 +1,43 @@
|
||||
var mnt = require('./fixtures/mnt')
|
||||
var stat = require('./fixtures/stat')
|
||||
var fuse = require('../')
|
||||
var tape = require('tape')
|
||||
var fs = require('fs')
|
||||
var path = require('path')
|
||||
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 cb(null, ['hello', 'link'])
|
||||
return cb(fuse.ENOENT)
|
||||
if (path === '/') return process.nextTick(cb, null, ['hello', 'link'])
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
},
|
||||
readlink: function (path, cb) {
|
||||
cb(0, 'hello')
|
||||
process.nextTick(cb, 0, 'hello')
|
||||
},
|
||||
getattr: function (path, cb) {
|
||||
if (path === '/') return cb(null, stat({mode: 'dir', size: 4096}))
|
||||
if (path === '/hello') return cb(null, stat({mode: 'file', size: 11}))
|
||||
if (path === '/link') return cb(null, stat({mode: 'link', size: 5}))
|
||||
return cb(fuse.ENOENT)
|
||||
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) {
|
||||
cb(0, 42)
|
||||
process.nextTick(cb, 0, 42)
|
||||
},
|
||||
read: function (path, fd, buf, len, pos, cb) {
|
||||
var str = 'hello world'.slice(pos, pos + len)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
fuse.mount(mnt, ops, function (err) {
|
||||
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) {
|
||||
@ -49,9 +54,9 @@ tape('readlink', function (t) {
|
||||
|
||||
fs.readFile(path.join(mnt, 'link'), function (err, buf) {
|
||||
t.error(err, 'no error')
|
||||
t.same(buf, new Buffer('hello world'), 'can read link content')
|
||||
t.same(buf, Buffer.from('hello world'), 'can read link content')
|
||||
|
||||
fuse.unmount(mnt, function () {
|
||||
unmount(fuse, function () {
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
|
187
test/misc.js
187
test/misc.js
@ -1,26 +1,39 @@
|
||||
var mnt = require('./fixtures/mnt')
|
||||
var fuse = require('../')
|
||||
var tape = require('tape')
|
||||
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) {
|
||||
fuse.mount(mnt, {force: true}, function (err) {
|
||||
const fuse = new Fuse(mnt, {}, { force: true })
|
||||
fuse.mount(function (err) {
|
||||
t.error(err, 'no error')
|
||||
t.ok(true, 'works')
|
||||
fuse.unmount(mnt, function () {
|
||||
unmount(fuse, function () {
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
tape('mount + unmount + mount', function (t) {
|
||||
fuse.mount(mnt, {force: true}, function (err) {
|
||||
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')
|
||||
fuse.unmount(mnt, function () {
|
||||
fuse.mount(mnt, {force: true}, function (err) {
|
||||
unmount(fuse1, function () {
|
||||
fuse2.mount(function (err) {
|
||||
t.error(err, 'no error')
|
||||
t.ok(true, 'works')
|
||||
fuse.unmount(mnt, function () {
|
||||
unmount(fuse2, function () {
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
@ -28,16 +41,168 @@ tape('mount + unmount + mount', function (t) {
|
||||
})
|
||||
})
|
||||
|
||||
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) {
|
||||
fuse.mount(mnt + '.does-not-exist', {}, function (err) {
|
||||
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) {
|
||||
fuse.mount(__filename, {}, function (err) {
|
||||
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'
|
||||
})
|
||||
}
|
||||
|
133
test/read.js
133
test/read.js
@ -1,56 +1,41 @@
|
||||
var mnt = require('./fixtures/mnt')
|
||||
var stat = require('./fixtures/stat')
|
||||
var fuse = require('../')
|
||||
var tape = require('tape')
|
||||
var fs = require('fs')
|
||||
var path = require('path')
|
||||
var concat = require('concat-stream')
|
||||
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) {
|
||||
var ops = {
|
||||
force: true,
|
||||
readdir: function (path, cb) {
|
||||
if (path === '/') return cb(null, ['test'])
|
||||
return cb(fuse.ENOENT)
|
||||
},
|
||||
getattr: function (path, cb) {
|
||||
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) {
|
||||
cb(0, 42)
|
||||
},
|
||||
release: function (path, fd, cb) {
|
||||
const testFS = simpleFS({
|
||||
release: function (path, fd) {
|
||||
t.same(fd, 42, 'fd was passed to release')
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
fuse.mount(mnt, ops, function (err) {
|
||||
})
|
||||
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, new Buffer('hello world'), 'read file')
|
||||
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, new Buffer('hello world'), 'read file again')
|
||||
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, new Buffer('hello'), 'partial read file')
|
||||
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, new Buffer('world'), 'partial read file + start offset')
|
||||
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')
|
||||
|
||||
fuse.unmount(mnt, function () {
|
||||
unmount(fuse, function () {
|
||||
t.end()
|
||||
})
|
||||
}))
|
||||
@ -59,3 +44,73 @@ tape('read', function (t) {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Skipped because this test takes 2 minutes to run.
|
||||
tape.skip('read timeout does not force unmount', function (t) {
|
||||
var ops = {
|
||||
force: true,
|
||||
readdir: function (path, cb) {
|
||||
if (path === '/') return process.nextTick(cb, null, ['test'])
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
},
|
||||
getattr: function (path, cb) {
|
||||
if (path === '/') return process.nextTick(cb, null, stat({ mode: 'dir', size: 4096 }))
|
||||
if (path === '/test') return process.nextTick(cb, null, stat({ mode: 'file', size: 11 }))
|
||||
if (path === '/timeout') return process.nextTick(cb, null, stat({ mode: 'file', size: 11 }))
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
},
|
||||
open: function (path, flags, cb) {
|
||||
return process.nextTick(cb, 0, 42)
|
||||
},
|
||||
release: function (path, fd, cb) {
|
||||
t.same(fd, 42, 'fd was passed to release')
|
||||
return process.nextTick(cb, 0)
|
||||
},
|
||||
read: function (path, fd, buf, len, pos, cb) {
|
||||
if (path === '/test') {
|
||||
var str = 'hello world'.slice(pos, pos + len)
|
||||
if (!str) return process.nextTick(cb, 0)
|
||||
buf.write(str)
|
||||
return process.nextTick(cb, str.length)
|
||||
} else if (path === '/timeout') {
|
||||
console.log('read is gonna time out')
|
||||
// Just let this one timeout
|
||||
setTimeout(cb, 20 * 1000, -2)
|
||||
return
|
||||
}
|
||||
return cb(-2)
|
||||
}
|
||||
}
|
||||
|
||||
const fuse = new Fuse(mnt, ops, { debug: false })
|
||||
fuse.mount(function (err) {
|
||||
t.error(err, 'no error')
|
||||
|
||||
fs.readFile(path.join(mnt, 'test'), function (err, buf) {
|
||||
t.error(err, 'no error')
|
||||
t.same(buf, Buffer.from('hello world'), 'read file')
|
||||
|
||||
// Start the read that will time out, wait a bit, then ensure that the second read works.
|
||||
console.time('timeout')
|
||||
fs.readFile(path.join(mnt, 'timeout'), function (err, buf) {
|
||||
console.timeEnd('timeout')
|
||||
console.log('the read timed out')
|
||||
t.true(err)
|
||||
})
|
||||
|
||||
// The default FUSE timeout is 2 minutes, so wait another second after the timeout.
|
||||
setTimeout(function () {
|
||||
console.log('reading from test')
|
||||
fs.readFile(path.join(mnt, 'test'), function (err, buf) {
|
||||
t.error(err, 'no error')
|
||||
t.same(buf, Buffer.from('hello world'), 'read file')
|
||||
unmount(fuse, function () {
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
}, 1000 * 121)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
40
test/statfs.js
Normal file
40
test/statfs.js
Normal file
@ -0,0 +1,40 @@
|
||||
const { exec } = require('child_process')
|
||||
const { unmount } = require('./helpers')
|
||||
const tape = require('tape')
|
||||
|
||||
const Fuse = require('../')
|
||||
const createMountpoint = require('./fixtures/mnt')
|
||||
const stat = require('./fixtures/stat')
|
||||
|
||||
const mnt = createMountpoint()
|
||||
|
||||
tape('statfs', function (t) {
|
||||
const ops = {
|
||||
force: true,
|
||||
statfs: function (path, cb) {
|
||||
return cb(0, {
|
||||
bsize: 1000000,
|
||||
frsize: 1000000,
|
||||
blocks: 1000000,
|
||||
bfree: 1000000,
|
||||
bavail: 1000000,
|
||||
files: 1000000,
|
||||
ffree: 1000000,
|
||||
favail: 1000000,
|
||||
fsid: 1000000,
|
||||
flag: 1000000,
|
||||
namemax: 1000000
|
||||
})
|
||||
},
|
||||
}
|
||||
const fuse = new Fuse(mnt, ops, { debug: true })
|
||||
fuse.mount(function (err) {
|
||||
t.error(err, 'no error')
|
||||
exec(`df ${mnt}`, (err) => {
|
||||
t.error(err, 'no error')
|
||||
unmount(fuse, function () {
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
@ -1,52 +1,57 @@
|
||||
var mnt = require('./fixtures/mnt')
|
||||
var stat = require('./fixtures/stat')
|
||||
var fuse = require('../')
|
||||
var tape = require('tape')
|
||||
var fs = require('fs')
|
||||
var path = require('path')
|
||||
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 = new Buffer(1024)
|
||||
var data = Buffer.alloc(1024)
|
||||
var size = 0
|
||||
|
||||
var ops = {
|
||||
force: true,
|
||||
readdir: function (path, cb) {
|
||||
if (path === '/') return cb(null, created ? ['hello'] : [])
|
||||
return cb(fuse.ENOENT)
|
||||
if (path === '/') return process.nextTick(cb, null, created ? ['hello'] : [], [])
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
},
|
||||
truncate: function (path, size, cb) {
|
||||
cb(0)
|
||||
process.nextTick(cb, 0)
|
||||
},
|
||||
getattr: function (path, cb) {
|
||||
if (path === '/') return cb(null, stat({mode: 'dir', size: 4096}))
|
||||
if (path === '/hello' && created) return cb(null, stat({mode: 'file', size: size}))
|
||||
return cb(fuse.ENOENT)
|
||||
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
|
||||
cb(0, 42)
|
||||
process.nextTick(cb, 0, 42)
|
||||
},
|
||||
release: function (path, fd, cb) {
|
||||
cb(0)
|
||||
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)
|
||||
cb(buf.length)
|
||||
process.nextTick(cb, buf.length)
|
||||
}
|
||||
}
|
||||
|
||||
fuse.mount(mnt, ops, function (err) {
|
||||
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), new Buffer('hello world'), 'data was written')
|
||||
t.same(data.slice(0, size), Buffer.from('hello world'), 'data was written')
|
||||
|
||||
fuse.unmount(mnt, function () {
|
||||
unmount(fuse, function () {
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user