From e4732baf83131ba8d2d7500547cd4dc4e5189f14 Mon Sep 17 00:00:00 2001 From: Andrew Osheroff Date: Thu, 1 Aug 2019 14:24:01 +0200 Subject: [PATCH] init flow works, getattr fails --- example.js | 13 --- fuse-native.c | 72 +++++++----- index.js | 260 +++++++++++++++++++++--------------------- package.json | 7 +- test/fixtures/mnt.js | 13 +++ test/fixtures/stat.js | 11 ++ test/links.js | 62 ++++++++++ test/misc.js | 43 +++++++ test/read.js | 64 +++++++++++ test/write.js | 54 +++++++++ 10 files changed, 429 insertions(+), 170 deletions(-) create mode 100644 test/fixtures/mnt.js create mode 100644 test/fixtures/stat.js create mode 100644 test/links.js create mode 100644 test/misc.js create mode 100644 test/read.js create mode 100644 test/write.js diff --git a/example.js b/example.js index d2311a2..a8592ca 100644 --- a/example.js +++ b/example.js @@ -69,16 +69,3 @@ process.on('SIGINT', function () { } }) }) - -function emptyStat (mode) { - return { - mtime: new Date(), - atime: new Date(), - ctime: new Date(), - nlink: 1, - size: 100, - mode: mode, - uid: process.getuid ? process.getuid() : 0, - gid: process.getgid ? process.getgid() : 0 - } -} diff --git a/fuse-native.c b/fuse-native.c index c387e4f..2b0a530 100644 --- a/fuse-native.c +++ b/fuse-native.c @@ -39,29 +39,36 @@ fuse_native_semaphore_wait(&(l->sem)); \ return l->res; -#define FUSE_METHOD(name, callbackArgs, signalArgs, signature, callBlk, callbackBlk, signalBlk) \ - static void fuse_native_dispatch_##name (uv_async_t* handle, int status, fuse_thread_locals_t* l, fuse_thread_t* ft) { \ - uint32_t op = op_##name; \ - FUSE_NATIVE_CALLBACK(ft->handlers[op], { \ - napi_value argv[callbackArgs + 2]; \ - napi_create_external_buffer(env, sizeof(fuse_thread_locals_t), l, &fin, NULL, &(argv[0])); \ - napi_create_uint32(env, l->op, &(argv[1])); \ - callbackBlk \ - NAPI_MAKE_CALLBACK(env, NULL, ctx, callback, callbackArgs + 2, argv, NULL) \ - }) \ - } \ - NAPI_METHOD(fuse_native_signal_##name) { \ - NAPI_ARGV(signalArgs + 2) \ - NAPI_ARGV_BUFFER_CAST(fuse_thread_locals_t *, l, 0); \ - NAPI_ARGV_INT32(res, 1); \ - signalBlk \ - l->res = res; \ - fuse_native_semaphore_signal(&(l->sem)); \ - return NULL; \ - } \ - static int fuse_native_##name signature { \ - FUSE_NATIVE_HANDLER(name, callBlk) \ - } \ +#define FUSE_METHOD(name, callbackArgs, signalArgs, signature, callBlk, callbackBlk, signalBlk)\ + static void fuse_native_dispatch_##name (uv_async_t* handle, int status, fuse_thread_locals_t* l, fuse_thread_t* ft) {\ + printf("at beginning of fuse_native_dispatch_%s\n", #name);\ + uint32_t op = op_##name;\ + printf("op here: %i\n", op);\ + printf("handler here: %zu\n", ft->handlers[op]);\ + printf("in fuse_native_dispatch, op: %i, handler: %zu\n", op, ft->handlers[op]);\ + 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_create_uint32(env, l->op, &(argv[1]));\ + callbackBlk\ + printf("in fuse_native_callback, calling callback for %s\n", #name);\ + NAPI_MAKE_CALLBACK(env, NULL, ctx, callback, callbackArgs + 2, argv, NULL)\ + })\ + }\ + NAPI_METHOD(fuse_native_signal_##name) {\ + NAPI_ARGV(signalArgs + 2)\ + NAPI_ARGV_BUFFER_CAST(fuse_thread_locals_t *, l, 0);\ + NAPI_ARGV_INT32(res, 1);\ + signalBlk\ + l->res = res;\ + printf("in _fuse_native_signal_%s, signalling semaphore\n", #name);\ + fuse_native_semaphore_signal(&(l->sem));\ + return NULL;\ + }\ + static int fuse_native_##name signature {\ + printf("in fuse_native_%s\n", #name);\ + FUSE_NATIVE_HANDLER(name, callBlk)\ + } // Opcodes @@ -110,7 +117,7 @@ typedef struct { napi_ref ctx; // Operation handlers - napi_ref handlers[34]; + napi_ref handlers[35]; struct fuse *fuse; struct fuse_chan *ch; @@ -537,7 +544,13 @@ FUSE_METHOD(removexattr, 2, 0, (const char *path, const char *name), { }, {}) -FUSE_METHOD(init, 0, 0, (struct fuse_conn_info *conn, struct fuse_config *cfg), {}, {}, {}) +FUSE_METHOD(init, 0, 0, (struct fuse_conn_info *conn, struct fuse_config *cfg), { + printf("in fuse_native_init\n"); + }, { + printf("in fuse_native_init_callback\n"); + }, { + printf("in fuse_native_init_signal\n"); + }) FUSE_METHOD(error, 0, 0, (), {}, {}, {}) @@ -713,6 +726,8 @@ static void fuse_native_dispatch (uv_async_t* handle, int status) { fuse_thread_locals_t *l = (fuse_thread_locals_t *) handle->data; fuse_thread_t *ft = l->fuse; + printf("dispatching %i\n", l->op); + // TODO: Either use a function pointer (like ft->handlers[op]) or generate with a macro. switch (l->op) { case (op_init): return fuse_native_dispatch_init(handle, status, l, ft); @@ -799,10 +814,9 @@ NAPI_METHOD(fuse_native_mount) { NAPI_ARGV_BUFFER_CAST(fuse_thread_t *, ft, 2); napi_create_reference(env, argv[3], 1, &(ft->ctx)); - napi_ref handlers; - napi_create_reference(env, argv[4], 1, &handlers); - + napi_value handlers = argv[4]; NAPI_FOR_EACH(handlers, handler) { + printf("creating reference for handler: %d\n", i); napi_create_reference(env, handler, 1, &ft->handlers[i]); } @@ -845,7 +859,7 @@ NAPI_METHOD(fuse_native_mount) { .destroy = fuse_native_destroy }; - int _argc = 2; + int _argc = (strcmp(mntopts, "-o") <= 0) ? 1 : 2; char *_argv[] = { (char *) "fuse_bindings_dummy", (char *) mntopts diff --git a/index.js b/index.js index 9e9e4a8..1eb399b 100644 --- a/index.js +++ b/index.js @@ -9,115 +9,118 @@ const HAS_FOLDER_ICON = IS_OSX && fs.existsSync(OSX_FOLDER_ICON) const binding = require('node-gyp-build')(__dirname) const OpcodesAndDefaults = new Map([ - [ 'init', { + ['init', { op: 0 - } ], - [ 'error', { + }], + ['error', { op: 1 - } ], - [ 'access', { + }], + ['access', { op: 2, - defaults: [ 0 ] - } ], - [ 'statfs', { + defaults: [0] + }], + ['statfs', { op: 3, - defaults: [ getStatfsArray() ] - } ], - [ 'fgetattr', { + defaults: [getStatfsArray()] + }], + ['fgetattr', { op: 4, - defaults: [ getStatArray() ] - } ], - [ 'getattr', { + defaults: [getStatArray()] + }], + ['getattr', { op: 5, - defaults: [ getStatArray() ] - } ], - [ 'flush', { - op: 6, - } ], - [ 'fsync', { - op: 7 - } ], - [ 'fsyncdir', { - op: 8 - } ], - [ 'readdir', { - op: 9 - } ], - [ 'truncate', { - op: 10 - } ], - [ 'ftruncate', { - op: 11 - } ], - [ 'utimens', { - op: 12 - } ], - [ 'readlink', { - op: 13 - } ], - [ 'chown', { - op: 14 - } ], - [ 'chmod', { - op: 15 - } ], - [ 'mknod', { - op: 16 - } ], - [ 'setxattr', { - op: 17 - } ], - [ 'getxattr', { - op: 18 - } ], - [ 'listxattr', { - op: 19 - } ], - [ 'removexattr', { - op: 20 - } ], - [ 'open', { - op: 21 - } ], - [ 'opendir', { - op: 22 - } ], - [ 'read', { - op: 23 - } ], - [ 'write', { - op: 24 - } ], - [ 'release', { - op: 25 - } ], - [ 'releasedir', { - op: 26 - } ], - [ 'create', { - op: 27 - } ], - [ 'unlink', { - op: 28 - } ], - [ 'rename', { - op: 29 - } ], - [ 'link', { - op: 30 - } ], - [ 'symlink', { - op: 31 - } ], - [ 'mkdir', { - op: 32 - } ], - [ 'rmdir', { - op: 33 - } ], - [ 'destroy', { - op: 34 - } ] + defaults: [getStatArray()] + }], + ['flush', { + op: 6 + }], + ['fsync', { + op: 7 + }], + ['fsyncdir', { + op: 8 + }], + ['readdir', { + op: 9 + }], + ['truncate', { + op: 10 + }], + ['ftruncate', { + op: 11 + }], + ['utimens', { + op: 12 + }], + ['readlink', { + op: 13, + defaults: [''] + }], + ['chown', { + op: 14 + }], + ['chmod', { + op: 15 + }], + ['mknod', { + op: 16 + }], + ['setxattr', { + op: 17 + }], + ['getxattr', { + op: 18 + }], + ['listxattr', { + op: 19 + }], + ['removexattr', { + op: 20 + }], + ['open', { + op: 21 + }], + ['opendir', { + op: 22 + }], + ['read', { + op: 23, + defaults: [0] + }], + ['write', { + op: 24, + defaults: [0] + }], + ['release', { + op: 25 + }], + ['releasedir', { + op: 26 + }], + ['create', { + op: 27 + }], + ['unlink', { + op: 28 + }], + ['rename', { + op: 29 + }], + ['link', { + op: 30 + }], + ['symlink', { + op: 31 + }], + ['mkdir', { + op: 32 + }], + ['rmdir', { + op: 33 + }], + ['destroy', { + op: 34 + }] ]) class Fuse { @@ -133,8 +136,8 @@ class Fuse { const implemented = [0, 1, 5] if (ops) { - for (const [name, code] of Opcodes) { - if (ops[name]) implemented.push(code) + for (const [name, { op }] of OpcodesAndDefaults) { + if (ops[name]) implemented.push(op) } } this._implemented = new Set(implemented) @@ -189,7 +192,9 @@ class Fuse { // Handlers _makeHandlerArray () { + const self = this const handlers = new Array(OpcodesAndDefaults.size) + for (const [name, { op, defaults }] of OpcodesAndDefaults) { const nativeSignal = binding[`fuse_native_signal_${name}`] if (!nativeSignal) continue @@ -200,22 +205,25 @@ class Fuse { return handlers function makeHandler (name, op, defaults, nativeSignal) { - return () => { - const boundSignal = signal.bind(arguments[0]) - const funcName = `_$name` - if (!this[funcName] || !this._implemented.has(op)) return boundSignal(-1, defaults) - return this[funcName].apply(null, [boundSignal, [...arguments].slice(1) ]) + return function () { + const boundSignal = signal.bind(null, arguments[0]) + const funcName = `_${name}` + if (!self[funcName] || !self._implemented.has(op)) return boundSignal(-1, defaults) + return self[funcName].apply(self, [boundSignal, ...[...arguments].slice(1)]) } - function signal (nativeHandler, err, args) { - const args = [nativeHandler, err] - if (defaults) args.concat(defaults) - return nativeSignal(args) + function signal (nativeHandler, err, ...args) { + console.log('nativeHanlder:', nativeHandler, 'err:', err, 'args:', args) + const arr = [nativeHandler, err, ...args] + if (defaults && (!args || !args.length)) arr.concat(defaults) + console.log('signalling with arr:', arr) + return nativeSignal(...arr) } } } _init (signal) { + console.log('IN JS INIT') if (!this.ops.init) { signal(0) return @@ -461,7 +469,8 @@ class Fuse { mount (cb) { const opts = this._fuseOptions() - + console.log('mounting at %s with opts: %s', this.mnt, opts) + console.log('handlers:', this._handlers) 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')) @@ -646,7 +655,7 @@ function setDoubleInt (arr, idx, num) { arr[idx + 1] = (num - arr[idx]) / 4294967296 } -function getDoubleInt(arr, idx) { +function getDoubleInt (arr, idx) { arr = new Uint32Array(arr) var num = arr[idx + 1] * 4294967296 num += arr[idx] @@ -658,20 +667,17 @@ function getStatArray (stat) { 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 + 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()) return ints } - -function noop () {} -function call (cb) { cb() } diff --git a/package.json b/package.json index 70cfdbd..df0dad3 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "index.js", "scripts": { "install": "node-gyp-build", + "test": "standard && test/*.js", "prebuild": "prebuildify -a --strip", "prebuild-ia32": "prebuildify -a --strip --arch=ia32" }, @@ -14,7 +15,11 @@ "napi-macros": "^2.0.0", "node-gyp-build": "^3.2.2" }, - "devDependencies": {}, + "devDependencies": { + "concat-stream": "^2.0.0", + "standard": "^13.1.0", + "tape": "^4.11.0" + }, "repository": { "type": "git", "url": "https://github.com/fuse-friends/fuse-native.git" diff --git a/test/fixtures/mnt.js b/test/fixtures/mnt.js new file mode 100644 index 0000000..ab7762b --- /dev/null +++ b/test/fixtures/mnt.js @@ -0,0 +1,13 @@ +var os = require('os') +var path = require('path') +var fs = require('fs') + +var mnt = path.join(os.tmpdir(), 'fuse-bindings-' + process.pid + '-' + Date.now()) + +try { + fs.mkdirSync(mnt) +} catch (err) { + // do nothing +} + +module.exports = mnt diff --git a/test/fixtures/stat.js b/test/fixtures/stat.js new file mode 100644 index 0000000..59de361 --- /dev/null +++ b/test/fixtures/stat.js @@ -0,0 +1,11 @@ +module.exports = function (st) { + return { + mtime: st.mtime || new Date(), + atime: st.atime || new Date(), + ctime: st.ctime || new Date(), + size: st.size !== undefined ? st.size : 0, + mode: st.mode === 'dir' ? 16877 : (st.mode === 'file' ? 33188 : (st.mode === 'link' ? 41453 : st.mode)), + uid: st.uid !== undefined ? st.uid : process.getuid(), + gid: st.gid !== undefined ? st.gid : process.getgid() + } +} diff --git a/test/links.js b/test/links.js new file mode 100644 index 0000000..19f02e1 --- /dev/null +++ b/test/links.js @@ -0,0 +1,62 @@ +var mnt = require('./fixtures/mnt') +var stat = require('./fixtures/stat') +var fuse = require('../') +var tape = require('tape') +var fs = require('fs') +var path = require('path') + +tape('readlink', function (t) { + var ops = { + force: true, + readdir: function (path, cb) { + if (path === '/') return cb(null, ['hello', 'link']) + return cb(fuse.ENOENT) + }, + readlink: function (path, cb) { + cb(0, 'hello') + }, + getattr: function (path, cb) { + if (path === '/') return cb(null, stat({ mode: 'dir', size: 4096 })) + if (path === '/hello') return cb(null, stat({ mode: 'file', size: 11 })) + if (path === '/link') return cb(null, stat({ mode: 'link', size: 5 })) + return cb(fuse.ENOENT) + }, + open: function (path, flags, cb) { + cb(0, 42) + }, + read: function (path, fd, buf, len, pos, cb) { + var str = 'hello world'.slice(pos, pos + len) + if (!str) return cb(0) + buf.write(str) + return cb(str.length) + } + } + + fuse.mount(mnt, ops, function (err) { + t.error(err, 'no error') + + fs.lstat(path.join(mnt, 'link'), function (err, stat) { + t.error(err, 'no error') + t.same(stat.size, 5, 'correct size') + + fs.stat(path.join(mnt, 'hello'), function (err, stat) { + t.error(err, 'no error') + t.same(stat.size, 11, 'correct size') + + fs.readlink(path.join(mnt, 'link'), function (err, dest) { + t.error(err, 'no error') + t.same(dest, 'hello', 'link resolves') + + fs.readFile(path.join(mnt, 'link'), function (err, buf) { + t.error(err, 'no error') + t.same(buf, new Buffer('hello world'), 'can read link content') + + fuse.unmount(mnt, function () { + t.end() + }) + }) + }) + }) + }) + }) +}) diff --git a/test/misc.js b/test/misc.js new file mode 100644 index 0000000..49ad64a --- /dev/null +++ b/test/misc.js @@ -0,0 +1,43 @@ +var mnt = require('./fixtures/mnt') +var fuse = require('../') +var tape = require('tape') + +tape('mount', function (t) { + fuse.mount(mnt, { force: true }, function (err) { + t.error(err, 'no error') + t.ok(true, 'works') + fuse.unmount(mnt, function () { + t.end() + }) + }) +}) + +tape('mount + unmount + mount', function (t) { + fuse.mount(mnt, { force: true }, function (err) { + t.error(err, 'no error') + t.ok(true, 'works') + fuse.unmount(mnt, function () { + fuse.mount(mnt, { force: true }, function (err) { + t.error(err, 'no error') + t.ok(true, 'works') + fuse.unmount(mnt, function () { + t.end() + }) + }) + }) + }) +}) + +tape('mnt point must exist', function (t) { + fuse.mount(mnt + '.does-not-exist', {}, function (err) { + t.ok(err, 'had error') + t.end() + }) +}) + +tape('mnt point must be directory', function (t) { + fuse.mount(__filename, {}, function (err) { + t.ok(err, 'had error') + t.end() + }) +}) diff --git a/test/read.js b/test/read.js new file mode 100644 index 0000000..09c3caf --- /dev/null +++ b/test/read.js @@ -0,0 +1,64 @@ +const tape = require('tape') +const fs = require('fs') +const path = require('path') +const concat = require('concat-stream') + +const Fuse = require('../') +const mnt = require('./fixtures/mnt') +const stat = require('./fixtures/stat') + +tape('read', function (t) { + var ops = { + force: true, + readdir: function (path, cb) { + if (path === '/') return cb(null, ['test']) + return cb(fuse.ENOENT) + }, + getattr: function (path, cb) { + if (path === '/') return cb(null, stat({ mode: 'dir', size: 4096 })) + if (path === '/test') return cb(null, stat({ mode: 'file', size: 11 })) + return cb(fuse.ENOENT) + }, + open: function (path, flags, cb) { + cb(0, 42) + }, + release: function (path, fd, cb) { + t.same(fd, 42, 'fd was passed to release') + cb(0) + }, + read: function (path, fd, buf, len, pos, cb) { + var str = 'hello world'.slice(pos, pos + len) + if (!str) return cb(0) + buf.write(str) + return cb(str.length) + } + } + + const fuse = new Fuse(mnt, ops, { debug: true }) + + fuse.mount(function (err) { + t.error(err, 'no error') + + fs.readFile(path.join(mnt, 'test'), function (err, buf) { + t.error(err, 'no error') + t.same(buf, new Buffer('hello world'), 'read file') + + fs.readFile(path.join(mnt, 'test'), function (err, buf) { + t.error(err, 'no error') + t.same(buf, new Buffer('hello world'), 'read file again') + + fs.createReadStream(path.join(mnt, 'test'), { start: 0, end: 4 }).pipe(concat(function (buf) { + t.same(buf, new Buffer('hello'), 'partial read file') + + fs.createReadStream(path.join(mnt, 'test'), { start: 6, end: 10 }).pipe(concat(function (buf) { + t.same(buf, new Buffer('world'), 'partial read file + start offset') + + fuse.unmount(mnt, function () { + t.end() + }) + })) + })) + }) + }) + }) +}) diff --git a/test/write.js b/test/write.js new file mode 100644 index 0000000..da15402 --- /dev/null +++ b/test/write.js @@ -0,0 +1,54 @@ +var mnt = require('./fixtures/mnt') +var stat = require('./fixtures/stat') +var fuse = require('../') +var tape = require('tape') +var fs = require('fs') +var path = require('path') + +tape('write', function (t) { + var created = false + var data = new Buffer(1024) + var size = 0 + + var ops = { + force: true, + readdir: function (path, cb) { + if (path === '/') return cb(null, created ? ['hello'] : []) + return cb(fuse.ENOENT) + }, + truncate: function (path, size, cb) { + cb(0) + }, + getattr: function (path, cb) { + if (path === '/') return cb(null, stat({ mode: 'dir', size: 4096 })) + if (path === '/hello' && created) return cb(null, stat({ mode: 'file', size: size })) + return cb(fuse.ENOENT) + }, + create: function (path, flags, cb) { + t.ok(!created, 'file not created yet') + created = true + cb(0, 42) + }, + release: function (path, fd, cb) { + cb(0) + }, + write: function (path, fd, buf, len, pos, cb) { + buf.slice(0, len).copy(data, pos) + size = Math.max(pos + len, size) + cb(buf.length) + } + } + + fuse.mount(mnt, ops, function (err) { + t.error(err, 'no error') + + fs.writeFile(path.join(mnt, 'hello'), 'hello world', function (err) { + t.error(err, 'no error') + t.same(data.slice(0, size), new Buffer('hello world'), 'data was written') + + fuse.unmount(mnt, function () { + t.end() + }) + }) + }) +})