From a653f23dfc2ac7c0717ffcb87bb4a7f5981169df Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 13 Mar 2018 20:42:33 +0100 Subject: [PATCH] =?UTF-8?q?fs:=20fix=20`createReadStream(=E2=80=A6,=20{end?= =?UTF-8?q?:=20n})`=20for=20non-seekable=20fds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 82bdf8fba2d3f fixed an issue by silently modifying the `start` option for the case when only `end` is passed, in order to perform reads from a specified range in the file. However, that approach does not work for non-seekable files, since a numeric `start` option means that positioned reads will be used to read data from the file. This patch fixes that, and instead ends reading after a specified size by adjusting the read buffer size. This way we avoid re-introducing the bug that 82bdf8fba2d3f fixed, and align behaviour with the native file stream mechanism introduced in https://github.com/nodejs/node/pull/18936 as well. Backport-PR-URL: https://github.com/nodejs/node/pull/19410 PR-URL: https://github.com/nodejs/node/pull/19329 Fixes: https://github.com/nodejs/node/issues/19240 Refs: https://github.com/nodejs/node/pull/18121 Reviewed-By: James M Snell Reviewed-By: Matteo Collina Reviewed-By: Chen Gang --- lib/fs.js | 11 +++++++++-- test/parallel/test-fs-read-stream.js | 26 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index bfd085b4213881..b5565201e2abdd 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -1950,8 +1950,7 @@ function ReadStream(path, options) { this.flags = options.flags === undefined ? 'r' : options.flags; this.mode = options.mode === undefined ? 0o666 : options.mode; - this.start = typeof this.fd !== 'number' && options.start === undefined ? - 0 : options.start; + this.start = options.start; this.end = options.end; this.autoClose = options.autoClose === undefined ? true : options.autoClose; this.pos = undefined; @@ -1974,6 +1973,12 @@ function ReadStream(path, options) { this.pos = this.start; } + // Backwards compatibility: Make sure `end` is a number regardless of `start`. + // TODO(addaleax): Make the above typecheck not depend on `start` instead. + // (That is a semver-major change). + if (typeof this.end !== 'number') + this.end = Infinity; + if (typeof this.fd !== 'number') this.open(); @@ -2028,6 +2033,8 @@ ReadStream.prototype._read = function(n) { if (this.pos !== undefined) toRead = Math.min(this.end - this.pos + 1, toRead); + else + toRead = Math.min(this.end - this.bytesRead + 1, toRead); // already read everything we were supposed to read! // treat as EOF. diff --git a/test/parallel/test-fs-read-stream.js b/test/parallel/test-fs-read-stream.js index 6748c68b52596c..11d1c2f63ff507 100644 --- a/test/parallel/test-fs-read-stream.js +++ b/test/parallel/test-fs-read-stream.js @@ -22,6 +22,7 @@ 'use strict'; const common = require('../common'); +const child_process = require('child_process'); const assert = require('assert'); const fs = require('fs'); const fixtures = require('../common/fixtures'); @@ -171,6 +172,31 @@ assert.throws(function() { })); } +if (!common.isWindows) { + // Verify that end works when start is not specified, and we do not try to + // use positioned reads. This makes sure that this keeps working for + // non-seekable file descriptors. + common.refreshTmpDir(); + const filename = `${common.tmpDir}/foo.pipe`; + const mkfifoResult = child_process.spawnSync('mkfifo', [filename]); + if (!mkfifoResult.error) { + child_process.exec(`echo "xyz foobar" > '${filename}'`); + const stream = new fs.createReadStream(filename, { end: 1 }); + stream.data = ''; + + stream.on('data', function(chunk) { + stream.data += chunk; + }); + + stream.on('end', common.mustCall(function() { + assert.strictEqual('xy', stream.data); + fs.unlinkSync(filename); + })); + } else { + common.printSkipMessage('mkfifo not available'); + } +} + { // pause and then resume immediately. const pauseRes = fs.createReadStream(rangeFile);