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. "master" and "v2.0.0" have entirely different histories.

15 changed files with 200 additions and 794 deletions

View File

@ -33,19 +33,5 @@ before_deploy:
- ARCHIVE_NAME="${TRAVIS_TAG:-latest}-$TRAVIS_OS_NAME.tar" - ARCHIVE_NAME="${TRAVIS_TAG:-latest}-$TRAVIS_OS_NAME.tar"
- npm run prebuild - npm run prebuild
- if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then PREBUILD_ARCH=ia32 npm run prebuild; fi - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then PREBUILD_ARCH=ia32 npm run prebuild; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then PREBUILD_ARCH=ia32 npm run prebuild; fi
- cd prebuilds && tar cvf "../$ARCHIVE_NAME" . && cd .. - 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'

View File

@ -1,5 +1,4 @@
# fuse-native # fuse-native
[![Build Status](https://travis-ci.org/fuse-friends/fuse-native.svg?branch=master)](https://travis-ci.org/fuse-friends/fuse-native)
Multithreaded FUSE bindings for Node JS. Multithreaded FUSE bindings for Node JS.
@ -73,28 +72,9 @@ Create a new `Fuse` object.
``` ```
displayFolder: 'Folder Name', // Add a name/icon to the mount volume on OSX, displayFolder: 'Folder Name', // Add a name/icon to the mount volume on OSX,
debug: false, // Enable detailed tracing of operations. 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). Additionally, all (FUSE-specific options)[http://man7.org/linux/man-pages/man8/mount.fuse.8.html] will be passed to the underlying FUSE module (though we use camel casing instead of snake casing).
#### `Fuse.isConfigured(cb)`
Returns `true` if FUSE has been configured on your machine and ready to be used, `false` otherwise.
#### `Fuse.configure(cb)`
Configures FUSE on your machine by enabling the FUSE kernel extension.
You usually want to do this as part of an installation phase for your app.
Might require `sudo` access.
#### `Fuse.unconfigure(cb)`
Unconfigures FUSE on your machine. Basically undos any change the above
method does.
See the CLI section below on how to run these commands from the command line if you prefer doing that.
### FUSE API ### 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. 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.
@ -202,30 +182,21 @@ Called when the mode of a path is being changed
Called when the a new device file is being made. Called when the a new device file is being made.
#### `ops.setxattr(path, name, value, position, flags, cb)` #### `ops.setxattr(path, name, buffer, length, offset, flags, cb)`
Called when extended attributes is being set (see the extended docs for your platform). 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`.
Copy the `value` buffer somewhere to store it. #### `ops.getxattr(path, name, buffer, length, offset, cb)`
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. Called when extended attributes is being read.
Currently you have to write the result to the provided `buffer` at `offset`.
Return the extended attribute as the second argument to the callback (needs to be a buffer). #### `ops.listxattr(path, buffer, length, cb)`
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. 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.)
Return a list of strings of the names of the attributes you have stored as the second argument to the callback. 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.
#### `ops.removexattr(path, name, cb)` #### `ops.removexattr(path, name, cb)`
@ -321,17 +292,6 @@ Called when a new directory is being created
Called when a directory is being removed Called when a directory is being removed
## CLI
There is a CLI tool available to help you configure the FUSE kernel extension setup
if you don't want to use the JavaScript API for that
```
npm install -g fuse-native
fuse-native is-configured # checks if the kernel extension is already configured
fuse-native configure # configures the kernel extension
```
## License ## License
MIT for these bindings. MIT for these bindings.

View File

@ -11,18 +11,7 @@
"sources": [ "sources": [
"fuse-native.c" "fuse-native.c"
], ],
'xcode_settings': { "cflags": ["-rdynamic"]
'OTHER_CFLAGS': [
'-g',
'-O3',
'-Wall'
]
},
'cflags': [
'-g',
'-O3',
'-Wall'
],
}, { }, {
"target_name": "postinstall", "target_name": "postinstall",
"type": "none", "type": "none",

View File

@ -65,7 +65,7 @@ const ops = {
} }
} }
const fuse = new Fuse('./mnt', ops, { debug: true, displayFolder: true }) const fuse = new Fuse('./mnt', ops, { debug: true })
fuse.mount(err => { fuse.mount(err => {
if (err) throw err if (err) throw err
console.log('filesystem mounted on ' + fuse.mnt) console.log('filesystem mounted on ' + fuse.mnt)

View File

@ -18,10 +18,6 @@
#include <sys/wait.h> #include <sys/wait.h>
#include <pthread.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)\ #define FUSE_NATIVE_CALLBACK(fn, blk)\
napi_env env = ft->env;\ napi_env env = ft->env;\
napi_handle_scope scope;\ napi_handle_scope scope;\
@ -47,7 +43,7 @@ napi_status napi_detach_arraybuffer(napi_env env, napi_value buf);
uint32_t op = op_##name;\ uint32_t op = op_##name;\
FUSE_NATIVE_CALLBACK(ft->handlers[op], {\ FUSE_NATIVE_CALLBACK(ft->handlers[op], {\
napi_value argv[callbackArgs + 2];\ napi_value argv[callbackArgs + 2];\
napi_get_reference_value(env, l->self, &(argv[0]));\ napi_create_external_buffer(env, sizeof(fuse_thread_locals_t), l, &fin, NULL, &(argv[0]));\
napi_create_uint32(env, l->op, &(argv[1]));\ napi_create_uint32(env, l->op, &(argv[1]));\
callbackBlk\ callbackBlk\
NAPI_MAKE_CALLBACK(env, NULL, ctx, callback, callbackArgs + 2, argv, NULL)\ NAPI_MAKE_CALLBACK(env, NULL, ctx, callback, callbackArgs + 2, argv, NULL)\
@ -69,13 +65,6 @@ napi_status napi_detach_arraybuffer(napi_env env, napi_value buf);
#define FUSE_METHOD_VOID(name, callbackArgs, signalArgs, signature, callBlk, callbackBlk)\ #define FUSE_METHOD_VOID(name, callbackArgs, signalArgs, signature, callBlk, callbackBlk)\
FUSE_METHOD(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 // Opcodes
static const uint32_t op_init = 0; static const uint32_t op_init = 0;
@ -120,7 +109,6 @@ typedef struct {
pthread_t thread; pthread_t thread;
pthread_attr_t attr; pthread_attr_t attr;
napi_ref ctx; napi_ref ctx;
napi_ref malloc;
// Operation handlers // Operation handlers
napi_ref handlers[35]; napi_ref handlers[35];
@ -137,8 +125,6 @@ typedef struct {
} fuse_thread_t; } fuse_thread_t;
typedef struct { typedef struct {
napi_ref self;
// Opcode // Opcode
uint32_t op; uint32_t op;
void *op_fn; void *op_fn;
@ -155,8 +141,8 @@ typedef struct {
dev_t dev; dev_t dev;
uid_t uid; uid_t uid;
gid_t gid; gid_t gid;
size_t atime; uint32_t atim[2];
size_t mtime; uint32_t mtim[2];
int32_t res; int32_t res;
// Extended attributes // Extended attributes
@ -182,62 +168,61 @@ typedef struct {
} fuse_thread_locals_t; } fuse_thread_locals_t;
static pthread_key_t thread_locals_key; static pthread_key_t thread_locals_key;
static fuse_thread_locals_t* get_thread_locals(); static fuse_thread_locals_t* get_thread_locals ();
// Helpers // Helpers
// TODO: Extract into a separate file. // TODO: Extract into a separate file.
static uint64_t uint32s_to_uint64 (uint32_t **ints) { static void fin (napi_env env, void *fin_data, void* fin_hint) {
uint64_t low = *((*ints)++); //exit(0);
uint64_t high = *((*ints)++);
return high * 4294967296 + low;
} }
static void uint32s_to_timespec (struct timespec* ts, uint32_t** ints) { static void to_timespec (struct timespec* ts, uint32_t* int_ptr) {
uint64_t ms = uint32s_to_uint64(ints); long unsigned int ms = *int_ptr + (*(int_ptr + 1) * 4294967296);
ts->tv_sec = ms / 1000; ts->tv_sec = ms / 1000;
ts->tv_nsec = (ms % 1000) * 1000000; ts->tv_nsec = (ms % 1000) * 1000000;
} }
static uint64_t timespec_to_uint64 (const struct timespec* ts) { static void from_timespec(const struct timespec* ts, uint32_t* int_ptr) {
uint64_t ms = (ts->tv_sec * 1000) + (ts->tv_nsec / 1000000); long unsigned int ms = (ts->tv_sec * 1000) + (ts->tv_nsec / 1000000);
return ms; *int_ptr = ms % 4294967296;
*(int_ptr + 1) = (ms - *int_ptr) / 4294967296;
} }
static void populate_stat (uint32_t *ints, struct stat* stat) { static void populate_stat (uint32_t *ints, struct stat* stat) {
stat->st_mode = *ints++; stat->st_mode = *ints++;
stat->st_uid = *ints++; stat->st_uid = *ints++;
stat->st_gid = *ints++; stat->st_gid = *ints++;
stat->st_size = uint32s_to_uint64(&ints); stat->st_size = *ints++;
stat->st_dev = *ints++; stat->st_dev = *ints++;
stat->st_nlink = *ints++; stat->st_nlink = *ints++;
stat->st_ino = *ints++; stat->st_ino = *ints++;
stat->st_rdev = *ints++; stat->st_rdev = *ints++;
stat->st_blksize = *ints++; stat->st_blksize = *ints++;
stat->st_blocks = uint32s_to_uint64(&ints); stat->st_blocks = *ints++;
#ifdef __APPLE__ #ifdef __APPLE__
uint32s_to_timespec(&stat->st_atimespec, &ints); to_timespec(&stat->st_atimespec, ints);
uint32s_to_timespec(&stat->st_mtimespec, &ints); to_timespec(&stat->st_mtimespec, ints + 2);
uint32s_to_timespec(&stat->st_ctimespec, &ints); to_timespec(&stat->st_ctimespec, ints + 4);
#else #else
uint32s_to_timespec(&stat->st_atim, &ints); to_timespec(&stat->st_atim, ints);
uint32s_to_timespec(&stat->st_mtim, &ints); to_timespec(&stat->st_mtim, ints + 2);
uint32s_to_timespec(&stat->st_ctim, &ints); to_timespec(&stat->st_ctim, ints + 4);
#endif #endif
} }
static void populate_statvfs (uint32_t *ints, struct statvfs* statvfs) { static void populate_statvfs (uint32_t *ints, struct statvfs* statvfs) {
statvfs->f_bsize = *ints++; statvfs->f_bsize = *ints++;
statvfs->f_frsize = *ints++; statvfs->f_frsize = *ints++;
statvfs->f_blocks = *ints++; statvfs->f_blocks = *ints++;
statvfs->f_bfree = *ints++; statvfs->f_bfree = *ints++;
statvfs->f_bavail = *ints++; statvfs->f_bavail = *ints++;
statvfs->f_files = *ints++; statvfs->f_files = *ints++;
statvfs->f_ffree = *ints++; statvfs->f_ffree = *ints++;
statvfs->f_favail = *ints++; statvfs->f_favail = *ints++;
statvfs->f_fsid = *ints++; statvfs->f_fsid = *ints++;
statvfs->f_flag = *ints++; statvfs->f_flag = *ints++;
statvfs->f_namemax = *ints++; statvfs->f_namemax = *ints++;
} }
// Methods // Methods
@ -336,14 +321,14 @@ FUSE_METHOD(create, 2, 1, (const char *path, mode_t mode, struct fuse_file_info
} }
}) })
FUSE_METHOD_VOID(utimens, 5, 0, (const char *path, const struct timespec tv[2]), { FUSE_METHOD_VOID(utimens, 3, 0, (const char *path, const struct timespec tv[2]), {
l->path = path; l->path = path;
l->atime = timespec_to_uint64(&tv[0]); from_timespec(&tv[0], l->atim);
l->mtime = timespec_to_uint64(&tv[1]); from_timespec(&tv[1], l->mtim);
}, { }, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
FUSE_UINT64_TO_INTS_ARGV(l->atime, 3) napi_create_external_arraybuffer(env, l->atim, 2 * sizeof(uint32_t), &fin, NULL, &argv[3]);
FUSE_UINT64_TO_INTS_ARGV(l->atime, 5) napi_create_external_arraybuffer(env, l->mtim, 2 * sizeof(uint32_t), &fin, NULL, &argv[4]);
}) })
FUSE_METHOD_VOID(release, 2, 0, (const char *path, struct fuse_file_info *info), { FUSE_METHOD_VOID(release, 2, 0, (const char *path, struct fuse_file_info *info), {
@ -370,7 +355,7 @@ FUSE_METHOD_VOID(releasedir, 2, 0, (const char *path, struct fuse_file_info *inf
} }
}) })
FUSE_METHOD(read, 6, 2, (const char *path, char *buf, size_t len, off_t offset, struct fuse_file_info *info), { FUSE_METHOD(read, 5, 1, (const char *path, char *buf, size_t len, off_t offset, struct fuse_file_info *info), {
l->path = path; l->path = path;
l->buf = buf; l->buf = buf;
l->len = len; l->len = len;
@ -379,14 +364,14 @@ FUSE_METHOD(read, 6, 2, (const char *path, char *buf, size_t len, off_t offset,
}, { }, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_uint32(env, l->info->fh, &(argv[3])); 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_external_buffer(env, l->len, (char *) l->buf, &fin, NULL, &(argv[4]));
napi_create_uint32(env, l->len, &(argv[5])); napi_create_uint32(env, l->len, &(argv[5]));
FUSE_UINT64_TO_INTS_ARGV(l->offset, 6) napi_create_uint32(env, l->offset, &(argv[6]));
}, { }, {
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[3]) == napi_ok); // TODO: handle bytes processed?
}) })
FUSE_METHOD(write, 6, 2, (const char *path, const char *buf, size_t len, off_t offset, struct fuse_file_info *info), { FUSE_METHOD(write, 5, 1, (const char *path, const char *buf, size_t len, off_t offset, struct fuse_file_info *info), {
l->path = path; l->path = path;
l->buf = buf; l->buf = buf;
l->len = len; l->len = len;
@ -395,11 +380,11 @@ FUSE_METHOD(write, 6, 2, (const char *path, const char *buf, size_t len, off_t o
}, { }, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_uint32(env, l->info->fh, &(argv[3])); 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_external_buffer(env, l->len, (char *) l->buf, &fin, NULL, &(argv[4]));
napi_create_uint32(env, l->len, &(argv[5])); napi_create_uint32(env, l->len, &(argv[5]));
FUSE_UINT64_TO_INTS_ARGV(l->offset, 6) napi_create_uint32(env, l->offset, &(argv[6]));
}, { }, {
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[3]) == napi_ok); // TODO: handle bytes processed?
}) })
FUSE_METHOD(readdir, 1, 2, (const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *info), { FUSE_METHOD(readdir, 1, 2, (const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *info), {
@ -449,7 +434,7 @@ FUSE_METHOD(readdir, 1, 2, (const char *path, void *buf, fuse_fill_dir_t filler,
#ifdef __APPLE__ #ifdef __APPLE__
FUSE_METHOD(setxattr, 5, 1, (const char *path, const char *name, const char *value, size_t size, int flags, uint32_t position), { FUSE_METHOD_VOID(setxattr, 6, 0, (const char *path, const char *name, const char *value, size_t size, int flags, uint32_t position), {
l->path = path; l->path = path;
l->name = name; l->name = name;
l->value = value; l->value = value;
@ -459,14 +444,13 @@ FUSE_METHOD(setxattr, 5, 1, (const char *path, const char *name, const char *val
}, { }, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 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_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_string_utf8(env, l->value, NAPI_AUTO_LENGTH, &(argv[4]));
napi_create_uint32(env, l->position, &(argv[5])); napi_create_uint32(env, l->size, &(argv[5]));
napi_create_uint32(env, l->flags, &(argv[6])); napi_create_uint32(env, l->flags, &(argv[6]));
}, { napi_create_uint32(env, l->position, &(argv[7]));
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), { FUSE_METHOD_VOID(getxattr, 5, 0, (const char *path, const char *name, char *value, size_t size, uint32_t position), {
l->path = path; l->path = path;
l->name = name; l->name = name;
l->value = value; l->value = value;
@ -475,15 +459,14 @@ FUSE_METHOD(getxattr, 4, 1, (const char *path, const char *name, char *value, si
}, { }, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 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_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_string_utf8(env, l->value, NAPI_AUTO_LENGTH, &(argv[4]));
napi_create_uint32(env, l->position, &(argv[5])); napi_create_uint32(env, l->size, &(argv[5]));
}, { napi_create_uint32(env, l->position, &(argv[6]));
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[2]) == napi_ok);
}) })
#else #else
FUSE_METHOD(setxattr, 5, 1, (const char *path, const char *name, const char *value, size_t size, int flags), { FUSE_METHOD_VOID(setxattr, 5, 0, (const char *path, const char *name, const char *value, size_t size, int flags), {
l->path = path; l->path = path;
l->name = name; l->name = name;
l->value = value; l->value = value;
@ -492,14 +475,12 @@ FUSE_METHOD(setxattr, 5, 1, (const char *path, const char *name, const char *val
}, { }, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 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_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_string_utf8(env, l->value, NAPI_AUTO_LENGTH, &(argv[4]));
napi_create_uint32(env, 0, &(argv[5])); // normalize apis between mac and linux napi_create_uint32(env, l->size, &(argv[5]));
napi_create_uint32(env, l->flags, &(argv[6])); 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), { FUSE_METHOD_VOID(getxattr, 4, 0, (const char *path, const char *name, char *value, size_t size), {
l->path = path; l->path = path;
l->name = name; l->name = name;
l->value = value; l->value = value;
@ -507,23 +488,20 @@ FUSE_METHOD(getxattr, 4, 1, (const char *path, const char *name, char *value, si
}, { }, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 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_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_string_utf8(env, l->value, NAPI_AUTO_LENGTH, &(argv[4]));
napi_create_uint32(env, 0, &(argv[5])); napi_create_uint32(env, l->size, &(argv[5]));
}, {
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[2]) == napi_ok);
}) })
#endif #endif
FUSE_METHOD(listxattr, 2, 1, (const char *path, char *list, size_t size), { FUSE_METHOD_VOID(listxattr, 3, 0, (const char *path, char *list, size_t size), {
l->path = path; l->path = path;
l->list = list; l->list = list;
l->size = size; l->size = size;
}, { }, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); 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])); napi_create_external_buffer(env, l->size, l->list, &fin, NULL, &(argv[3]));
}, { napi_create_uint32(env, l->size, &(argv[4]));
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), { FUSE_METHOD_VOID(removexattr, 2, 0, (const char *path, const char *name), {
@ -575,26 +553,26 @@ FUSE_METHOD_VOID(fsyncdir, 3, 0, (const char *path, int datasync, struct fuse_fi
}) })
FUSE_METHOD_VOID(truncate, 3, 0, (const char *path, off_t size), { FUSE_METHOD_VOID(truncate, 2, 0, (const char *path, off_t size), {
l->path = path; l->path = path;
l->offset = size; l->len = size;
}, { }, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
FUSE_UINT64_TO_INTS_ARGV(l->offset, 3) napi_create_uint32(env, l->len, &(argv[3]));
}) })
FUSE_METHOD_VOID(ftruncate, 4, 0, (const char *path, off_t size, struct fuse_file_info *info), { FUSE_METHOD_VOID(ftruncate, 2, 0, (const char *path, off_t size, struct fuse_file_info *info), {
l->path = path; l->path = path;
l->offset = size; l->len = size;
l->info = info; l->info = info;
}, { }, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2])); napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_uint32(env, l->len, &(argv[3]));
if (l->info != NULL) { if (l->info != NULL) {
napi_create_uint32(env, l->info->fh, &(argv[3])); napi_create_uint32(env, l->info->fh, &(argv[4]));
} else { } else {
napi_create_uint32(env, 0, &(argv[3])); napi_create_uint32(env, 0, &(argv[4]));
} }
FUSE_UINT64_TO_INTS_ARGV(l->offset, 4)
}) })
FUSE_METHOD(readlink, 1, 1, (const char *path, char *linkname, size_t len), { FUSE_METHOD(readlink, 1, 1, (const char *path, char *linkname, size_t len), {
@ -683,10 +661,8 @@ FUSE_METHOD_VOID(rmdir, 1, 0, (const char *path), {
static void fuse_native_dispatch_init (uv_async_t* handle, fuse_thread_locals_t* l, fuse_thread_t* ft) {\ 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], { FUSE_NATIVE_CALLBACK(ft->handlers[op_init], {
napi_value argv[2]; napi_value argv[2];
napi_create_external_buffer(env, sizeof(fuse_thread_locals_t), l, &fin, NULL, &(argv[0]));
napi_get_reference_value(env, l->self, &(argv[0]));
napi_create_uint32(env, l->op, &(argv[1])); napi_create_uint32(env, l->op, &(argv[1]));
NAPI_MAKE_CALLBACK(env, NULL, ctx, callback, 2, argv, NULL); NAPI_MAKE_CALLBACK(env, NULL, ctx, callback, 2, argv, NULL);
}) })
} }
@ -702,13 +678,10 @@ NAPI_METHOD(fuse_native_signal_init) {
static void * fuse_native_init (struct fuse_conn_info *conn) { static void * fuse_native_init (struct fuse_conn_info *conn) {
fuse_thread_locals_t *l = get_thread_locals(); fuse_thread_locals_t *l = get_thread_locals();
l->op = op_init; l->op = op_init;
l->op_fn = fuse_native_dispatch_init; l->op_fn = fuse_native_dispatch_init;
uv_async_send(&(l->async)); uv_async_send(&(l->async));
uv_sem_wait(&(l->sem)); uv_sem_wait(&(l->sem));
return l->fuse; return l->fuse;
} }
@ -718,27 +691,12 @@ static void fuse_native_dispatch (uv_async_t* handle) {
fuse_thread_locals_t *l = (fuse_thread_locals_t *) handle->data; fuse_thread_locals_t *l = (fuse_thread_locals_t *) handle->data;
fuse_thread_t *ft = l->fuse; fuse_thread_t *ft = l->fuse;
void (*fn)(uv_async_t *, fuse_thread_locals_t *, fuse_thread_t *) = l->op_fn; void (*fn)(uv_async_t *, fuse_thread_locals_t *, fuse_thread_t *) = l->op_fn;
fn(handle, l, ft); fn(handle, l, ft);
} }
static void fuse_native_async_init (uv_async_t* handle) { 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_thread_locals_t *) handle->data;
fuse_thread_locals_t *l; fuse_thread_t *ft = l->fuse;
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); int err = uv_async_init(uv_default_loop(), &(l->async), (uv_async_cb) fuse_native_dispatch);
assert(err >= 0); assert(err >= 0);
@ -747,8 +705,6 @@ static void fuse_native_async_init (uv_async_t* handle) {
uv_sem_init(&(l->sem), 0); uv_sem_init(&(l->sem), 0);
l->async.data = l; l->async.data = l;
ft->async.data = l;
l->fuse = ft;
uv_sem_post(&(ft->sem)); uv_sem_post(&(ft->sem));
} }
@ -760,18 +716,21 @@ static fuse_thread_locals_t* get_thread_locals () {
void *data = pthread_getspecific(thread_locals_key); void *data = pthread_getspecific(thread_locals_key);
if (data != NULL) { if (data != NULL) {
return (fuse_thread_locals_t *) data; return (fuse_thread_locals_t *)data;
} }
fuse_thread_locals_t* l = (fuse_thread_locals_t *) malloc(sizeof(fuse_thread_locals_t));
l->fuse = ft;
// Need to lock the mutation of l->async. // Need to lock the mutation of l->async.
uv_mutex_lock(&(ft->mut)); uv_mutex_lock(&(ft->mut));
ft->async.data = ft; ft->async.data = l;
// Notify the main thread to uv_async_init l->async. // Notify the main thread to uv_async_init l->async.
uv_async_send(&(ft->async)); uv_async_send(&(ft->async));
uv_sem_wait(&(ft->sem)); uv_sem_wait(&(ft->sem));
fuse_thread_locals_t *l = (fuse_thread_locals_t*) ft->async.data; l->async.data = l;
pthread_setspecific(thread_locals_key, (void *) l); pthread_setspecific(thread_locals_key, (void *) l);
uv_mutex_unlock(&(ft->mut)); uv_mutex_unlock(&(ft->mut));
@ -791,15 +750,14 @@ static void* start_fuse_thread (void *data) {
} }
NAPI_METHOD(fuse_native_mount) { NAPI_METHOD(fuse_native_mount) {
NAPI_ARGV(7) NAPI_ARGV(6)
NAPI_ARGV_UTF8(mnt, 1024, 0); NAPI_ARGV_UTF8(mnt, 1024, 0);
NAPI_ARGV_UTF8(mntopts, 1024, 1); NAPI_ARGV_UTF8(mntopts, 1024, 1);
NAPI_ARGV_BUFFER_CAST(fuse_thread_t *, ft, 2); NAPI_ARGV_BUFFER_CAST(fuse_thread_t *, ft, 2);
napi_create_reference(env, argv[3], 1, &(ft->ctx)); napi_create_reference(env, argv[3], 1, &(ft->ctx));
napi_create_reference(env, argv[4], 1, &(ft->malloc)); napi_value handlers = argv[4];
napi_value handlers = argv[5]; NAPI_ARGV_BUFFER_CAST(uint32_t *, implemented, 5)
NAPI_ARGV_BUFFER_CAST(uint32_t *, implemented, 6)
for (int i = 0; i < 35; i++) { for (int i = 0; i < 35; i++) {
ft->handlers[i] = NULL; ft->handlers[i] = NULL;
@ -894,7 +852,6 @@ NAPI_METHOD(fuse_native_unmount) {
// pthread_join(ft->thread, NULL); // pthread_join(ft->thread, NULL);
} }
// TODO: fix the async holding the loop
uv_unref((uv_handle_t *) &(ft->async)); uv_unref((uv_handle_t *) &(ft->async));
ft->mounted--; ft->mounted--;
@ -902,13 +859,6 @@ NAPI_METHOD(fuse_native_unmount) {
} }
NAPI_INIT() { 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 pthread_key_create(&(thread_locals_key), NULL); // TODO: add destructor
NAPI_EXPORT_SIZEOF(fuse_thread_t) NAPI_EXPORT_SIZEOF(fuse_thread_t)

256
index.js
View File

@ -11,9 +11,6 @@ const binding = require('node-gyp-build')(__dirname)
const IS_OSX = os.platform() === 'darwin' const IS_OSX = os.platform() === 'darwin'
const OSX_FOLDER_ICON = '/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericFolderIcon.icns' const OSX_FOLDER_ICON = '/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericFolderIcon.icns'
const HAS_FOLDER_ICON = IS_OSX && fs.existsSync(OSX_FOLDER_ICON) const HAS_FOLDER_ICON = IS_OSX && fs.existsSync(OSX_FOLDER_ICON)
const DEFAULT_TIMEOUT = 15 * 1000
const TIMEOUT_ERRNO = IS_OSX ? -60 : -110
const ENOTCONN = IS_OSX ? -57 : -107
const OpcodesAndDefaults = new Map([ const OpcodesAndDefaults = new Map([
['init', { ['init', {
@ -134,17 +131,12 @@ const OpcodesAndDefaults = new Map([
class Fuse extends Nanoresource { class Fuse extends Nanoresource {
constructor (mnt, ops, opts = {}) { constructor (mnt, ops, opts = {}) {
super() super()
this.opts = opts this.opts = opts
this.mnt = path.resolve(mnt) this.mnt = path.resolve(mnt)
this.ops = ops
this.timeout = opts.timeout === false ? 0 : (opts.timeout || DEFAULT_TIMEOUT)
this._force = !!opts.force this.ops = ops
this._mkdir = !!opts.mkdir
this._thread = null this._thread = null
this._handlers = this._makeHandlerArray() this._handlers = this._makeHandlerArray()
this._threads = new Set()
const implemented = [binding.op_init, binding.op_error, binding.op_getattr] const implemented = [binding.op_init, binding.op_error, binding.op_getattr]
if (ops) { if (ops) {
@ -194,17 +186,11 @@ class Fuse extends Nanoresource {
if (this.opts.modules) options.push('modules=' + this.opts.modules) if (this.opts.modules) options.push('modules=' + this.opts.modules)
if (this.opts.displayFolder && IS_OSX) { // only works on osx if (this.opts.displayFolder && IS_OSX) { // only works on osx
options.push('volname=' + path.basename(this.opts.name || this.mnt)) options.push('volname=' + path.basename(this.mnt))
if (HAS_FOLDER_ICON) options.push('volicon=' + OSX_FOLDER_ICON) if (HAS_FOLDER_ICON) options.push('volicon=' + OSX_FOLDER_ICON)
} }
return options.length ? '-o' + options.join(',') : '' return options.map(o => '-o' + o).join(' ')
}
_malloc (size) {
const buf = Buffer.alloc(size)
this._threads.add(buf)
return buf
} }
_makeHandlerArray () { _makeHandlerArray () {
@ -221,17 +207,8 @@ class Fuse extends Nanoresource {
return handlers return handlers
function makeHandler (name, op, defaults, nativeSignal) { function makeHandler (name, op, defaults, nativeSignal) {
let to = self.timeout
if (typeof to === 'object' && to) {
const defaultTimeout = to.default || DEFAULT_TIMEOUT
to = to[name]
if (!to && to !== false) to = defaultTimeout
}
return function (nativeHandler, opCode, ...args) { return function (nativeHandler, opCode, ...args) {
const sig = signal.bind(null, nativeHandler) const boundSignal = signal.bind(null, nativeHandler)
const input = [...args]
const boundSignal = to ? autoTimeout(sig, input) : sig
const funcName = `_op_${name}` const funcName = `_op_${name}`
if (!self[funcName] || !self._implemented.has(op)) return boundSignal(-1, ...defaults) if (!self[funcName] || !self._implemented.has(op)) return boundSignal(-1, ...defaults)
return self[funcName].apply(self, [boundSignal, ...args]) return self[funcName].apply(self, [boundSignal, ...args])
@ -239,118 +216,44 @@ class Fuse extends Nanoresource {
function signal (nativeHandler, err, ...args) { function signal (nativeHandler, err, ...args) {
var arr = [nativeHandler, err, ...args] var arr = [nativeHandler, err, ...args]
if (defaults && (!args.length)) arr = arr.concat(defaults)
if (defaults) {
while (arr.length > 2 && arr[arr.length - 1] === undefined) arr.pop()
if (arr.length === 2) arr = arr.concat(defaults)
}
return process.nextTick(nativeSignal, ...arr) return process.nextTick(nativeSignal, ...arr)
} }
function autoTimeout (cb, input) {
let called = false
const timeout = setTimeout(timeoutWrap, to, TIMEOUT_ERRNO)
return timeoutWrap
function timeoutWrap (err, ...args) {
if (called) return
called = true
clearTimeout(timeout)
if (err === TIMEOUT_ERRNO) {
switch (name) {
case 'write':
case 'read':
return cb(TIMEOUT_ERRNO, 0, input[2].buffer)
case 'setxattr':
return cb(TIMEOUT_ERRNO, input[2].buffer)
case 'getxattr':
return cb(TIMEOUT_ERRNO, input[2].buffer)
case 'listxattr':
return cb(TIMEOUT_ERRNO, input[1].buffer)
}
}
cb(err, ...args)
}
}
} }
} }
// Static methods
static unmount (mnt, cb) {
mnt = JSON.stringify(mnt)
const cmd = IS_OSX ? `diskutil unmount force ${mnt}` : `fusermount -uz ${mnt}`
exec(cmd, err => {
if (err) return cb(err)
return cb(null)
})
}
// Debugging methods
// Lifecycle methods // Lifecycle methods
_open (cb) { _open (cb) {
const self = this this._thread = Buffer.alloc(binding.sizeof_fuse_thread_t)
this._openCallback = cb
if (this._force) { const opts = this._fuseOptions()
return fs.stat(path.join(this.mnt, 'test'), (err, st) => { const implemented = this._getImplementedArray()
if (err && (err.errno === ENOTCONN || err.errno === Fuse.ENXIO)) return Fuse.unmount(this.mnt, open)
return open()
})
}
return open()
function open () { return fs.stat(this.mnt, (err, stat) => {
// If there was an unmount error, continue attempting to mount (this is the best we can do) if (err) return cb(new Error('Mountpoint does not exist'))
self._thread = Buffer.alloc(binding.sizeof_fuse_thread_t) if (!stat.isDirectory()) return cb(new Error('Mountpoint is not a directory'))
self._openCallback = cb return fs.stat(path.join(this.mnt, '..'), (_, parent) => {
if (parent && parent.dev !== stat.dev) return cb(new Error('Mountpoint in use'))
const opts = self._fuseOptions() try {
const implemented = self._getImplementedArray() // TODO: asyncify
binding.fuse_native_mount(this.mnt, opts, this._thread, this, this._handlers, implemented)
return fs.stat(self.mnt, (err, stat) => { } catch (err) {
if (err && err.errno !== -2) return cb(err) return cb(err)
if (err) {
if (!self._mkdir) return cb(new Error('Mountpoint does not exist'))
return fs.mkdir(self.mnt, { recursive: true }, err => {
if (err) return cb(err)
fs.stat(self.mnt, (err, stat) => {
if (err) return cb(err)
return onexists(stat)
})
})
} }
if (!stat.isDirectory()) return cb(new Error('Mountpoint is not a directory'))
return onexists(stat)
}) })
})
function onexists (stat) {
fs.stat(path.join(self.mnt, '..'), (_, parent) => {
if (parent && parent.dev !== stat.dev) return cb(new Error('Mountpoint in use'))
try {
// TODO: asyncify
binding.fuse_native_mount(self.mnt, opts, self._thread, self, self._malloc, self._handlers, implemented)
} catch (err) {
return cb(err)
}
})
}
}
} }
_close (cb) { _close (cb) {
if (this._closed) return process.nextTick(cb, null)
const self = this const self = this
const mnt = JSON.stringify(this.mnt)
const cmd = IS_OSX ? `diskutil umount ${mnt}` : `fusermount -uz ${mnt}`
Fuse.unmount(this.mnt, err => { exec(cmd, (err, stdout, stderr) => {
if (err) { if (err) return cb(err)
err.unmountFailure = true
return cb(err)
}
nativeUnmount() nativeUnmount()
}) })
@ -360,6 +263,7 @@ class Fuse extends Nanoresource {
} catch (err) { } catch (err) {
return cb(err) return cb(err)
} }
self._closed = true
return cb(null) return cb(null)
} }
} }
@ -407,7 +311,6 @@ class Fuse extends Nanoresource {
} }
return return
} }
this.ops.getattr(path, (err, stat) => { this.ops.getattr(path, (err, stat) => {
if (err) return signal(err, getStatArray()) if (err) return signal(err, getStatArray())
return signal(0, getStatArray(stat)) return signal(0, getStatArray(stat))
@ -453,10 +356,10 @@ class Fuse extends Nanoresource {
}) })
} }
_op_utimens (signal, path, atimeLow, atimeHigh, mtimeLow, mtimeHigh) { _op_utimens (signal, path, atim, mtim) {
const atime = getDoubleArg(atimeLow, atimeHigh) atim = getDoubleInt(atim, 0)
const mtime = getDoubleArg(mtimeLow, mtimeHigh) mtim = getDoubleInt(mtim, 0)
this.ops.utimens(path, atime, mtime, err => { this.ops.utimens(path, atim, mtim, err => {
return signal(err) return signal(err)
}) })
} }
@ -473,15 +376,15 @@ class Fuse extends Nanoresource {
}) })
} }
_op_read (signal, path, fd, buf, len, offsetLow, offsetHigh) { _op_read (signal, path, fd, buf, len, offset) {
this.ops.read(path, fd, buf, len, getDoubleArg(offsetLow, offsetHigh), (err, bytesRead) => { this.ops.read(path, fd, buf, len, offset, (err, bytesRead) => {
return signal(err, bytesRead || 0, buf.buffer) return signal(err, bytesRead)
}) })
} }
_op_write (signal, path, fd, buf, len, offsetLow, offsetHigh) { _op_write (signal, path, fd, buf, len, offset) {
this.ops.write(path, fd, buf, len, getDoubleArg(offsetLow, offsetHigh), (err, bytesWritten) => { this.ops.write(path, fd, buf, len, offset, (err, bytesWritten) => {
return signal(err, bytesWritten || 0, buf.buffer) return signal(err, bytesWritten)
}) })
} }
@ -493,43 +396,21 @@ class Fuse extends Nanoresource {
}) })
} }
_op_setxattr (signal, path, name, value, position, flags) { _op_setxattr (signal, path, name, value, size, position, flags) {
this.ops.setxattr(path, name, value, position, flags, err => { this.ops.setxattr(path, name, value, size, position, flags, err => {
return signal(err, value.buffer) return signal(err)
}) })
} }
_op_getxattr (signal, path, name, valueBuf, position) { _op_getxattr (signal, path, name, value, size, position) {
this.ops.getxattr(path, name, position, (err, value) => { this.ops.getxattr(path, name, value, size, position, err => {
if (!err) { return signal(err)
if (!value) return signal(IS_OSX ? -93 : -61, valueBuf.buffer)
value.copy(valueBuf)
return signal(value.length, valueBuf.buffer)
}
return signal(err, valueBuf.buffer)
}) })
} }
_op_listxattr (signal, path, listBuf) { _op_listxattr (signal, path, list, size) {
this.ops.listxattr(path, (err, list) => { this.ops.listxattr(path, list, size, err => {
if (list && !err) { return signal(err)
if (!listBuf.length) {
let size = 0
for (const name of list) size += Buffer.byteLength(name) + 1
size += 128 // fuse yells if we do not signal room for some mac stuff also
return signal(size, listBuf.buffer)
}
let ptr = 0
for (const name of list) {
listBuf.write(name, ptr)
ptr += Buffer.byteLength(name)
listBuf[ptr++] = 0
}
return signal(ptr, listBuf.buffer)
}
return signal(err, listBuf.buffer)
}) })
} }
@ -539,8 +420,8 @@ class Fuse extends Nanoresource {
}) })
} }
_op_flush (signal, path, fd) { _op_flush (signal, path, datasync, fd) {
this.ops.flush(path, fd, err => { this.ops.flush(path, datasync, fd, err => {
return signal(err) return signal(err)
}) })
} }
@ -557,16 +438,14 @@ class Fuse extends Nanoresource {
}) })
} }
_op_truncate (signal, path, sizeLow, sizeHigh) { _op_truncate (signal, path, size) {
const size = getDoubleArg(sizeLow, sizeHigh)
this.ops.truncate(path, size, err => { this.ops.truncate(path, size, err => {
return signal(err) return signal(err)
}) })
} }
_op_ftruncate (signal, path, fd, sizeLow, sizeHigh) { _op_ftruncate (signal, path, size, fd) {
const size = getDoubleArg(sizeLow, sizeHigh) this.ops.ftruncate(path, size, fd, err => {
this.ops.ftruncate(path, fd, size, err => {
return signal(err) return signal(err)
}) })
} }
@ -803,32 +682,29 @@ function setDoubleInt (arr, idx, num) {
arr[idx + 1] = (num - arr[idx]) / 4294967296 arr[idx + 1] = (num - arr[idx]) / 4294967296
} }
function getDoubleArg (a, b) { function getDoubleInt (arr, idx) {
return a + b * 4294967296 arr = new Uint32Array(arr)
} var num = arr[idx + 1] * 4294967296
num += arr[idx]
function toDateMS (st) { return num
if (typeof st === 'number') return st
if (!st) return Date.now()
return st.getTime()
} }
function getStatArray (stat) { function getStatArray (stat) {
const ints = new Uint32Array(18) const ints = new Uint32Array(16)
ints[0] = (stat && stat.mode) || 0 ints[0] = (stat && stat.mode) || 0
ints[1] = (stat && stat.uid) || 0 ints[1] = (stat && stat.uid) || 0
ints[2] = (stat && stat.gid) || 0 ints[2] = (stat && stat.gid) || 0
setDoubleInt(ints, 3, (stat && stat.size) || 0) ints[3] = (stat && stat.size) || 0
ints[5] = (stat && stat.dev) || 0 ints[4] = (stat && stat.dev) || 0
ints[6] = (stat && stat.nlink) || 1 ints[5] = (stat && stat.nlink) || 1
ints[7] = (stat && stat.ino) || 0 ints[6] = (stat && stat.ino) || 0
ints[8] = (stat && stat.rdev) || 0 ints[7] = (stat && stat.rdev) || 0
ints[9] = (stat && stat.blksize) || 0 ints[8] = (stat && stat.blksize) || 0
setDoubleInt(ints, 10, (stat && stat.blocks) || 0) ints[9] = (stat && stat.blocks) || 0
setDoubleInt(ints, 12, toDateMS(stat && stat.atime)) setDoubleInt(ints, 10, (stat && stat.atim) || Date.now())
setDoubleInt(ints, 14, toDateMS(stat && stat.mtime)) setDoubleInt(ints, 12, (stat && stat.atim) || Date.now())
setDoubleInt(ints, 16, toDateMS(stat && stat.ctime)) setDoubleInt(ints, 14, (stat && stat.atim) || Date.now())
return ints return ints
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "fuse-native", "name": "fuse-native",
"version": "2.2.6", "version": "2.0.0",
"description": "Fully maintained fuse bindings for Node that aims to cover the entire FUSE api", "description": "Fully maintained fuse bindings for Node that aims to cover the entire FUSE api",
"main": "index.js", "main": "index.js",
"bin": { "bin": {
@ -15,16 +15,17 @@
}, },
"gypfile": true, "gypfile": true,
"dependencies": { "dependencies": {
"fuse-shared-library": "^1.0.2", "fuse-shared-library": "^1.0.1",
"nanoresource": "^1.3.0", "nanoresource": "^1.2.0",
"napi-macros": "^2.0.0", "napi-macros": "^2.0.0",
"node-gyp-build": "^4.2.0" "node-gyp-build": "^3.2.2",
"why-is-node-running": "^2.1.0"
}, },
"devDependencies": { "devDependencies": {
"concat-stream": "^2.0.0", "concat-stream": "^2.0.0",
"prebuildify": "^3.0.4", "prebuildify": "^3.0.4",
"standard": "^13.1.0", "standard": "^13.1.0",
"tape": "^4.12.0" "tape": "^4.11.0"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -1,116 +0,0 @@
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,18 +2,12 @@ var os = require('os')
var path = require('path') var path = require('path')
var fs = require('fs') var fs = require('fs')
function create (opts = {}) { var mnt = path.join(os.tmpdir(), 'fuse-bindings-' + process.pid + '-' + Date.now())
var mnt = path.join(os.tmpdir(), 'fuse-bindings-' + process.pid + '-' + Date.now())
if (!opts.doNotCreate) { try {
try { fs.mkdirSync(mnt)
fs.mkdirSync(mnt) } catch (err) {
} catch (err) { // do nothing
// do nothing
}
}
return mnt
} }
module.exports = create module.exports = mnt

View File

@ -1,34 +0,0 @@
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)
}
}
}

View File

@ -4,11 +4,9 @@ const path = require('path')
const { unmount } = require('./helpers') const { unmount } = require('./helpers')
const Fuse = require('../') const Fuse = require('../')
const createMountpoint = require('./fixtures/mnt') const mnt = require('./fixtures/mnt')
const stat = require('./fixtures/stat') const stat = require('./fixtures/stat')
const mnt = createMountpoint()
tape('readlink', function (t) { tape('readlink', function (t) {
var ops = { var ops = {
force: true, force: true,

View File

@ -1,15 +1,8 @@
const os = require('os') const mnt = require('./fixtures/mnt')
const fs = require('fs')
const tape = require('tape') const tape = require('tape')
const { spawnSync, exec } = require('child_process')
const createMountpoint = require('./fixtures/mnt')
const Fuse = require('../') const Fuse = require('../')
const { unmount } = require('./helpers') const { unmount } = require('./helpers')
const simpleFS = require('./fixtures/simple-fs')
const mnt = createMountpoint()
tape('mount', function (t) { tape('mount', function (t) {
const fuse = new Fuse(mnt, {}, { force: true }) const fuse = new Fuse(mnt, {}, { force: true })
@ -46,7 +39,7 @@ tape('mount + unmount + mount with same instance fails', function (t) {
fuse.mount(function (err) { fuse.mount(function (err) {
t.error(err, 'no error') t.error(err, 'no error')
t.pass('works') t.ok(true, 'works')
unmount(fuse, function () { unmount(fuse, function () {
fuse.mount(function (err) { fuse.mount(function (err) {
t.ok(err, 'had error') t.ok(err, 'had error')
@ -71,138 +64,3 @@ tape('mnt point must be directory', function (t) {
t.end() 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

@ -4,20 +4,38 @@ const path = require('path')
const concat = require('concat-stream') const concat = require('concat-stream')
const Fuse = require('../') const Fuse = require('../')
const createMountpoint = require('./fixtures/mnt') const mnt = require('./fixtures/mnt')
const stat = require('./fixtures/stat') const stat = require('./fixtures/stat')
const simpleFS = require('./fixtures/simple-fs')
const { unmount } = require('./helpers') const { unmount } = require('./helpers')
const mnt = createMountpoint()
tape('read', function (t) { tape('read', function (t) {
const testFS = simpleFS({ var ops = {
release: function (path, fd) { 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 }))
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') t.same(fd, 42, 'fd was passed to release')
return process.nextTick(cb, 0)
},
read: function (path, fd, buf, len, pos, cb) {
var str = 'hello world'.slice(pos, pos + len)
if (!str) return process.nextTick(cb, 0)
buf.write(str)
return process.nextTick(cb, str.length)
} }
}) }
const fuse = new Fuse(mnt, testFS, { debug: true })
const fuse = new Fuse(mnt, ops, { debug: true })
fuse.mount(function (err) { fuse.mount(function (err) {
t.error(err, 'no error') t.error(err, 'no error')
@ -44,73 +62,3 @@ 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)
})
})
})

View File

@ -3,11 +3,9 @@ const { unmount } = require('./helpers')
const tape = require('tape') const tape = require('tape')
const Fuse = require('../') const Fuse = require('../')
const createMountpoint = require('./fixtures/mnt') const mnt = require('./fixtures/mnt')
const stat = require('./fixtures/stat') const stat = require('./fixtures/stat')
const mnt = createMountpoint()
tape('statfs', function (t) { tape('statfs', function (t) {
const ops = { const ops = {
force: true, force: true,

View File

@ -3,12 +3,10 @@ const fs = require('fs')
const path = require('path') const path = require('path')
const Fuse = require('../') const Fuse = require('../')
const createMountpoint = require('./fixtures/mnt') const mnt = require('./fixtures/mnt')
const stat = require('./fixtures/stat') const stat = require('./fixtures/stat')
const { unmount } = require('./helpers') const { unmount } = require('./helpers')
const mnt = createMountpoint()
tape('write', function (t) { tape('write', function (t) {
var created = false var created = false
var data = Buffer.alloc(1024) var data = Buffer.alloc(1024)