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

24 changed files with 2570 additions and 3812 deletions

3
.gitignore vendored
View File

@ -1,5 +1,2 @@
node_modules
.nadconfig.mk
build
deps/
npm-debug.log

51
.travis.yml Normal file
View File

@ -0,0 +1,51 @@
language: node_js
sudo: required
osx_image: xcode8.3
node_js:
- node
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
- gcc-4.8-multilib
- g++-4.8-multilib
- gcc-multilib
- g++-multilib
os:
- osx
- linux
before_install:
- if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then cinst -y python2; fi
install:
- npm install
- npm run configure
before_deploy:
- ARCHIVE_NAME="${TRAVIS_TAG:-latest}-$TRAVIS_OS_NAME.tar"
- npm run prebuild
- if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then PREBUILD_ARCH=ia32 npm run prebuild; fi
- cd prebuilds && tar cvf "../$ARCHIVE_NAME" . && cd ..
cache:
npm: false
deploy:
provider: releases
draft: false
prerelease: true
api_key:
secure: "KPn3xR4LWcg/H259aSZh26XX0eapR88xSNUkBmEri/sCJSyZ0+asLZSv/HDD3KJP4HeuIKsQc0v8fcebD83fkvaSvlUzSppMQgniwuGC1cAefbrgZDwmJJ/n+lE8Wr9x4adOBTgICS5Uc8LlZ1PuJGm4mmequVs29BEw9738LzN4+3NpoCoWd0FAgGF0tDTsaYL1tJqERAyNIxHS+adUPe0F2r0d2UJ7mOrW7s8Ai6e6QryFsFvA2m0Xn/pQmNO/mcq+LPcw57pWuI3Hm3Cu3W8VPJXYp/yJaFqTAn3D9Fwz4tkmbfmca4ETwZYOS3lvL/rjLQ+69SJlRRu/QfPECkZven+wwsLX/DmyGHgEWqeGWjKj/NxYOIKUKEZZCVrF8cy4j9mac+LK6bAeDZERKSxUJ9GT5WsjvV3RNKgp3MZF7mtmj4IWXfgcuwFX49oIqhzSJsucBBXlB74J7Qua5VJPEAo/7X7Q+Y9IT9JHwxXsXVF5ZNj1PMlJicVD6oKi4XCFOVxSE9wdzlBwMOlUyBGhAIzS6lmxHOELYO9C7l8t/8Zvi4a+YGvOwn0dzLb9zuA1bzqJmEB1fkQMZXHvcEY1o5jSTQ0cNn1Wx4Ck9zyLyhnZ5KRXKzGQ1du55iVOThcbl/8j6zT218SiZMMtv8ZwPy4pJt4skMGsoOZtYlE="
file: "$ARCHIVE_NAME"
skip_cleanup: true
on:
tags: true
node: 'node'

21
LICENSE
View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Mathias Buus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

344
README.md
View File

@ -1,147 +1,101 @@
# fuse-bindings
# fuse-native
[![Build Status](https://travis-ci.org/fuse-friends/fuse-native.svg?branch=master)](https://travis-ci.org/fuse-friends/fuse-native)
Fully maintained fuse bindings for Node that aims to cover the entire FUSE api
Multithreaded FUSE bindings for Node JS.
## Features
* N-API support means we ship prebuilds and in general works on new Node.js releases.
* Multithreading support means multiple calls to FUSE can run in parallel.
* Close to feature complete in terms of the the FUSE API.
* Embedded shared library support means users do not have to install FUSE from a 3rd party.
* API support for initial FUSE kernel extension configuration so you can control the user experience.
## Installation
```
npm install fuse-bindings
npm i fuse-native --save
```
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.

View File

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

View File

@ -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
View File

@ -0,0 +1,20 @@
#!/usr/bin/env node
const Fuse = require('./')
const cmd = process.argv[2]
if (cmd === 'configure') {
Fuse.configure(onerror)
} else if (cmd === 'unconfigure') {
Fuse.unconfigure(onerror)
} else if (cmd === 'is-configured') {
Fuse.isConfigured(function (err, bool) {
if (err) return onerror(err)
console.log('' + bool)
process.exit(bool ? 0 : 1)
})
}
function onerror (err) {
if (err) throw err
}

View File

@ -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",

View File

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

File diff suppressed because it is too large Load Diff

989
fuse-native.c Normal file
View File

@ -0,0 +1,989 @@
#define FUSE_USE_VERSION 29
#include <uv.h>
#include <node_api.h>
#include <napi-macros.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <fuse.h>
#include <fuse_opt.h>
#include <fuse_common.h>
#include <fuse_lowlevel.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
static int IS_ARRAY_BUFFER_DETACH_SUPPORTED = 0;
napi_status napi_detach_arraybuffer(napi_env env, napi_value buf);
#define FUSE_NATIVE_CALLBACK(fn, blk)\
napi_env env = ft->env;\
napi_handle_scope scope;\
napi_open_handle_scope(env, &scope);\
napi_value ctx;\
napi_get_reference_value(env, ft->ctx, &ctx);\
napi_value callback;\
napi_get_reference_value(env, fn, &callback);\
blk\
napi_close_handle_scope(env, scope);
#define FUSE_NATIVE_HANDLER(name, blk)\
fuse_thread_locals_t *l = get_thread_locals();\
l->op = op_##name;\
l->op_fn = fuse_native_dispatch_##name;\
blk\
uv_async_send(&(l->async));\
uv_sem_wait(&(l->sem));\
return l->res;
#define FUSE_METHOD(name, callbackArgs, signalArgs, signature, callBlk, callbackBlk, signalBlk)\
static void fuse_native_dispatch_##name (uv_async_t* handle, fuse_thread_locals_t* l, fuse_thread_t* ft) {\
uint32_t op = op_##name;\
FUSE_NATIVE_CALLBACK(ft->handlers[op], {\
napi_value argv[callbackArgs + 2];\
napi_get_reference_value(env, l->self, &(argv[0]));\
napi_create_uint32(env, l->op, &(argv[1]));\
callbackBlk\
NAPI_MAKE_CALLBACK(env, NULL, ctx, callback, callbackArgs + 2, argv, NULL)\
})\
}\
NAPI_METHOD(fuse_native_signal_##name) {\
NAPI_ARGV(signalArgs + 2)\
NAPI_ARGV_BUFFER_CAST(fuse_thread_locals_t *, l, 0);\
NAPI_ARGV_INT32(res, 1);\
signalBlk\
l->res = res;\
uv_sem_post(&(l->sem));\
return NULL;\
}\
static int fuse_native_##name signature {\
FUSE_NATIVE_HANDLER(name, callBlk)\
}
#define FUSE_METHOD_VOID(name, callbackArgs, signalArgs, signature, callBlk, callbackBlk)\
FUSE_METHOD(name, callbackArgs, signalArgs, signature, callBlk, callbackBlk, {})
#define FUSE_UINT64_TO_INTS_ARGV(n, pos)\
uint32_t low##pos = n % 4294967296;\
uint32_t high##pos = (n - low##pos) / 4294967296;\
napi_create_uint32(env, low##pos, &(argv[pos]));\
napi_create_uint32(env, high##pos, &(argv[pos + 1]));
// Opcodes
static const uint32_t op_init = 0;
static const uint32_t op_error = 1;
static const uint32_t op_access = 2;
static const uint32_t op_statfs = 3;
static const uint32_t op_fgetattr = 4;
static const uint32_t op_getattr = 5;
static const uint32_t op_flush = 6;
static const uint32_t op_fsync = 7;
static const uint32_t op_fsyncdir = 8;
static const uint32_t op_readdir = 9;
static const uint32_t op_truncate = 10;
static const uint32_t op_ftruncate = 11;
static const uint32_t op_utimens = 12;
static const uint32_t op_readlink = 13;
static const uint32_t op_chown = 14;
static const uint32_t op_chmod = 15;
static const uint32_t op_mknod = 16;
static const uint32_t op_setxattr = 17;
static const uint32_t op_getxattr = 18;
static const uint32_t op_listxattr = 19;
static const uint32_t op_removexattr = 20;
static const uint32_t op_open = 21;
static const uint32_t op_opendir = 22;
static const uint32_t op_read = 23;
static const uint32_t op_write = 24;
static const uint32_t op_release = 25;
static const uint32_t op_releasedir = 26;
static const uint32_t op_create = 27;
static const uint32_t op_unlink = 28;
static const uint32_t op_rename = 29;
static const uint32_t op_link = 30;
static const uint32_t op_symlink = 31;
static const uint32_t op_mkdir = 32;
static const uint32_t op_rmdir = 33;
// Data structures
typedef struct {
napi_env env;
pthread_t thread;
pthread_attr_t attr;
napi_ref ctx;
napi_ref malloc;
// Operation handlers
napi_ref handlers[35];
struct fuse *fuse;
struct fuse_chan *ch;
char mnt[1024];
char mntopts[1024];
int mounted;
uv_async_t async;
uv_mutex_t mut;
uv_sem_t sem;
} fuse_thread_t;
typedef struct {
napi_ref self;
// Opcode
uint32_t op;
void *op_fn;
// Payloads
const char *path;
const char *dest;
char *linkname;
struct fuse_file_info *info;
const void *buf;
off_t offset;
size_t len;
mode_t mode;
dev_t dev;
uid_t uid;
gid_t gid;
size_t atime;
size_t mtime;
int32_t res;
// Extended attributes
const char *name;
const char *value;
char *list;
size_t size;
uint32_t position;
int flags;
// Stat + Statfs
struct stat *stat;
struct statvfs *statvfs;
// Readdir
fuse_fill_dir_t readdir_filler;
// Internal bookkeeping
fuse_thread_t *fuse;
uv_sem_t sem;
uv_async_t async;
} fuse_thread_locals_t;
static pthread_key_t thread_locals_key;
static fuse_thread_locals_t* get_thread_locals();
// Helpers
// TODO: Extract into a separate file.
static uint64_t uint32s_to_uint64 (uint32_t **ints) {
uint64_t low = *((*ints)++);
uint64_t high = *((*ints)++);
return high * 4294967296 + low;
}
static void uint32s_to_timespec (struct timespec* ts, uint32_t** ints) {
uint64_t ms = uint32s_to_uint64(ints);
ts->tv_sec = ms / 1000;
ts->tv_nsec = (ms % 1000) * 1000000;
}
static uint64_t timespec_to_uint64 (const struct timespec* ts) {
uint64_t ms = (ts->tv_sec * 1000) + (ts->tv_nsec / 1000000);
return ms;
}
static void populate_stat (uint32_t *ints, struct stat* stat) {
stat->st_mode = *ints++;
stat->st_uid = *ints++;
stat->st_gid = *ints++;
stat->st_size = uint32s_to_uint64(&ints);
stat->st_dev = *ints++;
stat->st_nlink = *ints++;
stat->st_ino = *ints++;
stat->st_rdev = *ints++;
stat->st_blksize = *ints++;
stat->st_blocks = uint32s_to_uint64(&ints);
#ifdef __APPLE__
uint32s_to_timespec(&stat->st_atimespec, &ints);
uint32s_to_timespec(&stat->st_mtimespec, &ints);
uint32s_to_timespec(&stat->st_ctimespec, &ints);
#else
uint32s_to_timespec(&stat->st_atim, &ints);
uint32s_to_timespec(&stat->st_mtim, &ints);
uint32s_to_timespec(&stat->st_ctim, &ints);
#endif
}
static void populate_statvfs (uint32_t *ints, struct statvfs* statvfs) {
statvfs->f_bsize = *ints++;
statvfs->f_frsize = *ints++;
statvfs->f_blocks = *ints++;
statvfs->f_bfree = *ints++;
statvfs->f_bavail = *ints++;
statvfs->f_files = *ints++;
statvfs->f_ffree = *ints++;
statvfs->f_favail = *ints++;
statvfs->f_fsid = *ints++;
statvfs->f_flag = *ints++;
statvfs->f_namemax = *ints++;
}
// Methods
FUSE_METHOD(statfs, 1, 1, (const char * path, struct statvfs *statvfs), {
l->path = path;
l->statvfs = statvfs;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
}, {
NAPI_ARGV_BUFFER_CAST(uint32_t*, ints, 2)
populate_statvfs(ints, l->statvfs);
})
FUSE_METHOD(getattr, 1, 1, (const char *path, struct stat *stat), {
l->path = path;
l->stat = stat;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
}, {
NAPI_ARGV_BUFFER_CAST(uint32_t*, ints, 2)
populate_stat(ints, l->stat);
})
FUSE_METHOD(fgetattr, 2, 1, (const char *path, struct stat *stat, struct fuse_file_info *info), {
l->path = path;
l->stat = stat;
l->info = info;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
if (l->info != NULL) {
napi_create_uint32(env, l->info->fh, &(argv[3]));
} else {
napi_create_uint32(env, 0, &(argv[3]));
}
}, {
NAPI_ARGV_BUFFER_CAST(uint32_t*, ints, 2)
populate_stat(ints, l->stat);
})
FUSE_METHOD_VOID(access, 2, 0, (const char *path, int mode), {
l->path = path;
l->mode = mode;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_uint32(env, l->mode, &(argv[3]));
})
FUSE_METHOD(open, 2, 1, (const char *path, struct fuse_file_info *info), {
l->path = path;
l->info = info;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
if (l->info != NULL) {
napi_create_uint32(env, l->info->flags, &(argv[3]));
} else {
napi_create_uint32(env, 0, &(argv[3]));
}
}, {
NAPI_ARGV_INT32(fd, 2)
if (fd != 0) {
l->info->fh = fd;
}
})
FUSE_METHOD(opendir, 3, 1, (const char *path, struct fuse_file_info *info), {
l->path = path;
l->info = info;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
if (l->info != NULL) {
napi_create_uint32(env, l->info->fh, &(argv[3]));
napi_create_uint32(env, l->info->flags, &(argv[4]));
} else {
napi_create_uint32(env, 0, &(argv[3]));
napi_create_uint32(env, 0, &(argv[4]));
}
}, {
NAPI_ARGV_INT32(fd, 2)
if (fd != 0) {
l->info->fh = fd;
}
})
FUSE_METHOD(create, 2, 1, (const char *path, mode_t mode, struct fuse_file_info *info), {
l->path = path;
l->mode = mode;
l->info = info;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_uint32(env, l->mode, &(argv[3]));
}, {
NAPI_ARGV_INT32(fd, 2)
if (fd != 0) {
l->info->fh = fd;
}
})
FUSE_METHOD_VOID(utimens, 5, 0, (const char *path, const struct timespec tv[2]), {
l->path = path;
l->atime = timespec_to_uint64(&tv[0]);
l->mtime = timespec_to_uint64(&tv[1]);
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
FUSE_UINT64_TO_INTS_ARGV(l->atime, 3)
FUSE_UINT64_TO_INTS_ARGV(l->atime, 5)
})
FUSE_METHOD_VOID(release, 2, 0, (const char *path, struct fuse_file_info *info), {
l->path = path;
l->info = info;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
if (l->info != NULL) {
napi_create_uint32(env, l->info->fh, &(argv[3]));
} else {
napi_create_uint32(env, 0, &(argv[3]));
}
})
FUSE_METHOD_VOID(releasedir, 2, 0, (const char *path, struct fuse_file_info *info), {
l->path = path;
l->info = info;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
if (l->info != NULL) {
napi_create_uint32(env, l->info->fh, &(argv[3]));
} else {
napi_create_uint32(env, 0, &(argv[3]));
}
})
FUSE_METHOD(read, 6, 2, (const char *path, char *buf, size_t len, off_t offset, struct fuse_file_info *info), {
l->path = path;
l->buf = buf;
l->len = len;
l->offset = offset;
l->info = info;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_uint32(env, l->info->fh, &(argv[3]));
napi_create_external_buffer(env, l->len, (char *) l->buf, NULL, NULL, &(argv[4]));
napi_create_uint32(env, l->len, &(argv[5]));
FUSE_UINT64_TO_INTS_ARGV(l->offset, 6)
}, {
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[3]) == napi_ok);
})
FUSE_METHOD(write, 6, 2, (const char *path, const char *buf, size_t len, off_t offset, struct fuse_file_info *info), {
l->path = path;
l->buf = buf;
l->len = len;
l->offset = offset;
l->info = info;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_uint32(env, l->info->fh, &(argv[3]));
napi_create_external_buffer(env, l->len, (char *) l->buf, NULL, NULL, &(argv[4]));
napi_create_uint32(env, l->len, &(argv[5]));
FUSE_UINT64_TO_INTS_ARGV(l->offset, 6)
}, {
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[3]) == napi_ok);
})
FUSE_METHOD(readdir, 1, 2, (const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *info), {
l->buf = buf;
l->path = path;
l->offset = offset;
l->info = info;
l->readdir_filler = filler;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
}, {
uint32_t stats_length;
uint32_t names_length;
napi_get_array_length(env, argv[3], &stats_length);
napi_get_array_length(env, argv[2], &names_length);
napi_value raw_names = argv[2];
napi_value raw_stats = argv[3];
if (names_length != stats_length) {
NAPI_FOR_EACH(raw_names, raw_name) {
NAPI_UTF8(name, 1024, raw_name)
int err = l->readdir_filler((char *) l->buf, name, NULL, 0);
if (err == 1) {
break;
}
}
} else {
NAPI_FOR_EACH(raw_names, raw_name) {
NAPI_UTF8(name, 1024, raw_name)
napi_value raw_stat;
napi_get_element(env, raw_stats, i, &raw_stat);
NAPI_BUFFER_CAST(uint32_t*, stats_array, raw_stat);
struct stat st;
populate_stat(stats_array, &st);
// TODO: It turns out readdirplus likely won't work with FUSE 29...
// Metadata caching between readdir/getattr will be enabled when we upgrade fuse-shared-library
int err = l->readdir_filler((char *) l->buf, name, (struct stat *) &st, 0);
if (err == 1) {
break;
}
}
}
})
#ifdef __APPLE__
FUSE_METHOD(setxattr, 5, 1, (const char *path, const char *name, const char *value, size_t size, int flags, uint32_t position), {
l->path = path;
l->name = name;
l->value = value;
l->size = size;
l->flags = flags;
l->position = position;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_string_utf8(env, l->name, NAPI_AUTO_LENGTH, &(argv[3]));
napi_create_external_buffer(env, l->size, (char *) l->value, NULL, NULL, &(argv[4]));
napi_create_uint32(env, l->position, &(argv[5]));
napi_create_uint32(env, l->flags, &(argv[6]));
}, {
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[2]) == napi_ok);
})
FUSE_METHOD(getxattr, 4, 1, (const char *path, const char *name, char *value, size_t size, uint32_t position), {
l->path = path;
l->name = name;
l->value = value;
l->size = size;
l->position = position;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_string_utf8(env, l->name, NAPI_AUTO_LENGTH, &(argv[3]));
napi_create_external_buffer(env, l->size, (char *) l->value, NULL, NULL, &(argv[4]));
napi_create_uint32(env, l->position, &(argv[5]));
}, {
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[2]) == napi_ok);
})
#else
FUSE_METHOD(setxattr, 5, 1, (const char *path, const char *name, const char *value, size_t size, int flags), {
l->path = path;
l->name = name;
l->value = value;
l->size = size;
l->flags = flags;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_string_utf8(env, l->name, NAPI_AUTO_LENGTH, &(argv[3]));
napi_create_external_buffer(env, l->size, (char *) l->value, NULL, NULL, &(argv[4]));
napi_create_uint32(env, 0, &(argv[5])); // normalize apis between mac and linux
napi_create_uint32(env, l->flags, &(argv[6]));
}, {
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[2]) == napi_ok);
})
FUSE_METHOD(getxattr, 4, 1, (const char *path, const char *name, char *value, size_t size), {
l->path = path;
l->name = name;
l->value = value;
l->size = size;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_string_utf8(env, l->name, NAPI_AUTO_LENGTH, &(argv[3]));
napi_create_external_buffer(env, l->size, (char *) l->value, NULL, NULL, &(argv[4]));
napi_create_uint32(env, 0, &(argv[5]));
}, {
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[2]) == napi_ok);
})
#endif
FUSE_METHOD(listxattr, 2, 1, (const char *path, char *list, size_t size), {
l->path = path;
l->list = list;
l->size = size;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_external_buffer(env, l->size, l->list, NULL, NULL, &(argv[3]));
}, {
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[2]) == napi_ok);
})
FUSE_METHOD_VOID(removexattr, 2, 0, (const char *path, const char *name), {
l->path = path;
l->name = name;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_string_utf8(env, l->name, NAPI_AUTO_LENGTH, &(argv[3]));
})
FUSE_METHOD_VOID(flush, 2, 0, (const char *path, struct fuse_file_info *info), {
l->path = path;
l->info = info;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
if (l->info != NULL) {
napi_create_uint32(env, l->info->fh, &(argv[3]));
} else {
napi_create_uint32(env, 0, &(argv[3]));
}
})
FUSE_METHOD_VOID(fsync, 3, 0, (const char *path, int datasync, struct fuse_file_info *info), {
l->path = path;
l->mode = datasync;
l->info = info;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_uint32(env, l->mode, &(argv[3]));
if (l->info != NULL) {
napi_create_uint32(env, l->info->fh, &(argv[4]));
} else {
napi_create_uint32(env, 0, &(argv[4]));
}
})
FUSE_METHOD_VOID(fsyncdir, 3, 0, (const char *path, int datasync, struct fuse_file_info *info), {
l->path = path;
l->mode = datasync;
l->info = info;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_uint32(env, l->mode, &(argv[3]));
if (l->info != NULL) {
napi_create_uint32(env, l->info->fh, &(argv[4]));
} else {
napi_create_uint32(env, 0, &(argv[4]));
}
})
FUSE_METHOD_VOID(truncate, 3, 0, (const char *path, off_t size), {
l->path = path;
l->offset = size;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
FUSE_UINT64_TO_INTS_ARGV(l->offset, 3)
})
FUSE_METHOD_VOID(ftruncate, 4, 0, (const char *path, off_t size, struct fuse_file_info *info), {
l->path = path;
l->offset = size;
l->info = info;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
if (l->info != NULL) {
napi_create_uint32(env, l->info->fh, &(argv[3]));
} else {
napi_create_uint32(env, 0, &(argv[3]));
}
FUSE_UINT64_TO_INTS_ARGV(l->offset, 4)
})
FUSE_METHOD(readlink, 1, 1, (const char *path, char *linkname, size_t len), {
l->path = path;
l->linkname = linkname;
l->len = len;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
}, {
NAPI_ARGV_UTF8(linkname, l->len, 2)
strncpy(l->linkname, linkname, l->len);
})
FUSE_METHOD_VOID(chown, 3, 0, (const char *path, uid_t uid, gid_t gid), {
l->path = path;
l->uid = uid;
l->gid = gid;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_uint32(env, l->uid, &(argv[3]));
napi_create_uint32(env, l->gid, &(argv[4]));
})
FUSE_METHOD_VOID(chmod, 2, 0, (const char *path, mode_t mode), {
l->path = path;
l->mode = mode;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_uint32(env, l->mode, &(argv[3]));
})
FUSE_METHOD_VOID(mknod, 3, 0, (const char *path, mode_t mode, dev_t dev), {
l->path = path;
l->mode = mode;
l->dev = dev;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_uint32(env, l->mode, &(argv[3]));
napi_create_uint32(env, l->dev, &(argv[4]));
})
FUSE_METHOD_VOID(unlink, 1, 0, (const char *path), {
l->path = path;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
})
FUSE_METHOD_VOID(rename, 2, 0, (const char *path, const char *dest), {
l->path = path;
l->dest = dest;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_string_utf8(env, l->dest, NAPI_AUTO_LENGTH, &(argv[3]));
})
FUSE_METHOD_VOID(link, 2, 0, (const char *path, const char *dest), {
l->path = path;
l->dest = dest;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_string_utf8(env, l->dest, NAPI_AUTO_LENGTH, &(argv[3]));
})
FUSE_METHOD_VOID(symlink, 2, 0, (const char *path, const char *dest), {
l->path = path;
l->dest = dest;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_string_utf8(env, l->dest, NAPI_AUTO_LENGTH, &(argv[3]));
})
FUSE_METHOD_VOID(mkdir, 2, 0, (const char *path, mode_t mode), {
l->path = path;
l->mode = mode;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_uint32(env, l->mode, &(argv[3]));
})
FUSE_METHOD_VOID(rmdir, 1, 0, (const char *path), {
l->path = path;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
})
static void fuse_native_dispatch_init (uv_async_t* handle, fuse_thread_locals_t* l, fuse_thread_t* ft) {\
FUSE_NATIVE_CALLBACK(ft->handlers[op_init], {
napi_value argv[2];
napi_get_reference_value(env, l->self, &(argv[0]));
napi_create_uint32(env, l->op, &(argv[1]));
NAPI_MAKE_CALLBACK(env, NULL, ctx, callback, 2, argv, NULL);
})
}
NAPI_METHOD(fuse_native_signal_init) {
NAPI_ARGV(2)
NAPI_ARGV_BUFFER_CAST(fuse_thread_locals_t *, l, 0);
NAPI_ARGV_INT32(res, 1);
l->res = res;
uv_sem_post(&(l->sem));
return NULL;
}
static void * fuse_native_init (struct fuse_conn_info *conn) {
fuse_thread_locals_t *l = get_thread_locals();
l->op = op_init;
l->op_fn = fuse_native_dispatch_init;
uv_async_send(&(l->async));
uv_sem_wait(&(l->sem));
return l->fuse;
}
// Top-level dispatcher
static void fuse_native_dispatch (uv_async_t* handle) {
fuse_thread_locals_t *l = (fuse_thread_locals_t *) handle->data;
fuse_thread_t *ft = l->fuse;
void (*fn)(uv_async_t *, fuse_thread_locals_t *, fuse_thread_t *) = l->op_fn;
fn(handle, l, ft);
}
static void fuse_native_async_init (uv_async_t* handle) {
fuse_thread_t *ft = (fuse_thread_t *) handle->data;
fuse_thread_locals_t *l;
FUSE_NATIVE_CALLBACK(ft->malloc, {
napi_value argv[1];
napi_create_uint32(ft->env, (uint32_t) sizeof(fuse_thread_locals_t), &(argv[0]));
napi_value buf;
NAPI_MAKE_CALLBACK(ft->env, NULL, ctx, callback, 1, argv, &buf);
size_t l_len;
napi_get_buffer_info(env, buf, (void **) &l, &l_len);
napi_create_reference(env, buf, 1, &(l->self));
})
int err = uv_async_init(uv_default_loop(), &(l->async), (uv_async_cb) fuse_native_dispatch);
assert(err >= 0);
uv_unref((uv_handle_t *) &(l->async));
uv_sem_init(&(l->sem), 0);
l->async.data = l;
ft->async.data = l;
l->fuse = ft;
uv_sem_post(&(ft->sem));
}
static fuse_thread_locals_t* get_thread_locals () {
struct fuse_context *ctx = fuse_get_context();
fuse_thread_t *ft = (fuse_thread_t *) ctx->private_data;
void *data = pthread_getspecific(thread_locals_key);
if (data != NULL) {
return (fuse_thread_locals_t *) data;
}
// Need to lock the mutation of l->async.
uv_mutex_lock(&(ft->mut));
ft->async.data = ft;
// Notify the main thread to uv_async_init l->async.
uv_async_send(&(ft->async));
uv_sem_wait(&(ft->sem));
fuse_thread_locals_t *l = (fuse_thread_locals_t*) ft->async.data;
pthread_setspecific(thread_locals_key, (void *) l);
uv_mutex_unlock(&(ft->mut));
return l;
}
static void* start_fuse_thread (void *data) {
fuse_thread_t *ft = (fuse_thread_t *) data;
fuse_loop_mt(ft->fuse);
fuse_unmount(ft->mnt, ft->ch);
fuse_session_remove_chan(ft->ch);
fuse_destroy(ft->fuse);
return NULL;
}
NAPI_METHOD(fuse_native_mount) {
NAPI_ARGV(7)
NAPI_ARGV_UTF8(mnt, 1024, 0);
NAPI_ARGV_UTF8(mntopts, 1024, 1);
NAPI_ARGV_BUFFER_CAST(fuse_thread_t *, ft, 2);
napi_create_reference(env, argv[3], 1, &(ft->ctx));
napi_create_reference(env, argv[4], 1, &(ft->malloc));
napi_value handlers = argv[5];
NAPI_ARGV_BUFFER_CAST(uint32_t *, implemented, 6)
for (int i = 0; i < 35; i++) {
ft->handlers[i] = NULL;
}
NAPI_FOR_EACH(handlers, handler) {
napi_create_reference(env, handler, 1, &ft->handlers[i]);
}
ft->env = env;
struct fuse_operations ops = { };
if (implemented[op_access]) ops.access = fuse_native_access;
if (implemented[op_truncate]) ops.truncate = fuse_native_truncate;
if (implemented[op_ftruncate]) ops.ftruncate = fuse_native_ftruncate;
if (implemented[op_getattr]) ops.getattr = fuse_native_getattr;
if (implemented[op_fgetattr]) ops.fgetattr = fuse_native_fgetattr;
if (implemented[op_flush]) ops.flush = fuse_native_flush;
if (implemented[op_fsync]) ops.fsync = fuse_native_fsync;
if (implemented[op_fsyncdir]) ops.fsyncdir = fuse_native_fsyncdir;
if (implemented[op_readdir]) ops.readdir = fuse_native_readdir;
if (implemented[op_readlink]) ops.readlink = fuse_native_readlink;
if (implemented[op_chown]) ops.chown = fuse_native_chown;
if (implemented[op_chmod]) ops.chmod = fuse_native_chmod;
if (implemented[op_mknod]) ops.mknod = fuse_native_mknod;
if (implemented[op_setxattr]) ops.setxattr = fuse_native_setxattr;
if (implemented[op_getxattr]) ops.getxattr = fuse_native_getxattr;
if (implemented[op_listxattr]) ops.listxattr = fuse_native_listxattr;
if (implemented[op_removexattr]) ops.removexattr = fuse_native_removexattr;
if (implemented[op_statfs]) ops.statfs = fuse_native_statfs;
if (implemented[op_open]) ops.open = fuse_native_open;
if (implemented[op_opendir]) ops.opendir = fuse_native_opendir;
if (implemented[op_read]) ops.read = fuse_native_read;
if (implemented[op_write]) ops.write = fuse_native_write;
if (implemented[op_release]) ops.release = fuse_native_release;
if (implemented[op_releasedir]) ops.releasedir = fuse_native_releasedir;
if (implemented[op_create]) ops.create = fuse_native_create;
if (implemented[op_utimens]) ops.utimens = fuse_native_utimens;
if (implemented[op_unlink]) ops.unlink = fuse_native_unlink;
if (implemented[op_rename]) ops.rename = fuse_native_rename;
if (implemented[op_link]) ops.link = fuse_native_link;
if (implemented[op_symlink]) ops.symlink = fuse_native_symlink;
if (implemented[op_mkdir]) ops.mkdir = fuse_native_mkdir;
if (implemented[op_rmdir]) ops.rmdir = fuse_native_rmdir;
if (implemented[op_init]) ops.init = fuse_native_init;
int _argc = (strcmp(mntopts, "-o") <= 0) ? 1 : 2;
char *_argv[] = {
(char *) "fuse_bindings_dummy",
(char *) mntopts
};
struct fuse_args args = FUSE_ARGS_INIT(_argc, _argv);
struct fuse_chan *ch = fuse_mount(mnt, &args);
if (ch == NULL) {
napi_throw_error(env, "fuse failed", "fuse failed");
return NULL;
}
struct fuse *fuse = fuse_new(ch, &args, &ops, sizeof(struct fuse_operations), ft);
uv_mutex_init(&(ft->mut));
uv_sem_init(&(ft->sem), 0);
strncpy(ft->mnt, mnt, 1024);
strncpy(ft->mntopts, mntopts, 1024);
ft->fuse = fuse;
ft->ch = ch;
ft->mounted++;
int err = uv_async_init(uv_default_loop(), &(ft->async), (uv_async_cb) fuse_native_async_init);
if (fuse == NULL || err < 0) {
napi_throw_error(env, "fuse failed", "fuse failed");
return NULL;
}
pthread_attr_init(&(ft->attr));
pthread_create(&(ft->thread), &(ft->attr), start_fuse_thread, ft);
return NULL;
}
NAPI_METHOD(fuse_native_unmount) {
NAPI_ARGV(2)
NAPI_ARGV_UTF8(mnt, 1024, 0);
NAPI_ARGV_BUFFER_CAST(fuse_thread_t *, ft, 1);
if (ft != NULL && ft->mounted) {
// TODO: Investigate why the FUSE thread is not always killed after fusermount.
// pthread_join(ft->thread, NULL);
}
// TODO: fix the async holding the loop
uv_unref((uv_handle_t *) &(ft->async));
ft->mounted--;
return NULL;
}
NAPI_INIT() {
const napi_node_version* version;
assert(napi_get_node_version(env, &version) == napi_ok);
if (version->major > 12 || (version->major == 12 && version->minor >= 16)) {
IS_ARRAY_BUFFER_DETACH_SUPPORTED = 1;
}
pthread_key_create(&(thread_locals_key), NULL); // TODO: add destructor
NAPI_EXPORT_SIZEOF(fuse_thread_t)
NAPI_EXPORT_FUNCTION(fuse_native_mount)
NAPI_EXPORT_FUNCTION(fuse_native_unmount)
NAPI_EXPORT_FUNCTION(fuse_native_signal_getattr)
NAPI_EXPORT_FUNCTION(fuse_native_signal_init)
NAPI_EXPORT_FUNCTION(fuse_native_signal_access)
NAPI_EXPORT_FUNCTION(fuse_native_signal_statfs)
NAPI_EXPORT_FUNCTION(fuse_native_signal_fgetattr)
NAPI_EXPORT_FUNCTION(fuse_native_signal_getattr)
NAPI_EXPORT_FUNCTION(fuse_native_signal_flush)
NAPI_EXPORT_FUNCTION(fuse_native_signal_fsync)
NAPI_EXPORT_FUNCTION(fuse_native_signal_fsyncdir)
NAPI_EXPORT_FUNCTION(fuse_native_signal_readdir)
NAPI_EXPORT_FUNCTION(fuse_native_signal_truncate)
NAPI_EXPORT_FUNCTION(fuse_native_signal_ftruncate)
NAPI_EXPORT_FUNCTION(fuse_native_signal_utimens)
NAPI_EXPORT_FUNCTION(fuse_native_signal_readlink)
NAPI_EXPORT_FUNCTION(fuse_native_signal_chown)
NAPI_EXPORT_FUNCTION(fuse_native_signal_chmod)
NAPI_EXPORT_FUNCTION(fuse_native_signal_mknod)
NAPI_EXPORT_FUNCTION(fuse_native_signal_setxattr)
NAPI_EXPORT_FUNCTION(fuse_native_signal_getxattr)
NAPI_EXPORT_FUNCTION(fuse_native_signal_listxattr)
NAPI_EXPORT_FUNCTION(fuse_native_signal_removexattr)
NAPI_EXPORT_FUNCTION(fuse_native_signal_open)
NAPI_EXPORT_FUNCTION(fuse_native_signal_opendir)
NAPI_EXPORT_FUNCTION(fuse_native_signal_read)
NAPI_EXPORT_FUNCTION(fuse_native_signal_write)
NAPI_EXPORT_FUNCTION(fuse_native_signal_release)
NAPI_EXPORT_FUNCTION(fuse_native_signal_releasedir)
NAPI_EXPORT_FUNCTION(fuse_native_signal_create)
NAPI_EXPORT_FUNCTION(fuse_native_signal_unlink)
NAPI_EXPORT_FUNCTION(fuse_native_signal_rename)
NAPI_EXPORT_FUNCTION(fuse_native_signal_link)
NAPI_EXPORT_FUNCTION(fuse_native_signal_symlink)
NAPI_EXPORT_FUNCTION(fuse_native_signal_mkdir)
NAPI_EXPORT_FUNCTION(fuse_native_signal_rmdir)
NAPI_EXPORT_UINT32(op_getattr)
NAPI_EXPORT_UINT32(op_init)
NAPI_EXPORT_UINT32(op_error)
NAPI_EXPORT_UINT32(op_access)
NAPI_EXPORT_UINT32(op_statfs)
NAPI_EXPORT_UINT32(op_fgetattr)
NAPI_EXPORT_UINT32(op_getattr)
NAPI_EXPORT_UINT32(op_flush)
NAPI_EXPORT_UINT32(op_fsync)
NAPI_EXPORT_UINT32(op_fsyncdir)
NAPI_EXPORT_UINT32(op_readdir)
NAPI_EXPORT_UINT32(op_truncate)
NAPI_EXPORT_UINT32(op_ftruncate)
NAPI_EXPORT_UINT32(op_utimens)
NAPI_EXPORT_UINT32(op_readlink)
NAPI_EXPORT_UINT32(op_chown)
NAPI_EXPORT_UINT32(op_chmod)
NAPI_EXPORT_UINT32(op_mknod)
NAPI_EXPORT_UINT32(op_setxattr)
NAPI_EXPORT_UINT32(op_getxattr)
NAPI_EXPORT_UINT32(op_listxattr)
NAPI_EXPORT_UINT32(op_removexattr)
NAPI_EXPORT_UINT32(op_open)
NAPI_EXPORT_UINT32(op_opendir)
NAPI_EXPORT_UINT32(op_read)
NAPI_EXPORT_UINT32(op_write)
NAPI_EXPORT_UINT32(op_release)
NAPI_EXPORT_UINT32(op_releasedir)
NAPI_EXPORT_UINT32(op_create)
NAPI_EXPORT_UINT32(op_unlink)
NAPI_EXPORT_UINT32(op_rename)
NAPI_EXPORT_UINT32(op_link)
NAPI_EXPORT_UINT32(op_symlink)
NAPI_EXPORT_UINT32(op_mkdir)
NAPI_EXPORT_UINT32(op_rmdir)
}

1062
index.js

File diff suppressed because it is too large Load Diff

1546
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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
View File

@ -0,0 +1,40 @@
#ifdef __APPLE__
#include <semaphore.h>
#include <dispatch/dispatch.h>
typedef dispatch_semaphore_t fuse_native_semaphore_t;
static int fuse_native_semaphore_init (dispatch_semaphore_t *sem) {
*sem = dispatch_semaphore_create(0);
return *sem == NULL ? -1 : 0;
}
static void fuse_native_semaphore_wait (dispatch_semaphore_t *sem) {
dispatch_semaphore_wait(*sem, DISPATCH_TIME_FOREVER);
}
static void fuse_native_semaphore_signal (dispatch_semaphore_t *sem) {
dispatch_semaphore_signal(*sem);
}
#else
#include <semaphore.h>
typedef sem_t fuse_native_semaphore_t;
static int fuse_native_semaphore_init (sem_t *sem) {
return sem_init(sem, 0, 0);
}
static void fuse_native_semaphore_wait (sem_t *sem) {
sem_wait(sem);
}
static void fuse_native_semaphore_signal (sem_t *sem) {
sem_post(sem);
}
#endif

116
test/big.js Normal file
View File

@ -0,0 +1,116 @@
const tape = require('tape')
const fs = require('fs')
const path = require('path')
const concat = require('concat-stream')
const Fuse = require('../')
const createMountpoint = require('./fixtures/mnt')
const stat = require('./fixtures/stat')
const { unmount } = require('./helpers')
const mnt = createMountpoint()
tape('read and write big file', function (t) {
let size = 0
const reads = [0, 4 * 1024 * 1024 * 1024, 6 * 1024 * 1024 * 1024]
const writes = [0, 4 * 1024 * 1024 * 1024, 6 * 1024 * 1024 * 1024]
var ops = {
force: true,
readdir (path, cb) {
if (path === '/') return process.nextTick(cb, null, ['test'])
return process.nextTick(cb, Fuse.ENOENT)
},
getattr (path, cb) {
if (path === '/') return process.nextTick(cb, null, stat({ mode: 'dir', size: 4096 }))
if (path === '/test') return process.nextTick(cb, null, stat({ mode: 'file', size, mtime: new Date() }))
return process.nextTick(cb, Fuse.ENOENT)
},
open (path, flags, cb) {
return process.nextTick(cb, 0, 42)
},
release (path, fd, cb) {
t.same(fd, 42, 'fd was passed to release')
return process.nextTick(cb, 0)
},
read (path, fd, buf, len, pos, cb) {
t.same(pos, reads.shift(), 'read is expected')
buf.fill(0)
if (pos + len > size) return cb(Math.max(size - pos, 0))
cb(len)
},
ftruncate (path, fd, len, cb) {
size = len
cb(0)
},
truncate (path, len, cb) {
size = len
cb(0)
},
write (path, fd, buf, len, pos, cb) {
if (!writes.length) return cb(-1)
t.same(pos, writes.shift(), 'write is expected')
size = Math.max(pos + len, size)
cb(len)
}
}
const fuse = new Fuse(mnt, ops, { debug: !true, autoCache: true })
let fd = 0
run(
(_, cb) => fuse.mount(cb),
open('w+'),
(_, cb) => fs.fstat(fd, cb),
checkSize(0),
(_, cb) => fs.ftruncate(fd, 4 * 1024 * 1024 * 1024 + 1, cb),
(_, cb) => fs.fstat(fd, cb),
checkSize(4 * 1024 * 1024 * 1024 + 1),
(_, cb) => fs.truncate(path.join(mnt, 'test'), 6 * 1024 * 1024 * 1024 + 2, cb),
(_, cb) => fs.fstat(fd, cb),
checkSize(6 * 1024 * 1024 * 1024 + 2),
(_, cb) => fs.write(fd, Buffer.alloc(4096), 0, 4096, 0, cb),
(_, cb) => fs.write(fd, Buffer.alloc(4096), 0, 4096, 4 * 1024 * 1024 * 1024, cb),
(_, cb) => fs.write(fd, Buffer.alloc(4096), 0, 4096, 6 * 1024 * 1024 * 1024, cb),
(_, cb) => fs.fstat(fd, cb),
checkSize(6 * 1024 * 1024 * 1024 + 4096),
(_, cb) => fs.close(fd, cb),
open('a+'),
(_, cb) => fs.read(fd, Buffer.alloc(4096), 0, 4096, 0, cb),
(_, cb) => fs.read(fd, Buffer.alloc(4096), 0, 4096, 4 * 1024 * 1024 * 1024, cb),
(_, cb) => fs.read(fd, Buffer.alloc(4096), 0, 4096, 6 * 1024 * 1024 * 1024, cb),
(_, cb) => fs.close(fd, cb),
(_, cb) => unmount(fuse, cb),
() => {
t.same(writes.length, 0)
t.same(reads.length, 0)
t.end()
}
)
function open (mode) {
return (_, cb) => {
fs.open(path.join(mnt, 'test'), mode, function (_, res) {
fd = res
cb()
})
}
}
function checkSize (n) {
return ({ size}, cb) => {
t.same(size, n)
cb()
}
}
function run (...fns) {
const all = [...fns]
tick()
function tick (err, val) {
t.error(err, 'no error')
const next = all.shift()
if (next) next(val, tick)
}
}
})

18
test/fixtures/mnt.js vendored
View File

@ -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
View File

@ -0,0 +1,34 @@
const stat = require('./stat')
const Fuse = require('../../')
module.exports = function (tests = {}) {
return {
readdir: function (path, cb) {
if (tests.readdir) tests.readdir(path)
if (path === '/') return process.nextTick(cb, null, ['test'])
return process.nextTick(cb, Fuse.ENOENT)
},
getattr: function (path, cb) {
if (tests.getattr) tests.getattr(path)
if (path === '/') return process.nextTick(cb, null, stat({ mode: 'dir', size: 4096 }))
if (path === '/test') return process.nextTick(cb, null, stat({ mode: 'file', size: 11 }))
return process.nextTick(cb, Fuse.ENOENT)
},
open: function (path, flags, cb) {
if (tests.open) tests.open(path, flags)
return process.nextTick(cb, 0, 42)
},
release: function (path, fd, cb) {
if (tests.release) tests.release(path, fd)
return process.nextTick(cb, 0)
},
read: function (path, fd, buf, len, pos, cb) {
if (tests.read) tests.read(path, fd, buf, len, pos)
var str = 'hello world'.slice(pos, pos + len)
if (!str) return process.nextTick(cb, 0)
buf.write(str)
return process.nextTick(cb, str.length)
}
}
}

6
test/helpers/index.js Normal file
View File

@ -0,0 +1,6 @@
exports.unmount = function (fuse, cb) { // This only seems to be nessesary an the ancient osx we use on travis so ... yolo
fuse.unmount(function (err) {
if (err) return cb(err)
setTimeout(cb, 1000)
})
}

View File

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

View File

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

View File

@ -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
View File

@ -0,0 +1,40 @@
const { exec } = require('child_process')
const { unmount } = require('./helpers')
const tape = require('tape')
const Fuse = require('../')
const createMountpoint = require('./fixtures/mnt')
const stat = require('./fixtures/stat')
const mnt = createMountpoint()
tape('statfs', function (t) {
const ops = {
force: true,
statfs: function (path, cb) {
return cb(0, {
bsize: 1000000,
frsize: 1000000,
blocks: 1000000,
bfree: 1000000,
bavail: 1000000,
files: 1000000,
ffree: 1000000,
favail: 1000000,
fsid: 1000000,
flag: 1000000,
namemax: 1000000
})
},
}
const fuse = new Fuse(mnt, ops, { debug: true })
fuse.mount(function (err) {
t.error(err, 'no error')
exec(`df ${mnt}`, (err) => {
t.error(err, 'no error')
unmount(fuse, function () {
t.end()
})
})
})
})

View File

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