mirror of
https://github.com/fuse-friends/fuse-native
synced 2024-10-27 18:34:01 +00:00
Compare commits
52 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
21658b5ebb | ||
|
34554d664b | ||
|
14b3a23d68 | ||
|
3d8ddd21b5 | ||
|
fc92548561 | ||
|
7f529df642 | ||
|
977307b1b3 | ||
|
56e4b99e04 | ||
|
4606e58acd | ||
|
b7f98c6192 | ||
|
3507fe0907 | ||
|
e218d40f90 | ||
|
519b468995 | ||
|
2e6c27c3a3 | ||
|
e09247de88 | ||
|
42e5321365 | ||
|
5b62bc5380 | ||
|
9d0892abdd | ||
|
38a4a52231 | ||
|
b63edfd61e | ||
|
2f6d2054fe | ||
|
9528eeff48 | ||
|
2237155e60 | ||
|
1a08b3de57 | ||
|
95386bf49c | ||
|
b0c3489030 | ||
|
f7dde5d16a | ||
|
de825b61e6 | ||
|
b333a2fd3d | ||
|
b98133a18f | ||
|
baee91d5da | ||
|
05f6eecd45 | ||
|
bd61dff342 | ||
|
b46da7e4f9 | ||
|
8a6e480858 | ||
|
aa745f4ff5 | ||
|
62401463df | ||
|
5236374b01 | ||
|
b5e9b34dc2 | ||
|
383ace28ab | ||
|
b1189f356a | ||
|
23c3eccd7f | ||
|
85424c7705 | ||
|
393250d107 | ||
|
148f2af59d | ||
|
0a17689a51 | ||
|
b085941a08 | ||
|
b746b10658 | ||
|
8aa9c5d885 | ||
|
36bc277198 | ||
|
2d063aa975 | ||
|
dcde0edbcc |
16
.travis.yml
16
.travis.yml
@ -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'
|
||||
|
54
README.md
54
README.md
@ -1,4 +1,5 @@
|
||||
# fuse-native
|
||||
[](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.
|
||||
|
13
binding.gyp
13
binding.gyp
@ -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",
|
||||
|
@ -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)
|
||||
|
212
fuse-native.c
212
fuse-native.c
@ -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
270
index.js
@ -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
|
||||
}
|
||||
|
11
package.json
11
package.json
@ -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
116
test/big.js
Normal file
@ -0,0 +1,116 @@
|
||||
const tape = require('tape')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const concat = require('concat-stream')
|
||||
|
||||
const Fuse = require('../')
|
||||
const createMountpoint = require('./fixtures/mnt')
|
||||
const stat = require('./fixtures/stat')
|
||||
const { unmount } = require('./helpers')
|
||||
|
||||
const mnt = createMountpoint()
|
||||
|
||||
tape('read and write big file', function (t) {
|
||||
let size = 0
|
||||
const reads = [0, 4 * 1024 * 1024 * 1024, 6 * 1024 * 1024 * 1024]
|
||||
const writes = [0, 4 * 1024 * 1024 * 1024, 6 * 1024 * 1024 * 1024]
|
||||
|
||||
var ops = {
|
||||
force: true,
|
||||
readdir (path, cb) {
|
||||
if (path === '/') return process.nextTick(cb, null, ['test'])
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
},
|
||||
getattr (path, cb) {
|
||||
if (path === '/') return process.nextTick(cb, null, stat({ mode: 'dir', size: 4096 }))
|
||||
if (path === '/test') return process.nextTick(cb, null, stat({ mode: 'file', size, mtime: new Date() }))
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
},
|
||||
open (path, flags, cb) {
|
||||
return process.nextTick(cb, 0, 42)
|
||||
},
|
||||
release (path, fd, cb) {
|
||||
t.same(fd, 42, 'fd was passed to release')
|
||||
return process.nextTick(cb, 0)
|
||||
},
|
||||
read (path, fd, buf, len, pos, cb) {
|
||||
t.same(pos, reads.shift(), 'read is expected')
|
||||
buf.fill(0)
|
||||
if (pos + len > size) return cb(Math.max(size - pos, 0))
|
||||
cb(len)
|
||||
},
|
||||
ftruncate (path, fd, len, cb) {
|
||||
size = len
|
||||
cb(0)
|
||||
},
|
||||
truncate (path, len, cb) {
|
||||
size = len
|
||||
cb(0)
|
||||
},
|
||||
write (path, fd, buf, len, pos, cb) {
|
||||
if (!writes.length) return cb(-1)
|
||||
t.same(pos, writes.shift(), 'write is expected')
|
||||
size = Math.max(pos + len, size)
|
||||
cb(len)
|
||||
}
|
||||
}
|
||||
|
||||
const fuse = new Fuse(mnt, ops, { debug: !true, autoCache: true })
|
||||
let fd = 0
|
||||
|
||||
run(
|
||||
(_, cb) => fuse.mount(cb),
|
||||
open('w+'),
|
||||
(_, cb) => fs.fstat(fd, cb),
|
||||
checkSize(0),
|
||||
(_, cb) => fs.ftruncate(fd, 4 * 1024 * 1024 * 1024 + 1, cb),
|
||||
(_, cb) => fs.fstat(fd, cb),
|
||||
checkSize(4 * 1024 * 1024 * 1024 + 1),
|
||||
(_, cb) => fs.truncate(path.join(mnt, 'test'), 6 * 1024 * 1024 * 1024 + 2, cb),
|
||||
(_, cb) => fs.fstat(fd, cb),
|
||||
checkSize(6 * 1024 * 1024 * 1024 + 2),
|
||||
(_, cb) => fs.write(fd, Buffer.alloc(4096), 0, 4096, 0, cb),
|
||||
(_, cb) => fs.write(fd, Buffer.alloc(4096), 0, 4096, 4 * 1024 * 1024 * 1024, cb),
|
||||
(_, cb) => fs.write(fd, Buffer.alloc(4096), 0, 4096, 6 * 1024 * 1024 * 1024, cb),
|
||||
(_, cb) => fs.fstat(fd, cb),
|
||||
checkSize(6 * 1024 * 1024 * 1024 + 4096),
|
||||
(_, cb) => fs.close(fd, cb),
|
||||
open('a+'),
|
||||
(_, cb) => fs.read(fd, Buffer.alloc(4096), 0, 4096, 0, cb),
|
||||
(_, cb) => fs.read(fd, Buffer.alloc(4096), 0, 4096, 4 * 1024 * 1024 * 1024, cb),
|
||||
(_, cb) => fs.read(fd, Buffer.alloc(4096), 0, 4096, 6 * 1024 * 1024 * 1024, cb),
|
||||
(_, cb) => fs.close(fd, cb),
|
||||
(_, cb) => unmount(fuse, cb),
|
||||
() => {
|
||||
t.same(writes.length, 0)
|
||||
t.same(reads.length, 0)
|
||||
t.end()
|
||||
}
|
||||
)
|
||||
|
||||
function open (mode) {
|
||||
return (_, cb) => {
|
||||
fs.open(path.join(mnt, 'test'), mode, function (_, res) {
|
||||
fd = res
|
||||
cb()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function checkSize (n) {
|
||||
return ({ size}, cb) => {
|
||||
t.same(size, n)
|
||||
cb()
|
||||
}
|
||||
}
|
||||
|
||||
function run (...fns) {
|
||||
const all = [...fns]
|
||||
tick()
|
||||
function tick (err, val) {
|
||||
t.error(err, 'no error')
|
||||
const next = all.shift()
|
||||
if (next) next(val, tick)
|
||||
}
|
||||
}
|
||||
})
|
18
test/fixtures/mnt.js
vendored
18
test/fixtures/mnt.js
vendored
@ -2,12 +2,18 @@ var os = require('os')
|
||||
var path = require('path')
|
||||
var fs = require('fs')
|
||||
|
||||
var mnt = path.join(os.tmpdir(), 'fuse-bindings-' + process.pid + '-' + Date.now())
|
||||
function create (opts = {}) {
|
||||
var mnt = path.join(os.tmpdir(), 'fuse-bindings-' + process.pid + '-' + Date.now())
|
||||
|
||||
try {
|
||||
fs.mkdirSync(mnt)
|
||||
} catch (err) {
|
||||
// do nothing
|
||||
if (!opts.doNotCreate) {
|
||||
try {
|
||||
fs.mkdirSync(mnt)
|
||||
} catch (err) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
return mnt
|
||||
}
|
||||
|
||||
module.exports = mnt
|
||||
module.exports = create
|
||||
|
34
test/fixtures/simple-fs.js
vendored
Normal file
34
test/fixtures/simple-fs.js
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
const stat = require('./stat')
|
||||
const Fuse = require('../../')
|
||||
|
||||
module.exports = function (tests = {}) {
|
||||
return {
|
||||
readdir: function (path, cb) {
|
||||
if (tests.readdir) tests.readdir(path)
|
||||
if (path === '/') return process.nextTick(cb, null, ['test'])
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
},
|
||||
getattr: function (path, cb) {
|
||||
if (tests.getattr) tests.getattr(path)
|
||||
if (path === '/') return process.nextTick(cb, null, stat({ mode: 'dir', size: 4096 }))
|
||||
if (path === '/test') return process.nextTick(cb, null, stat({ mode: 'file', size: 11 }))
|
||||
return process.nextTick(cb, Fuse.ENOENT)
|
||||
},
|
||||
open: function (path, flags, cb) {
|
||||
if (tests.open) tests.open(path, flags)
|
||||
return process.nextTick(cb, 0, 42)
|
||||
},
|
||||
release: function (path, fd, cb) {
|
||||
if (tests.release) tests.release(path, fd)
|
||||
return process.nextTick(cb, 0)
|
||||
},
|
||||
read: function (path, fd, buf, len, pos, cb) {
|
||||
if (tests.read) tests.read(path, fd, buf, len, pos)
|
||||
var str = 'hello world'.slice(pos, pos + len)
|
||||
if (!str) return process.nextTick(cb, 0)
|
||||
buf.write(str)
|
||||
return process.nextTick(cb, str.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
146
test/misc.js
146
test/misc.js
@ -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'
|
||||
})
|
||||
}
|
||||
|
104
test/read.js
104
test/read.js
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user