1
0
mirror of https://github.com/fuse-friends/fuse-native synced 2024-10-27 18:34:01 +00:00

Compare commits

...

52 Commits

Author SHA1 Message Date
Mathias Buus
21658b5ebb 2.2.6 2020-06-03 21:26:19 +02:00
Mathias Buus
34554d664b pass down array buffer in timeout 2020-06-03 21:26:08 +02:00
Mathias Buus
14b3a23d68 2.2.5 2020-06-02 16:15:01 +02:00
Mathias Buus
3d8ddd21b5 wrong argument in timeout switch 2020-06-02 16:14:59 +02:00
Mathias Buus
fc92548561 2.2.4 2020-06-02 16:10:23 +02:00
Mathias Buus
7f529df642 fix timeout assert crashes 2020-06-02 16:10:19 +02:00
Mathias Buus
977307b1b3 2.2.3 2020-05-28 12:42:01 +02:00
Mathias Buus
56e4b99e04 default bytesRead,Written to 0 2020-05-28 12:41:53 +02:00
Mathias Buus
4606e58acd 2.2.2 2020-05-20 17:00:23 +02:00
Mathias Buus
b7f98c6192 fix travis file 2020-05-20 16:59:54 +02:00
Mathias Buus
3507fe0907 fix travis link 2020-05-20 16:34:50 +02:00
Mathias Buus
e218d40f90
malloc and track thread_local buffers from js (#20)
* malloc and track thread_local buffers from js to avoid crashes when multiple external buffers are made from the same pointer

* make tests pass on 14.3.0

* disable npm cache

* debug log

* detach array buffer on read free as a test

* fix detatch

* detach arraybuffer asap, to avoid crashes

* reset test

* add secure travis key

* missing arraybuffer pass

* bump all the flags

* no linux 32
2020-05-20 16:33:46 +02:00
Bennjamin Blast
519b468995 fix: remove incorrect flush parameter 2020-05-20 10:49:18 +02:00
Andrew Osheroff
2e6c27c3a3 2.2.1 2020-02-07 14:55:35 +01:00
Andrew Osheroff
e09247de88 Forgot to add simple-fs.js 2020-02-07 14:53:21 +01:00
Andrew Osheroff
42e5321365 Better unmount behavior on OSX + OSX-specific unmount tests 2020-02-07 14:53:21 +01:00
Mathias Buus
5b62bc5380 2.2.0 2020-01-31 15:01:12 +01:00
Mathias Buus
9d0892abdd more timeout logic 2020-01-31 14:56:23 +01:00
Mathias Buus
38a4a52231 fix timeout code and make it opt-out 2020-01-31 14:09:00 +01:00
Andrew Osheroff
b63edfd61e 2.1.1 2020-01-29 13:32:35 +01:00
Andrew Osheroff
2f6d2054fe Add mkdir option + update README 2020-01-29 13:32:31 +01:00
Andrew Osheroff
9528eeff48
Update README.md
Add CI tag
2020-01-24 12:08:51 +01:00
Mathias Buus
2237155e60 2.1.0 2020-01-24 12:08:22 +01:00
Mathias Buus
1a08b3de57 support auto timeouts 2020-01-24 12:05:09 +01:00
Andrew Osheroff
95386bf49c Handle ENXIO in force mount 2020-01-24 12:01:29 +01:00
Andrew Osheroff
b0c3489030 Added force option + static unmount 2020-01-24 11:41:50 +01:00
Andrew Osheroff
f7dde5d16a Adding static unmount + force option 2020-01-24 11:41:50 +01:00
Andrew Osheroff
de825b61e6 Added fuse timeout read test 2020-01-24 10:32:01 +01:00
Mathias Buus
b333a2fd3d 2.0.7 2020-01-23 15:11:01 +01:00
Mathias Buus
b98133a18f fix defaults 2020-01-23 15:10:57 +01:00
Mathias Buus
baee91d5da 2.0.6 2020-01-14 16:55:44 +01:00
Mathias Buus
05f6eecd45 fix typo 2020-01-14 16:55:06 +01:00
Mathias Buus
bd61dff342 2.0.5 2020-01-14 16:38:48 +01:00
Mathias Buus
b46da7e4f9 use explicit 64bit ints for better cross platform support 2020-01-14 16:28:54 +01:00
Mathias Buus
8a6e480858 fix test on linux 2020-01-14 14:50:07 +01:00
Mathias Buus
aa745f4ff5 fix big file test 2020-01-14 14:41:27 +01:00
Mathias Buus
62401463df fix multiple options 2020-01-14 14:29:11 +01:00
Mathias Buus
5236374b01 add test for big files 2020-01-14 14:27:40 +01:00
Mathias Buus
b5e9b34dc2 add todo 2020-01-14 14:27:40 +01:00
Mathias Buus
383ace28ab >4gb support 2020-01-14 14:27:40 +01:00
Mathias Buus
b1189f356a 2.0.4 2020-01-08 21:34:17 +01:00
Mathias Buus
23c3eccd7f
fix segfault and add more sane api (#4) 2020-01-08 21:32:26 +01:00
Mathias Buus
85424c7705 2.0.3 2020-01-08 11:14:28 +01:00
Mathias Buus
393250d107 amc is the stat order 2020-01-07 10:54:30 +01:00
Mathias Buus
148f2af59d fix stat times not being set 2020-01-07 10:44:38 +01:00
Andrew Osheroff
0a17689a51 ftruncate fix 2020-01-06 16:51:19 +01:00
Andrew Osheroff
b085941a08 Removed package-lock.json 2019-12-18 11:32:28 +01:00
Mathias Buus
b746b10658 cli docs 2019-12-18 10:32:09 +01:00
Andrew Osheroff
8aa9c5d885 2.0.2 2019-12-17 15:48:32 +01:00
Andrew Osheroff
36bc277198 2.0.1 2019-12-17 15:48:23 +01:00
Andrew Osheroff
2d063aa975 Updated node-gyp-build 2019-12-17 15:47:37 +01:00
Mathias Buus
dcde0edbcc docs for configure,unconfigure 2019-12-17 15:28:35 +01:00
15 changed files with 801 additions and 207 deletions

View File

@ -33,5 +33,19 @@ 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
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; 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'

View File

@ -1,4 +1,5 @@
# 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.
@ -72,9 +73,28 @@ Create a new `Fuse` object.
```
displayFolder: 'Folder Name', // Add a name/icon to the mount volume on OSX,
debug: false, // Enable detailed tracing of operations.
force: false, // Attempt to unmount a the mountpoint before remounting.
mkdir: false // Create the mountpoint before mounting.
```
Additionally, all (FUSE-specific options)[http://man7.org/linux/man-pages/man8/mount.fuse.8.html] will be passed to the underlying FUSE module (though we use camel casing instead of snake casing).
#### `Fuse.isConfigured(cb)`
Returns `true` if FUSE has been configured on your machine and ready to be used, `false` otherwise.
#### `Fuse.configure(cb)`
Configures FUSE on your machine by enabling the FUSE kernel extension.
You usually want to do this as part of an installation phase for your app.
Might require `sudo` access.
#### `Fuse.unconfigure(cb)`
Unconfigures FUSE on your machine. Basically undos any change the above
method does.
See the CLI section below on how to run these commands from the command line if you prefer doing that.
### FUSE API
Most of the [FUSE api](http://fuse.sourceforge.net/doxygen/structfuse__operations.html) is supported. In general the callback for each op should be called with `cb(returnCode, [value])` where the return code is a number (`0` for OK and `< 0` for errors). See below for a list of POSIX error codes.
@ -182,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)`
@ -292,6 +321,17 @@ Called when a new directory is being created
Called when a directory is being removed
## CLI
There is a CLI tool available to help you configure the FUSE kernel extension setup
if you don't want to use the JavaScript API for that
```
npm install -g fuse-native
fuse-native is-configured # checks if the kernel extension is already configured
fuse-native configure # configures the kernel extension
```
## License
MIT for these bindings.

View File

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

View File

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

View File

@ -18,6 +18,10 @@
#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;\
@ -43,7 +47,7 @@
uint32_t op = op_##name;\
FUSE_NATIVE_CALLBACK(ft->handlers[op], {\
napi_value argv[callbackArgs + 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]));\
callbackBlk\
NAPI_MAKE_CALLBACK(env, NULL, ctx, callback, callbackArgs + 2, argv, NULL)\
@ -65,6 +69,13 @@
#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;
@ -109,6 +120,7 @@ typedef struct {
pthread_t thread;
pthread_attr_t attr;
napi_ref ctx;
napi_ref malloc;
// Operation handlers
napi_ref handlers[35];
@ -125,6 +137,8 @@ typedef struct {
} fuse_thread_t;
typedef struct {
napi_ref self;
// Opcode
uint32_t op;
void *op_fn;
@ -141,8 +155,8 @@ typedef struct {
dev_t dev;
uid_t uid;
gid_t gid;
uint32_t atim[2];
uint32_t mtim[2];
size_t atime;
size_t mtime;
int32_t res;
// Extended attributes
@ -168,61 +182,62 @@ typedef struct {
} fuse_thread_locals_t;
static pthread_key_t thread_locals_key;
static fuse_thread_locals_t* get_thread_locals ();
static fuse_thread_locals_t* get_thread_locals();
// Helpers
// TODO: Extract into a separate file.
static void fin (napi_env env, void *fin_data, void* fin_hint) {
//exit(0);
static uint64_t uint32s_to_uint64 (uint32_t **ints) {
uint64_t low = *((*ints)++);
uint64_t high = *((*ints)++);
return high * 4294967296 + low;
}
static void to_timespec (struct timespec* ts, uint32_t* int_ptr) {
long unsigned int ms = *int_ptr + (*(int_ptr + 1) * 4294967296);
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 void from_timespec(const struct timespec* ts, uint32_t* int_ptr) {
long unsigned int ms = (ts->tv_sec * 1000) + (ts->tv_nsec / 1000000);
*int_ptr = ms % 4294967296;
*(int_ptr + 1) = (ms - *int_ptr) / 4294967296;
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 = *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 = *ints++;
stat->st_blocks = uint32s_to_uint64(&ints);
#ifdef __APPLE__
to_timespec(&stat->st_atimespec, ints);
to_timespec(&stat->st_mtimespec, ints + 2);
to_timespec(&stat->st_ctimespec, ints + 4);
uint32s_to_timespec(&stat->st_atimespec, &ints);
uint32s_to_timespec(&stat->st_mtimespec, &ints);
uint32s_to_timespec(&stat->st_ctimespec, &ints);
#else
to_timespec(&stat->st_atim, ints);
to_timespec(&stat->st_mtim, ints + 2);
to_timespec(&stat->st_ctim, ints + 4);
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++;
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
@ -321,14 +336,14 @@ FUSE_METHOD(create, 2, 1, (const char *path, mode_t mode, struct fuse_file_info
}
})
FUSE_METHOD_VOID(utimens, 3, 0, (const char *path, const struct timespec tv[2]), {
FUSE_METHOD_VOID(utimens, 5, 0, (const char *path, const struct timespec tv[2]), {
l->path = path;
from_timespec(&tv[0], l->atim);
from_timespec(&tv[1], l->mtim);
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]));
napi_create_external_arraybuffer(env, l->atim, 2 * sizeof(uint32_t), &fin, NULL, &argv[3]);
napi_create_external_arraybuffer(env, l->mtim, 2 * sizeof(uint32_t), &fin, NULL, &argv[4]);
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), {
@ -355,7 +370,7 @@ FUSE_METHOD_VOID(releasedir, 2, 0, (const char *path, struct fuse_file_info *inf
}
})
FUSE_METHOD(read, 5, 1, (const char *path, char *buf, size_t len, off_t offset, struct fuse_file_info *info), {
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;
@ -364,14 +379,14 @@ FUSE_METHOD(read, 5, 1, (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_uint32(env, l->info->fh, &(argv[3]));
napi_create_external_buffer(env, l->len, (char *) l->buf, &fin, NULL, &(argv[4]));
napi_create_external_buffer(env, l->len, (char *) l->buf, NULL, NULL, &(argv[4]));
napi_create_uint32(env, l->len, &(argv[5]));
napi_create_uint32(env, l->offset, &(argv[6]));
FUSE_UINT64_TO_INTS_ARGV(l->offset, 6)
}, {
// TODO: handle bytes processed?
if (IS_ARRAY_BUFFER_DETACH_SUPPORTED == 1) assert(napi_detach_arraybuffer(env, argv[3]) == napi_ok);
})
FUSE_METHOD(write, 5, 1, (const char *path, const char *buf, size_t len, off_t offset, struct fuse_file_info *info), {
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;
@ -380,11 +395,11 @@ FUSE_METHOD(write, 5, 1, (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_uint32(env, l->info->fh, &(argv[3]));
napi_create_external_buffer(env, l->len, (char *) l->buf, &fin, NULL, &(argv[4]));
napi_create_external_buffer(env, l->len, (char *) l->buf, NULL, NULL, &(argv[4]));
napi_create_uint32(env, l->len, &(argv[5]));
napi_create_uint32(env, l->offset, &(argv[6]));
FUSE_UINT64_TO_INTS_ARGV(l->offset, 6)
}, {
// TODO: handle bytes processed?
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), {
@ -434,7 +449,7 @@ FUSE_METHOD(readdir, 1, 2, (const char *path, void *buf, fuse_fill_dir_t filler,
#ifdef __APPLE__
FUSE_METHOD_VOID(setxattr, 6, 0, (const char *path, const char *name, const char *value, size_t size, int flags, uint32_t position), {
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;
@ -444,13 +459,14 @@ FUSE_METHOD_VOID(setxattr, 6, 0, (const char *path, const char *name, const char
}, {
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->value, NAPI_AUTO_LENGTH, &(argv[4]));
napi_create_uint32(env, l->size, &(argv[5]));
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]));
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_VOID(getxattr, 5, 0, (const char *path, const char *name, char *value, size_t size, uint32_t position), {
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;
@ -459,14 +475,15 @@ FUSE_METHOD_VOID(getxattr, 5, 0, (const char *path, const char *name, char *valu
}, {
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->value, NAPI_AUTO_LENGTH, &(argv[4]));
napi_create_uint32(env, l->size, &(argv[5]));
napi_create_uint32(env, l->position, &(argv[6]));
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_VOID(setxattr, 5, 0, (const char *path, const char *name, const char *value, size_t size, int flags), {
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;
@ -475,12 +492,14 @@ FUSE_METHOD_VOID(setxattr, 5, 0, (const char *path, const char *name, const char
}, {
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->value, NAPI_AUTO_LENGTH, &(argv[4]));
napi_create_uint32(env, l->size, &(argv[5]));
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_VOID(getxattr, 4, 0, (const char *path, const char *name, char *value, size_t size), {
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;
@ -488,20 +507,23 @@ FUSE_METHOD_VOID(getxattr, 4, 0, (const char *path, const char *name, char *valu
}, {
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->value, NAPI_AUTO_LENGTH, &(argv[4]));
napi_create_uint32(env, l->size, &(argv[5]));
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_VOID(listxattr, 3, 0, (const char *path, char *list, size_t size), {
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, &fin, NULL, &(argv[3]));
napi_create_uint32(env, l->size, &(argv[4]));
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), {
@ -553,26 +575,26 @@ FUSE_METHOD_VOID(fsyncdir, 3, 0, (const char *path, int datasync, struct fuse_fi
})
FUSE_METHOD_VOID(truncate, 2, 0, (const char *path, off_t size), {
FUSE_METHOD_VOID(truncate, 3, 0, (const char *path, off_t size), {
l->path = path;
l->len = size;
l->offset = size;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_uint32(env, l->len, &(argv[3]));
FUSE_UINT64_TO_INTS_ARGV(l->offset, 3)
})
FUSE_METHOD_VOID(ftruncate, 2, 0, (const char *path, off_t size, struct fuse_file_info *info), {
FUSE_METHOD_VOID(ftruncate, 4, 0, (const char *path, off_t size, struct fuse_file_info *info), {
l->path = path;
l->len = size;
l->offset = size;
l->info = info;
}, {
napi_create_string_utf8(env, l->path, NAPI_AUTO_LENGTH, &(argv[2]));
napi_create_uint32(env, l->len, &(argv[3]));
if (l->info != NULL) {
napi_create_uint32(env, l->info->fh, &(argv[4]));
napi_create_uint32(env, l->info->fh, &(argv[3]));
} else {
napi_create_uint32(env, 0, &(argv[4]));
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), {
@ -661,8 +683,10 @@ 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) {\
FUSE_NATIVE_CALLBACK(ft->handlers[op_init], {
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_MAKE_CALLBACK(env, NULL, ctx, callback, 2, argv, NULL);
})
}
@ -678,10 +702,13 @@ NAPI_METHOD(fuse_native_signal_init) {
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;
}
@ -691,12 +718,27 @@ 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_locals_t *l = (fuse_thread_locals_t *) handle->data;
fuse_thread_t *ft = l->fuse;
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);
@ -705,6 +747,8 @@ static void fuse_native_async_init (uv_async_t* handle) {
uv_sem_init(&(l->sem), 0);
l->async.data = l;
ft->async.data = l;
l->fuse = ft;
uv_sem_post(&(ft->sem));
}
@ -716,21 +760,18 @@ static fuse_thread_locals_t* get_thread_locals () {
void *data = pthread_getspecific(thread_locals_key);
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.
uv_mutex_lock(&(ft->mut));
ft->async.data = l;
ft->async.data = ft;
// Notify the main thread to uv_async_init l->async.
uv_async_send(&(ft->async));
uv_sem_wait(&(ft->sem));
l->async.data = l;
fuse_thread_locals_t *l = (fuse_thread_locals_t*) ft->async.data;
pthread_setspecific(thread_locals_key, (void *) l);
uv_mutex_unlock(&(ft->mut));
@ -750,14 +791,15 @@ static void* start_fuse_thread (void *data) {
}
NAPI_METHOD(fuse_native_mount) {
NAPI_ARGV(6)
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_value handlers = argv[4];
NAPI_ARGV_BUFFER_CAST(uint32_t *, implemented, 5)
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;
@ -852,6 +894,7 @@ NAPI_METHOD(fuse_native_unmount) {
// pthread_join(ft->thread, NULL);
}
// TODO: fix the async holding the loop
uv_unref((uv_handle_t *) &(ft->async));
ft->mounted--;
@ -859,6 +902,13 @@ NAPI_METHOD(fuse_native_unmount) {
}
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)

270
index.js
View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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