fs: unset FileHandle fd after close

- Do not set the fd as a property on the native object.
- Use the already-existent `GetFD()` method to pass the
  fd from C++ to JS.
- Cache the fd in JS to avoid repeated accesses to the
  C++ getter.
- Set the fd to `-1` after close, thus reliably making
  subsequent calls using the `FileHandle` return `EBADF`.

Fixes: https://github.com/nodejs/node/issues/31361

PR-URL: https://github.com/nodejs/node/pull/31389
Reviewed-By: Richard Lau <riclau@uk.ibm.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Minwoo Jung <nodecorelab@gmail.com>
Reviewed-By: David Carlier <devnexen@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
This commit is contained in:
Anna Henningsen 2020-01-16 22:12:07 +01:00
parent cc0748f509
commit 7864c53629
No known key found for this signature in database
GPG Key ID: 9C63F3A6CD2AD8F9
4 changed files with 33 additions and 15 deletions

View File

@ -53,6 +53,7 @@ const pathModule = require('path');
const { promisify } = require('internal/util');
const kHandle = Symbol('kHandle');
const kFd = Symbol('kFd');
const { kUsePromises } = binding;
const getDirectoryEntriesPromise = promisify(getDirents);
@ -60,6 +61,7 @@ const getDirectoryEntriesPromise = promisify(getDirents);
class FileHandle {
constructor(filehandle) {
this[kHandle] = filehandle;
this[kFd] = filehandle.fd;
}
getAsyncId() {
@ -67,7 +69,7 @@ class FileHandle {
}
get fd() {
return this[kHandle].fd;
return this[kFd];
}
appendFile(data, options) {
@ -123,6 +125,7 @@ class FileHandle {
}
close = () => {
this[kFd] = -1;
return this[kHandle].close();
}
}

View File

@ -53,7 +53,6 @@ namespace fs {
using v8::Array;
using v8::Context;
using v8::DontDelete;
using v8::EscapableHandleScope;
using v8::Function;
using v8::FunctionCallbackInfo;
@ -68,8 +67,6 @@ using v8::Number;
using v8::Object;
using v8::ObjectTemplate;
using v8::Promise;
using v8::PropertyAttribute;
using v8::ReadOnly;
using v8::String;
using v8::Symbol;
using v8::Uint32;
@ -138,15 +135,6 @@ FileHandle* FileHandle::New(Environment* env, int fd, Local<Object> obj) {
.ToLocal(&obj)) {
return nullptr;
}
PropertyAttribute attr =
static_cast<PropertyAttribute>(ReadOnly | DontDelete);
if (obj->DefineOwnProperty(env->context(),
env->fd_string(),
Integer::New(env->isolate(), fd),
attr)
.IsNothing()) {
return nullptr;
}
return new FileHandle(env, obj, fd);
}
@ -190,12 +178,13 @@ inline void FileHandle::Close() {
uv_fs_t req;
int ret = uv_fs_close(env()->event_loop(), &req, fd_, nullptr);
uv_fs_req_cleanup(&req);
AfterClose();
struct err_detail { int ret; int fd; };
err_detail detail { ret, fd_ };
AfterClose();
if (ret < 0) {
// Do not unref this
env()->SetImmediate([detail](Environment* env) {
@ -340,6 +329,7 @@ void FileHandle::ReleaseFD(const FunctionCallbackInfo<Value>& args) {
void FileHandle::AfterClose() {
closing_ = false;
closed_ = true;
fd_ = -1;
if (reading_ && !persistent().IsEmpty())
EmitRead(UV_EOF);
}

View File

@ -205,7 +205,7 @@ class FileHandle final : public AsyncWrap, public StreamBase {
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
int fd() const { return fd_; }
int GetFD() override { return fd_; }
// Will asynchronously close the FD and return a Promise that will
// be resolved once closing is complete.

View File

@ -0,0 +1,25 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs').promises;
(async () => {
const filehandle = await fs.open(__filename);
assert.notStrictEqual(filehandle.fd, -1);
await filehandle.close();
assert.strictEqual(filehandle.fd, -1);
// Open another file handle first. This would typically receive the fd
// that `filehandle` previously used. In earlier versions of Node.js, the
// .stat() call would then succeed because it still used the original fd;
// See https://github.com/nodejs/node/issues/31361 for more details.
const otherFilehandle = await fs.open(process.execPath);
assert.rejects(() => filehandle.stat(), {
code: 'EBADF',
syscall: 'fstat'
});
await otherFilehandle.close();
})().then(common.mustCall());