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: track file offset in writeFile of fs/promises #23136

Closed

Conversation

thefourtheye
Copy link
Contributor

@thefourtheye thefourtheye commented Sep 27, 2018

If the file offset is not tracked with position in the write calls
all the data will be always written at the end of the file.

This is problematic when we pass file descriptors to writeFile because
the the file offset will not be updated. So, if some string is written
to the FD with writeFile and if some other string is written to the
same FD with writeFile without closing and reopening the file, the
contents will be appended instead of writing from the beginning.


Simple Reproduction of the Bug

'use strict';
const assert = require('assert');
const fs = require('fs').promises;

(async function() {
    const h = await fs.open('/tmp/test', 'w');
    await h.writeFile('AAAA');
    await h.truncate();
    await h.writeFile('BBB');
    await h.close();
    assert.deepStrictEqual(await fs.readFile('/tmp/test', 'utf8'), 'BBB');
})();
(node:51030) UnhandledPromiseRejectionWarning: AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected

+ '\u0000\u0000\u0000\u0000BBB'
- 'BBB'
Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines

If the file offset is not tracked with `position` in the `write` calls
all the data will be always written at the end of the file.

This is problematic when we pass file descriptors to `writeFile` because
the the file offset will not be updated. So, if some string is written
to the FD with `writeFile` and if some other string is written to the
same FD with `writeFile` without closing and reopening the file, the
contents will be appended instead of writing from the beginning.
@nodejs-github-bot nodejs-github-bot added the fs Issues and PRs related to the fs subsystem / file system. label Sep 27, 2018
@thefourtheye
Copy link
Contributor Author

cc @nodejs/fs


@vsemozhetbyt If this change gets in, I'll propose a fix for #22554

@thefourtheye
Copy link
Contributor Author

@vsemozhetbyt vsemozhetbyt added the promises Issues and PRs related to ECMAScript promises. label Sep 28, 2018
@addaleax
Copy link
Member

This might just be me and my unix background, but the current behaviour seems right to me (both here and in #23181)…

@thefourtheye
Copy link
Contributor Author

thefourtheye commented Sep 30, 2018

@addaleax Either way, one of the examples is not correct, right? Quoting the example from #22554

'use strict';

const fs = require('fs');

const fileName = 'test.txt';
fs.writeFileSync(fileName, '123');
fs.writeFileSync(fileName, '0');
console.log(fs.readFileSync(fileName, 'utf8'));
// 0
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'));
// 023
fs.unlinkSync(fileName);

Another possibility is that we can update the documentation about the file descriptor case.

Personally, I have always expected writeFile* functions to overwrite the file.

@addaleax
Copy link
Member

Personally, I have always expected writeFile* functions to overwrite the file.

@thefourtheye A file descriptor is more than a reference to a file, it’s a reference to a file and a position in that file (if the file supports positioning)… but yeah, let’s see what other people think.

@thefourtheye
Copy link
Contributor Author

@nodejs/fs PTAL.

@vsemozhetbyt vsemozhetbyt added this to the 11.0.0 milestone Oct 9, 2018
@thefourtheye
Copy link
Contributor Author

Bump @nodejs/fs

do {
const { bytesWritten } =
await write(filehandle, buffer, 0,
Math.min(16384, buffer.length));
Math.min(16384, buffer.length), position);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Positional writes don't work on streams, try running this variant of writeFile to stdout.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, if we cannot seek the position in fd, it will fail. Wouldn't that be the expected behaviour in this case?

@sam-github
Copy link
Contributor

This issue was beaten to death in the past. Seeking to position 0 on a fd provided to writeFile may seem like the obviously right thing to do... but doesn't work on pipes.

Pleas read #10853 (comment) for some history, and the ensuing conversation, its about readFile but the issues are the same.

Summary: I believe current behaviour is intended, and if you could get agreement on the behaviour around unseekable fds, changing it would be semver-major.

@thefourtheye
Copy link
Contributor Author

Today, we define fs.writeFile as

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

So, if the fd represents a file then it has to contain only the data given as one of the parameters to fs.writeFile.

If we cannot seek to a particular position in the file with a file descriptor, then an error would be the expected behaviour I believe. Maybe we can explicitly mention in the docs.

Other option would be to explicitly introduce a check in these functions to make sure that the fd actually corresponds to a file.

I understand that all these are breaking changes.

@thefourtheye
Copy link
Contributor Author

@sam-github This PR is not semver-major as the fs promises APIs are still experimental, I believe. The actual PR to fix the behaviour of writeFile is in #23181 and that is tagged as semver-major.

@sam-github
Copy link
Contributor

@thefourtheye Did you read the history in #10853? Some people expected seeking, some people did not. One node test at that time, perhaps accidently, required readFile to work on a stdin. It would be sensible if the equivalent test existed for writeFile, but it possibly does not.

Quoting the docs is not enormously compelling, they were not sufficiently updated when the fd capability was shoe-horned into the existing API.

I don't have a strong opinion on seek or not anymore, both alternatives have valid use-cases, but I do have one strong opinion: if writeFile seeks, so should readFile. Please do not make them diverge. Note: I don't know if this PR does or not, I haven't gone to check the current state of readFile.

@thefourtheye
Copy link
Contributor Author

@sam-github Thanks, I read the history from #10853 and the linked issues. We'll make sure that the behaviour is consistent with both readFile and writeFile.

I don't know if this PR does or not, I haven't gone to check the current state of readFile.

Neither of these PRs change the readFile. Perhaps, lets come to a conclusion and implement it, if needed.

@jasnell
Copy link
Member

jasnell commented Oct 17, 2018

This will need to land by Saturday in order to make it into 11.0.0.
Note that this is blocking a semver-major from landing that is also on the 11.0.0 milestone.

@jasnell jasnell removed this from the 11.0.0 milestone Oct 23, 2018
@Trott
Copy link
Member

Trott commented Nov 20, 2018

@thefourtheye Is the hope to still land this? Or has this been superseded by other things?

@thefourtheye
Copy link
Contributor Author

As per the decision in #23433, the current behaviour is expected. So closing this.

@thefourtheye thefourtheye deleted the fix-fs-promises-writefile branch December 17, 2018 05:51
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. promises Issues and PRs related to ECMAScript promises.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants