├── .gitignore ├── LICENSE ├── README.md ├── example.js ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | hyperfuse 3 | test 4 | tmp 5 | mnt 6 | trusty 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Mathias Buus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hyperfuse 2 | 3 | [Hyperfused](https://github.com/mafintosh/hyperfused) client for node. 4 | 5 | ``` 6 | npm install hyperfuse 7 | ``` 8 | 9 | More docs will be added when this matures a bit more. 10 | 11 | For now install try installing [hyperfused](https://github.com/mafintosh/hyperfused) on your machine 12 | and run the example. 13 | 14 | That should spawn the fuse daemon and forward the fuse calls over stdin/stdout to the node process. 15 | The node process will mirror `./mnt` to `./tmp` in the current working directory. 16 | 17 | ## License 18 | 19 | MIT 20 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var rfuse = require('./') 3 | var proc = require('child_process') 4 | var join = require('path').join 5 | var resolve = require('path').resolve 6 | 7 | var from = resolve('tmp') 8 | 9 | var stream = rfuse({ 10 | statfs: function (path, cb) { 11 | console.error('statfs', path) 12 | cb(null, { 13 | bsize: 1000000, 14 | frsize: 1000000, 15 | blocks: 1000000, 16 | bfree: 1000000, 17 | bavail: 1000000, 18 | files: 1000000, 19 | ffree: 1000000, 20 | favail: 1000000, 21 | fsid: 1000000, 22 | flag: 1000000, 23 | namemax: 1000000 24 | }) 25 | }, 26 | ftruncate: function (path, fd, size, cb) { 27 | console.error('ftruncate', path, fd, size) 28 | fs.ftruncate(fd, size, cb) 29 | }, 30 | fsync: function (path, fd, datasync, cb) { 31 | console.error('fsync', path, fd, datasync) 32 | fs.fsync(fd, cb) 33 | }, 34 | unlink: function (path, cb) { 35 | console.error('unlink', path) 36 | fs.unlink(join(from, path), cb) 37 | }, 38 | create: function (path, mode, cb) { 39 | console.error('create', path, mode) 40 | fs.open(join(from, path), 'w', cb) 41 | }, 42 | open: function (path, mode, cb) { 43 | console.error('open', path, mode) 44 | fs.open(join(from, path), mode, cb) 45 | }, 46 | truncate: function (path, size, cb) { 47 | console.error('truncate', path, size) 48 | fs.truncate(join(from, path), size, cb) 49 | }, 50 | read: function (path, fd, buffer, len, pos, cb) { 51 | console.error('read', path, fd, len, pos) 52 | fs.read(fd, buffer, 0, len, pos, cb) 53 | }, 54 | write: function (path, fd, buffer, len, pos, cb) { 55 | console.error('write', path, fd, len, pos) 56 | fs.write(fd, buffer, 0, len, pos, cb) 57 | }, 58 | readdir: function (path, cb) { 59 | console.error('readdir', path) 60 | fs.readdir(join(from, path), cb) 61 | }, 62 | fgetattr: function (path, fd, cb) { 63 | console.error('fgetattr', path, fd) 64 | fs.fstat(fd, cb) 65 | }, 66 | getattr: function (path, cb) { 67 | console.error('getattr', path) 68 | fs.lstat(join(from, path), cb) 69 | }, 70 | chmod: function (path, mode, cb) { 71 | console.error('chmod', path, mode) 72 | fs.chmod(join(from, path), mode, cb) 73 | }, 74 | chown: function (path, uid, gid, cb) { 75 | console.error('chown', path, uid, gid) 76 | fs.chown(join(from, path), uid, gid, cb) 77 | }, 78 | release: function (path, fd, cb) { 79 | console.error('release', path, fd) 80 | fs.close(fd, cb) 81 | }, 82 | mkdir: function (path, mode, cb) { 83 | console.error('mkdir', path, mode) 84 | fs.mkdir(join(from, path), mode, cb) 85 | }, 86 | rmdir: function (path, cb) { 87 | console.error('rmdir', path) 88 | fs.rmdir(join(from, path), cb) 89 | }, 90 | utimens: function (path, atime, mtime, cb) { 91 | console.error('utimens', path, atime, mtime) 92 | fs.utimes(join(from, path), atime, mtime, cb) 93 | }, 94 | rename: function (path, dst, cb) { 95 | console.error('rename', path, dst) 96 | fs.rename(join(from, path), join(from, dst), cb) 97 | }, 98 | symlink: function (src, dst, cb) { 99 | console.error('symlink', src, dst) 100 | fs.symlink(src, join(from, dst), cb) 101 | }, 102 | readlink: function (path, cb) { 103 | console.error('readlink', path) 104 | fs.readlink(join(from, path), function (err, link) { 105 | if (err) return cb(err) 106 | if (link === from || link.indexOf(from + '/') === 0) link = link.replace(from, stream.path) 107 | cb(0, link) 108 | }) 109 | }, 110 | link: function (src, dst, cb) { 111 | console.error('link', src, dst) 112 | fs.link(join(from, src), join(from, dst), cb) 113 | }, 114 | setxattr: function (path, name, val, len, pos, flags, cb) { 115 | console.error('setxattr', path, name, val, pos, flags) 116 | cb() 117 | } 118 | }) 119 | 120 | stream.on('mount', function (mnt) { 121 | console.error('fuse mounted on', mnt) 122 | }) 123 | 124 | proc.exec('mkdir -p mnt tmp', function () { 125 | if (process.argv.length > 2) { 126 | require('net').createServer(function (sock) { 127 | sock.pipe(stream).pipe(sock) 128 | }).listen(process.argv[2]) 129 | } else { 130 | var child = proc.spawn('hyperfused', ['mnt', '-']) 131 | child.stdout.pipe(stream).pipe(child.stdin) 132 | child.stderr.pipe(process.stderr) 133 | child.on('exit', function () { 134 | console.error('hyperfused exited') 135 | process.exit() 136 | }) 137 | } 138 | }) 139 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var stream = require('stream') 2 | var duplexify = require('duplexify') 3 | var bitfield = require('bitfield') 4 | var constants = require('constants') 5 | 6 | var HYPERFUSE_O_APPEND = 8 7 | var HYPERFUSE_O_CREAT = 512 8 | var HYPERFUSE_O_EXCL = 2048 9 | var HYPERFUSE_O_RDONLY = 0 10 | var HYPERFUSE_O_RDWR = 2 11 | var HYPERFUSE_O_SYNC = 128 12 | var HYPERFUSE_O_TRUNC = 1024 13 | var HYPERFUSE_O_WRONLY = 1 14 | 15 | var METHODS = [ 16 | 'init', 17 | 'getattr', 18 | 'readdir', 19 | 'read', 20 | 'open', 21 | 'truncate', 22 | 'create', 23 | 'unlink', 24 | 'write', 25 | 'chmod', 26 | 'chown', 27 | 'release', 28 | 'mkdir', 29 | 'rmdir', 30 | 'utimens', 31 | 'rename', 32 | 'symlink', 33 | 'readlink', 34 | 'link', 35 | 'access', 36 | 'statfs', 37 | 'fgetattr', 38 | 'flush', 39 | 'fsync', 40 | 'fsyncdir', 41 | 'ftruncate', 42 | 'mknod', 43 | 'setxattr', 44 | 'getxattr', 45 | 'opendir', 46 | 'releasedir' 47 | ] 48 | 49 | module.exports = hyperfuse 50 | 51 | function hyperfuse (bindings) { 52 | var input = stream.PassThrough() 53 | var output = stream.PassThrough() 54 | var remote = duplexify(input, output) 55 | var methods = bitfield(40) 56 | 57 | METHODS.forEach(function (m, i) { 58 | if (bindings[m]) methods.set(i) 59 | }) 60 | 61 | methods.set(0) // init is always defined 62 | output.write(methods.buffer) 63 | 64 | loop() 65 | 66 | return remote 67 | 68 | function init (mnt, cb) { 69 | remote.path = mnt 70 | remote.emit('mount', mnt) 71 | if (bindings.init) bindings.init(mnt, cb) 72 | else cb() 73 | } 74 | 75 | function onmessage (buf) { 76 | var id = buf.readUInt16BE(0) 77 | var method = buf[2] 78 | 79 | var pathLen = buf.readUInt16BE(3) 80 | var path = buf.toString('utf-8', 5, 5 + pathLen) 81 | var offset = 5 + pathLen + 1 82 | 83 | switch (method) { 84 | case 0: return init(path, writeAck(output, id)) 85 | case 1: return bindings.getattr(path, writeStat(output, id)) 86 | case 2: return bindings.readdir(path, writeDirs(output, id)) 87 | case 3: return onread(buf, path, offset, id) 88 | case 4: return bindings.open(path, buf.readUInt16BE(offset), writeFd(output, id)) 89 | case 5: return bindings.truncate(path, buf.readUInt32BE(offset), writeAck(output, id)) 90 | case 6: return bindings.create(path, buf.readUInt16BE(offset), writeFd(output, id)) 91 | case 7: return bindings.unlink(path, writeAck(output, id)) 92 | case 8: return onwrite(buf, path, offset, id) 93 | case 9: return bindings.chmod(path, buf.readUInt16BE(offset), writeAck(output, id)) 94 | case 10: return bindings.chown(path, buf.readUInt16BE(offset), buf.readUInt16BE(offset + 2), writeAck(output, id)) 95 | case 11: return bindings.release(path, buf.readUInt16BE(offset), writeAck(output, id)) 96 | case 12: return bindings.mkdir(path, buf.readUInt16BE(offset), writeAck(output, id)) 97 | case 13: return bindings.rmdir(path, writeAck(output, id)) 98 | case 14: return bindings.utimens(path, readDate(buf, offset), readDate(buf, offset + 4), writeAck(output, id)) 99 | case 15: return bindings.rename(path, readString(buf, offset), writeAck(output, id)) 100 | case 16: return bindings.symlink(path, readString(buf, offset), writeAck(output, id)) 101 | case 17: return bindings.readlink(path, writeLink(output, id)) 102 | case 18: return bindings.link(path, readString(buf, offset), writeAck(output, id)) 103 | case 19: return bindings.access(path, buf.readUInt16BE(offset), writeFd(output, id)) 104 | case 20: return bindings.statfs(path, writeStatfs(output, id)) 105 | case 21: return bindings.fgetattr(path, buf.readUInt16BE(offset), writeStat(output, id)) 106 | case 22: return bindings.flush(path, buf.readUInt16BE(offset), writeAck(output, id)) 107 | case 23: return bindings.fsync(path, buf.readUInt16BE(offset), buf.readUInt16BE(offset + 2), writeAck(output, id)) 108 | case 24: return bindings.fsyncdir(path, buf.readUInt16BE(offset), buf.readUInt16BE(offset + 2), writeAck(output, id)) 109 | case 25: return bindings.ftruncate(path, buf.readUInt16BE(offset), buf.readUInt32BE(offset + 2), writeAck(output, id)) 110 | case 26: return bindings.mknod(path, buf.readUInt16BE(offset), buf.readUInt32BE(offset + 2), writeAck(output, id)) 111 | case 27: return onsetxattr(buf, path, offset, id) 112 | case 28: return ongetxattr(buf, path, offset, id) 113 | case 29: return bindings.opendir(path, buf.readUInt16BE(offset), writeFd(output, id)) 114 | case 30: return bindings.release(path, buf.readUInt16BE(offset), writeAck(output, id)) 115 | } 116 | } 117 | 118 | function ongetxattr (buf, path, offset, id) { 119 | var name = readString(buf, offset) 120 | offset += readString.bytes 121 | var pos = buf.readUInt32BE(offset) 122 | offset += 4 123 | var data = buf.slice(offset) 124 | bindings.getxattr(path, name, data, data.length, pos, writeAck(output, id)) 125 | } 126 | 127 | function onsetxattr (buf, path, offset, id) { 128 | var name = readString(buf, offset) 129 | offset += readString.bytes 130 | var flags = buf.readUInt16BE(offset) 131 | offset += 2 132 | var pos = buf.readUInt32BE(offset) 133 | offset += 4 134 | var data = buf.slice(offset) 135 | bindings.setxattr(path, name, data, data.length, pos, flags, writeAck(output, id)) 136 | } 137 | 138 | function onread (buf, path, offset, id) { 139 | var fd = buf.readUInt16BE(offset) 140 | var len = buf.readUInt32BE(offset + 2) 141 | var pos = buf.readUInt32BE(offset + 6) 142 | var result = new Buffer(10 + len) // TODO: reuse buffers 143 | bindings.read(path, fd, result.slice(10), len, pos, writeRead(output, id, result)) 144 | } 145 | 146 | function onwrite (buf, path, offset, id) { 147 | var fd = buf.readUInt16BE(offset) 148 | var pos = buf.readUInt32BE(offset + 2) 149 | var writes = buf.slice(offset + 6) 150 | bindings.write(path, fd, writes, writes.length, pos, writeWrite(output, id)) 151 | } 152 | 153 | function loop () { 154 | read(input, 4, function (len) { 155 | read(input, len.readUInt32BE(0), function (buf) { 156 | onmessage(buf) 157 | loop() 158 | }) 159 | }) 160 | } 161 | } 162 | 163 | function unmapFlags (flags) { 164 | var res = 0 165 | if (flags & HYPERFUSE_O_APPEND) res |= constants.O_APPEND 166 | if (flags & HYPERFUSE_O_CREAT) res |= constants.O_CREAT 167 | if (flags & HYPERFUSE_O_EXCL) res |= constants.O_EXCL 168 | if (flags & HYPERFUSE_O_RDONLY) res |= constants.O_RDONLY 169 | if (flags & HYPERFUSE_O_RDWR) res |= constants.O_RDWR 170 | if (flags & HYPERFUSE_O_SYNC) res |= constants.O_SYNC 171 | if (flags & HYPERFUSE_O_TRUNC) res |= constants.O_TRUNC 172 | if (flags & HYPERFUSE_O_WRONLY) res |= constants.O_WRONLY 173 | return res 174 | } 175 | 176 | function readString (buf, offset) { 177 | var strLen = buf.readUInt16BE(offset) 178 | readString.bytes = strLen + 2 + 1 179 | return buf.toString('utf-8', offset + 2, offset + 2 + strLen) 180 | } 181 | 182 | function readDate (buf, offset) { 183 | return new Date(1000 * buf.readUInt32BE(offset)) 184 | } 185 | 186 | function read (sock, num, cb) { 187 | var b = sock.read(num) 188 | if (b) return cb(b) 189 | sock.once('readable', function () { 190 | read(sock, num, cb) 191 | }) 192 | } 193 | 194 | function errno (err) { 195 | if (!err) return 0 196 | if (typeof err === 'number') return err 197 | return err.errno || -1 198 | } 199 | 200 | function alloc (err, id, len) { 201 | var buf = new Buffer((err ? 0 : len) + 10) 202 | buf.writeUInt32BE(buf.length - 4, 0) 203 | buf.writeUInt16BE(id, 4) 204 | buf.writeInt32BE(errno(err), 6) 205 | return buf 206 | } 207 | 208 | function writeStatfs (sock, id) { 209 | return function (err, st) { 210 | var buf = alloc(err, id, 11 * 4) 211 | if (err) return sock.write(buf) 212 | 213 | var offset = 10 214 | buf.writeUInt32BE(st.bsize, offset) 215 | offset += 4 216 | buf.writeUInt32BE(st.frsize, offset) 217 | offset += 4 218 | buf.writeUInt32BE(st.blocks, offset) 219 | offset += 4 220 | buf.writeUInt32BE(st.bfree, offset) 221 | offset += 4 222 | buf.writeUInt32BE(st.bavail, offset) 223 | offset += 4 224 | buf.writeUInt32BE(st.files, offset) 225 | offset += 4 226 | buf.writeUInt32BE(st.ffree, offset) 227 | offset += 4 228 | buf.writeUInt32BE(st.favail, offset) 229 | offset += 4 230 | buf.writeUInt32BE(st.fsid, offset) 231 | offset += 4 232 | buf.writeUInt32BE(st.flag, offset) 233 | offset += 4 234 | buf.writeUInt32BE(st.namemax, offset) 235 | offset += 4 236 | 237 | sock.write(buf) 238 | } 239 | } 240 | 241 | function writeLink (sock, id) { 242 | return function (err, link) { 243 | var len = link ? Buffer.byteLength(link) : 0 244 | var buf = alloc(err, id, 2 + len + 1) 245 | if (err) return sock.write(buf) 246 | buf.writeUInt16BE(len, 10) 247 | buf.write(link, 12, 12 + len) 248 | buf[12 + len] = 0 249 | sock.write(buf) 250 | } 251 | } 252 | 253 | function writeStat (sock, id) { 254 | return function (err, st) { 255 | var buf = alloc(err, id, 13 * 4) 256 | if (err) return sock.write(buf) 257 | 258 | var offset = 10 259 | buf.writeUInt32BE(st.dev, offset) 260 | offset += 4 261 | buf.writeUInt32BE(st.mode, offset) 262 | offset += 4 263 | buf.writeUInt32BE(st.nlink, offset) 264 | offset += 4 265 | buf.writeUInt32BE(st.uid, offset) 266 | offset += 4 267 | buf.writeUInt32BE(st.gid, offset) 268 | offset += 4 269 | buf.writeUInt32BE(st.rdev, offset) 270 | offset += 4 271 | buf.writeUInt32BE(st.blksize, offset) 272 | offset += 4 273 | buf.writeUInt32BE(st.ino, offset) 274 | offset += 4 275 | buf.writeUInt32BE(st.size, offset) 276 | offset += 4 277 | buf.writeUInt32BE(st.blocks, offset) 278 | offset += 4 279 | buf.writeUInt32BE(st.atime.getTime() / 1000, offset) 280 | offset += 4 281 | buf.writeUInt32BE(st.mtime.getTime() / 1000, offset) 282 | offset += 4 283 | buf.writeUInt32BE(st.ctime.getTime() / 1000, offset) 284 | offset += 4 285 | 286 | sock.write(buf) 287 | } 288 | } 289 | 290 | function writeDirs (sock, id) { 291 | return function (err, dirs) { 292 | var len = 0 293 | var i 294 | if (!err) { 295 | for (i = 0; i < dirs.length; i++) len += 3 + Buffer.byteLength(dirs[i]) 296 | } 297 | var buf = alloc(err, id, len) 298 | if (err) return sock.write(buf) 299 | var offset = 10 300 | for (i = 0; i < dirs.length; i++) { 301 | var l = Buffer.byteLength(dirs[i]) 302 | buf.writeUInt16BE(l, offset) 303 | buf.write(dirs[i], offset + 2) 304 | buf[offset + 2 + l] = 0 305 | offset += 3 + l 306 | } 307 | sock.write(buf) 308 | } 309 | } 310 | 311 | function writeRead (sock, id, result) { 312 | return function (err, len) { 313 | var ret = typeof err === 'number' ? err : (errno(err) || len || 0) 314 | var bufLen = (ret > 0 ? ret : 0) + 10 315 | result = result.slice(0, bufLen) 316 | result.writeUInt32BE(result.length - 4, 0) 317 | result.writeUInt16BE(id, 4) 318 | result.writeInt32BE(ret, 6) 319 | sock.write(result) 320 | } 321 | } 322 | 323 | function writeFd (sock, id) { 324 | return function (err, fd) { 325 | var buf = alloc(err, id, 2) 326 | if (err) return sock.write(buf) 327 | buf.writeUInt16BE(fd, 10) 328 | sock.write(buf) 329 | } 330 | } 331 | 332 | function writeWrite (sock, id) { 333 | return function (err, len) { 334 | var ret = typeof err === 'number' ? err : (errno(err) || len || 0) 335 | sock.write(alloc(ret, id, 0)) 336 | } 337 | } 338 | 339 | function writeAck (sock, id) { 340 | return function (err) { 341 | sock.write(alloc(err, id, 0)) 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hyperfuse", 3 | "version": "0.1.0", 4 | "description": "Hyperfuse client for node", 5 | "main": "index.js", 6 | "author": "Mathias Buus (@mafintosh)", 7 | "license": "MIT", 8 | "dependencies": { 9 | "bitfield": "^1.1.2", 10 | "duplexify": "^3.4.2" 11 | } 12 | } 13 | --------------------------------------------------------------------------------