diff --git a/lib/binding.js b/lib/binding.js index c75cc95..341bd54 100644 --- a/lib/binding.js +++ b/lib/binding.js @@ -15,7 +15,7 @@ function getCallback(req) { } function addNullTerminationIfNeeded(s) { - // If the s is a Buffer, we need to add a NULL termination byte. + // If s is a Buffer, we need to add a NULL termination byte. if (Buffer.isBuffer(s)) { const nullTerminated = Buffer.allocUnsafe(s.length + 1); s.copy(nullTerminated); @@ -193,8 +193,21 @@ exports.writeBuffer = function(fd, buffer, offset, length, position, req) { } } -exports.writeBuffers = function() { - throw new Error('Unimplemented'); +exports.writeBuffers = function(fd, buffers, position, req) { + const callback = getCallback(req); + const iovecs = bindings.buffersToIoVecs(buffers); + if (typeof position === 'number' && position !== -1) { + return syscall.pwritev( + fd, + iovecs, + buffers.length, + position & 0xffffffff, // 32 lower bits + Math.floor(position / Math.pow(2, 32)), // high bits + callback + ); + } else { + return syscall.writev(fd, iovecs, buffers.length, callback); + } } exports.writeString = function() { diff --git a/lib/disk.js b/lib/disk.js index bddad0b..89b6a19 100644 --- a/lib/disk.js +++ b/lib/disk.js @@ -4,6 +4,10 @@ const filedisk = require('resin-file-disk'); const constants = require('./constants'); +function sum(arr) { + return arr.reduce(function(a, b) {return a + b}); +} + class FileDisk extends filedisk.FileDisk { constructor(path) { super( @@ -20,8 +24,34 @@ class FileDisk extends filedisk.FileDisk { }; request(type, offset, iovecs, callback) { - // FIXME: we have to fill / persist all the iovecs, not just the first - super.request(type, offset, iovecs[0].length, iovecs[0], callback); + if (iovecs.length > 1) { + // special case for read / write requests with more than one iovec + const length = sum(iovecs.map(function(b) {return b.length})); + if (type === constants.LKL_DEV_BLK_TYPE_READ) { + const buf = Buffer.allocUnsafe(length); + const cb = function(err, bytesRead, buf) { + if (err) { + callback(err); + return; + } + let off = 0; + // copy from result buffer to iovecs: + for (let vec of iovecs) { + buf.copy(vec, 0, off, off + vec.length); + off += vec.length; + } + callback(null); + }; + super.request(type, offset, length, buf, cb); + } else if (type === constants.LKL_DEV_BLK_TYPE_WRITE) { + // transform all iovecs to a single buffer: + const length = sum(iovecs.map(function(b) {return b.length})); + const buf = Buffer.concat(iovecs, length); + super.request(type, offset, length, buf, callback); + } + } else { + super.request(type, offset, iovecs[0].length, iovecs[0], callback); + } }; } diff --git a/lib/syscall.js b/lib/syscall.js index 72f9f07..b6ac194 100644 --- a/lib/syscall.js +++ b/lib/syscall.js @@ -47,6 +47,7 @@ module.exports = { openat: syscall.bind(null, constants.SYS_openat), pread64: syscall.bind(null, constants.SYS_pread64), pwrite64: syscall.bind(null, constants.SYS_pwrite64), + pwritev: syscall.bind(null, constants.SYS_pwritev), read: syscall.bind(null, constants.SYS_read), readlinkat: syscall.bind(null, constants.SYS_readlinkat), removexattr: syscall.bind(null, constants.SYS_removexattr), @@ -56,7 +57,8 @@ module.exports = { sync: syscall.bind(null, constants.SYS_sync), truncate: syscall.bind(null, constants.SYS_truncate), unlinkat: syscall.bind(null, constants.SYS_unlinkat), - write: syscall.bind(null, constants.SYS_write) + write: syscall.bind(null, constants.SYS_write), + writev: syscall.bind(null, constants.SYS_writev) }; // Unimplemented syscalls diff --git a/src/bindings.cc b/src/bindings.cc index cef1a58..510439d 100644 --- a/src/bindings.cc +++ b/src/bindings.cc @@ -13,6 +13,7 @@ NAN_MODULE_INIT(InitAll) { NAN_EXPORT(target, sizeOfStructLklStat); NAN_EXPORT(target, parseLklStat); NAN_EXPORT(target, millisecondsToTimespec); + NAN_EXPORT(target, buffersToIoVecs); } NODE_MODULE(bindings, InitAll) diff --git a/src/node_lkl.cc b/src/node_lkl.cc index ae72a64..3f4b507 100644 --- a/src/node_lkl.cc +++ b/src/node_lkl.cc @@ -208,3 +208,19 @@ NAN_METHOD(millisecondsToTimespec) { ).ToLocalChecked(); info.GetReturnValue().Set(result); } + +NAN_METHOD(buffersToIoVecs) { + auto arr = info[0].As(); + auto len = arr->Length(); + auto *iovecs = new struct iovec[len]; + for (unsigned int i = 0; i < len; i++) { + auto buf = arr->Get(i); + iovecs[i].iov_base = reinterpret_cast(node::Buffer::Data(buf)); + iovecs[i].iov_len = node::Buffer::Length(buf); + } + auto result = Nan::NewBuffer( + reinterpret_cast(iovecs), + sizeof *iovecs + ).ToLocalChecked(); + info.GetReturnValue().Set(result); +} diff --git a/src/node_lkl.h b/src/node_lkl.h index 83c9c98..6e95499 100644 --- a/src/node_lkl.h +++ b/src/node_lkl.h @@ -7,3 +7,4 @@ NAN_METHOD(parseDirent64); NAN_METHOD(sizeOfStructLklStat); NAN_METHOD(parseLklStat); NAN_METHOD(millisecondsToTimespec); +NAN_METHOD(buffersToIoVecs); diff --git a/test/index.js b/test/index.js index 6498a69..751b5f6 100644 --- a/test/index.js +++ b/test/index.js @@ -432,5 +432,31 @@ describe('node-lkl', function() { .then(done); }); }); + + describe('writev', function() { + it('should write all buffers to file', function(done) { + const fpath = path.join(this.mountpoint, 'filename'); + const stream = lkl.fs.WriteStream(fpath); + const chunks = [ + {chunk: Buffer.from('1')}, + {chunk: Buffer.from('2')}, + {chunk: Buffer.from('3')}, + ] + // testing writev syscall + stream._writev(chunks, function(err) { + // testing pwritev syscall + stream.pos = 1; + stream._writev(chunks, function(err) { + stream.close(function(err){ + lkl.fs.readFileAsync(fpath, 'utf8') + .then(function(content) { + assert.strictEqual(content, '1123', 'should read what it has written'); + done(); + }) + }) + }); + }); + }); + }); }); });