Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fs: .writeFile(filehandle, ...) behavior differs from the documented one in all its variants #22554

Closed
vsemozhetbyt opened this issue Aug 27, 2018 · 10 comments
Labels
fs Issues and PRs related to the fs subsystem / file system.

Comments

@vsemozhetbyt
Copy link
Contributor

vsemozhetbyt commented Aug 27, 2018

Currently, the fs.writeFile[Sync]() description states:

Asynchronously writes data to a file, replacing the file if it already exists.

However, this is only true if the first argument is a filename. If it is a file descriptor, the file content is not truncated (as somebody may expect) and a new data is merged from the 0 position into the old data.

  1. Compare fs.readFileSync() behavior:
'use strict';

const fs = require('fs');

const fileName = 'test.txt';
fs.writeFileSync(fileName, '123');
fs.writeFileSync(fileName, '0');
console.log(fs.readFileSync(fileName, 'utf8'));
fs.unlinkSync(fileName);

const fd = fs.openSync(fileName, 'w');
fs.writeFileSync(fd, '123');
fs.writeFileSync(fd, '0');
fs.closeSync(fd);
console.log(fs.readFileSync(fileName, 'utf8'));
fs.unlinkSync(fileName);
0
023
  1. Compare the same fs.writeFile() behavior:
const fs = require('fs');

const fileName = 'test.txt';

fs.writeFile(fileName, '123', () => {
  fs.writeFile(fileName, '0', () => {
    console.log(fs.readFileSync(fileName, 'utf8'));
    fs.unlinkSync(fileName);

    const fd = fs.openSync(fileName, 'w');

    fs.writeFile(fd, '123', () => {
      fs.writeFile(fd, '0', () => {
        fs.closeSync(fd);
        console.log(fs.readFileSync(fileName, 'utf8'));
        fs.unlinkSync(fileName);
      });
    });
  });
});
0
023

If this is intended behavior, should we make the description more accurate?

@vsemozhetbyt vsemozhetbyt added the fs Issues and PRs related to the fs subsystem / file system. label Aug 27, 2018
@Trott
Copy link
Member

Trott commented Aug 28, 2018

@nodejs/fs

@vsemozhetbyt
Copy link
Contributor Author

vsemozhetbyt commented Aug 31, 2018

It seems the fsPromises API is even more erroneous: the data is not merged from the 0 position, it is just appended:

  1. Compare fsPromises.writeFile() behavior:
const fsPromises = require('fs').promises;

const fileName = 'test.txt';

(async function main() {
  try {
    await fsPromises.writeFile(fileName, '123');
    await fsPromises.writeFile(fileName, '0');
    console.log(await fsPromises.readFile(fileName, 'utf8'));
    await fsPromises.unlink(fileName);

    const filehandle = await fsPromises.open(fileName, 'w');
    await fsPromises.writeFile(filehandle, '123');
    await fsPromises.writeFile(filehandle, '0');
    await filehandle.close();
    console.log(await fsPromises.readFile(fileName, 'utf8'));
    await fsPromises.unlink(fileName);
  } catch (err) {
    console.error(err);
  }
})();
0
1230
  1. See the same filehandle.writeFile() behavior:
const fsPromises = require('fs').promises;

const fileName = 'test.txt';

(async function main() {
  try {
    const filehandle = await fsPromises.open(fileName, 'w');
    await filehandle.writeFile('123');
    await filehandle.writeFile('0');
    await filehandle.close();
    console.log(await fsPromises.readFile(fileName, 'utf8'));
    await fsPromises.unlink(fileName);
  } catch (err) {
    console.error(err);
  }
})();
1230

@vsemozhetbyt
Copy link
Contributor Author

cc @jasnell re fsPromises API divergence ^^^.

@vsemozhetbyt
Copy link
Contributor Author

vsemozhetbyt commented Aug 31, 2018

Maybe the easiest solution would be to call .[f]truncate() on a filehandle before writing in all cases? If so, it would be semver major and we better hurry to fix before v11 RC will be cut.

@vsemozhetbyt vsemozhetbyt changed the title doc: possible fs.writeFile[Sync]() description ambiguity fs: .writeFile(filehandle, ...) behavior differs from the documented one in all its variants Aug 31, 2018
@vsemozhetbyt
Copy link
Contributor Author

Adding v11 milestone to be on the safe side. Feel free to remove if we should document these unexpectednesses rather than fix them.

@vsemozhetbyt vsemozhetbyt added this to the 11.0.0 milestone Sep 3, 2018
@vsemozhetbyt
Copy link
Contributor Author

@nodejs/fs if we want to fix this behavior in a semver-major way, we have just a week till v11 semver-major cut-off (October 2nd).

@thefourtheye
Copy link
Contributor

diff --git a/lib/fs.js b/lib/fs.js
index 3302cfe0bf..2fe9ec5bbd 100644
--- a/lib/fs.js
+++ b/lib/fs.js
@@ -1193,7 +1193,17 @@ function writeFile(path, data, options, callback) {
       data : Buffer.from('' + data, options.encoding || 'utf8');
     const position = /a/.test(flag) ? null : 0;

-    writeAll(fd, isUserFd, buffer, 0, buffer.byteLength, position, callback);
+    if (isUserFd && !/a/.test(flag)) {
+      ftruncate(fd, (err) => {
+        if (err) {
+          return callback(err);
+        }
+        writeAll(
+          fd, isUserFd, buffer, 0, buffer.byteLength, position, callback);
+      });
+    } else {
+      writeAll(fd, isUserFd, buffer, 0, buffer.byteLength, position, callback);
+    }
   }
 }

@@ -1203,6 +1213,9 @@ function writeFileSync(path, data, options) {

   const isUserFd = isFd(path); // file descriptor ownership
   const fd = isUserFd ? path : fs.openSync(path, flag, options.mode);
+  if (isUserFd && !/a/.test(flag)) {
+    ftruncateSync(fd);
+  }

   if (!isArrayBufferView(data)) {
     data = Buffer.from('' + data, options.encoding || 'utf8');

This rough attempt seems to fix it and all our current tests pass.

thefourtheye added a commit to thefourtheye/io.js that referenced this issue Sep 30, 2018
Simple Reproduction of the Bug

```js
'use strict';

const assert = require('assert');
const fs = require('fs');

const fileName = 'test.txt';
fs.writeFileSync(fileName, '123');
fs.writeFileSync(fileName, '0');
const result1 = fs.readFileSync(fileName, 'utf8');
fs.unlinkSync(fileName);

const fd = fs.openSync(fileName, 'w');
fs.writeFileSync(fd, '123');
fs.writeFileSync(fd, '0');
fs.closeSync(fd);
const result2 = fs.readFileSync(fileName, 'utf8');
fs.unlinkSync(fileName);

assert.deepStrictEqual(result2, result1);
// + expected - actual
//
//- '023'
//+ '0'

```

Fixes: nodejs#22554
@jasnell
Copy link
Member

jasnell commented Oct 17, 2018

This will need to land by Saturday if it's going to make the 11.0.0 milestone.

@jasnell jasnell removed this from the 11.0.0 milestone Oct 23, 2018
@thefourtheye
Copy link
Contributor

thefourtheye commented Dec 17, 2018

@vsemozhetbyt Can this be closed now, as we concluded #23433 and documentation update is pending in #25080?

@vsemozhetbyt
Copy link
Contributor Author

Thank you for handling this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
fs Issues and PRs related to the fs subsystem / file system.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants