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

[WIP] fs: add FD object to manage file descriptors #18109

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions doc/api/fs.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,44 @@ explicitly synchronous use libuv's threadpool, which can have surprising and
negative performance implications for some applications, see the
[`UV_THREADPOOL_SIZE`][] documentation for more information.

## class: fs.FileHandle
<!-- YAML
added: REPLACEME
-->

A `fs.FileHandle` object is a wrapper for a numeric file descriptor. Instances
of `fs.FileHandle` are distinct from numeric file descriptors in that, if the
`fs.FileHandle` is not explicitly closed using the `fd.close()` method, they
will automatically close the file descriptor and will emit a process warning,
thereby helping to prevent memory leaks.

Instances of the `fs.FileHandle` object are created internally by the
`fs.openFileHandle()` and `fs.openFileHandleSync()` methods.

### filehandle.close()
<!-- YAML
added: REPLACEME
-->

* Returns: {Promise} A `Promise` that will be resolved once the underlying
file descriptor is closed, or will reject if an error occurs while closing.

Closes the file descriptor.

```js
const filehandle = fs.openFileHandleSync('thefile.txt', 'r');
filehandle.close()
.then(() => console.log('ok'))
.catch(() => console.log('not ok'));
```

### filehandle.fd
<!-- YAML
added: REPLACEME
-->

Value: {number} The numeric file descriptor managed by the `FileHandle` object.

## Class: fs.FSWatcher
<!-- YAML
added: v0.5.8
Expand Down Expand Up @@ -2069,6 +2107,118 @@ through `fs.open()` or `fs.writeFile()`) will fail with `EPERM`. Existing hidden
files can be opened for writing with the `r+` flag. A call to `fs.ftruncate()`
can be used to reset the file contents.

## fs.openFileHandle(path, flags[, mode], callback)
<!-- YAML
added: REPLACEME
-->

* `path` {string|Buffer|URL}
* `flags` {string|number}
* `mode` {integer} **Default:** `0o666`
* `callback` {Function}
* `err` {Error}
* `fd` {[fs.FileHandle][#fs_class_fs_filehandle]}

Asynchronous file open that returns a `fs.FileHandle` object. See open(2).

The `flags` argument can be:

* `'r'` - Open file for reading.
An exception occurs if the file does not exist.

* `'r+'` - Open file for reading and writing.
An exception occurs if the file does not exist.

* `'rs+'` - Open file for reading and writing in synchronous mode. Instructs
the operating system to bypass the local file system cache.

This is primarily useful for opening files on NFS mounts as it allows skipping
the potentially stale local cache. It has a very real impact on I/O
performance so using this flag is not recommended unless it is needed.

Note that this doesn't turn `fs.openFileHAndle()` into a synchronous blocking
call. If synchronous operation is desired `fs.openFileHandleSync()` should be
used.

* `'w'` - Open file for writing.
The file is created (if it does not exist) or truncated (if it exists).

* `'wx'` - Like `'w'` but fails if `path` exists.

* `'w+'` - Open file for reading and writing.
The file is created (if it does not exist) or truncated (if it exists).

* `'wx+'` - Like `'w+'` but fails if `path` exists.

* `'a'` - Open file for appending.
The file is created if it does not exist.

* `'ax'` - Like `'a'` but fails if `path` exists.

* `'a+'` - Open file for reading and appending.
The file is created if it does not exist.

* `'ax+'` - Like `'a+'` but fails if `path` exists.

`mode` sets the file mode (permission and sticky bits), but only if the file was
created. It defaults to `0o666` (readable and writable).

The callback gets two arguments `(err, filehandle)`.

The exclusive flag `'x'` (`O_EXCL` flag in open(2)) ensures that `path` is newly
created. On POSIX systems, `path` is considered to exist even if it is a symlink
to a non-existent file. The exclusive flag may or may not work with network file
systems.

`flags` can also be a number as documented by open(2); commonly used constants
are available from `fs.constants`. On Windows, flags are translated to
their equivalent ones where applicable, e.g. `O_WRONLY` to `FILE_GENERIC_WRITE`,
or `O_EXCL|O_CREAT` to `CREATE_NEW`, as accepted by CreateFileW.

On Linux, positional writes don't work when the file is opened in append mode.
The kernel ignores the position argument and always appends the data to
the end of the file.

*Note*: The behavior of `fs.openFileHandle()` is platform-specific for some
flags. As such, opening a directory on macOS and Linux with the `'a+'` flag -
see example below - will return an error. In contrast, on Windows and FreeBSD,
a file descriptor will be returned.

```js
// macOS and Linux
fs.openFileHandle('<directory>', 'a+', (err, filehandle) => {
// => [Error: EISDIR: illegal operation on a directory, open <directory>]
});

// Windows and FreeBSD
fs.openFileHandle('<directory>', 'a+', (err, filehandle) => {
// => null, <filehandle>
});
```

Some characters (`< > : " / \ | ? *`) are reserved under Windows as documented
by [Naming Files, Paths, and Namespaces][]. Under NTFS, if the filename contains
a colon, Node.js will open a file system stream, as described by
[this MSDN page][MSDN-Using-Streams].

*Note:* On Windows, opening an existing hidden file using the `w` flag (e.g.
using `fs.openFileHandle()`) will fail with `EPERM`. Existing hidden
files can be opened for writing with the `r+` flag. A call to `fs.ftruncate()`
can be used to reset the file contents.

## fs.openFileHandleSync(path, flags[, mode])
<!-- YAML
added: REPLACEME
-->

* `path` {string|Buffer|URL}
* `flags` {string|number}
* `mode` {integer} **Default:** `0o666`
* Returns: {[fs.FileHandle][#fs_class_fs_filehandle]}

Synchronous version of [`fs.openFileHandle()`][]. Returns a `fs.FileHandle`
object representing the file descriptor.

## fs.openSync(path, flags[, mode])
<!-- YAML
added: v0.1.21
Expand Down
30 changes: 30 additions & 0 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,25 @@ fs.open = function(path, flags, mode, callback_) {
req);
};

fs.openFileHandle = function(path, flags, mode, callback_) {
const callback = makeCallback(arguments[arguments.length - 1]);
mode = modeNum(mode, 0o666);

if (handleError((path = getPathFromURL(path)), callback))
return;
if (!nullCheck(path, callback)) return;
validatePath(path);
validateUint32(mode, 'mode');

const req = new FSReqWrap();
req.oncomplete = callback;

binding.openFileHandle(pathModule.toNamespacedPath(path),
stringToFlags(flags),
mode,
req);
};

fs.openSync = function(path, flags, mode) {
mode = modeNum(mode, 0o666);
handleError((path = getPathFromURL(path)));
Expand All @@ -779,6 +798,17 @@ fs.openSync = function(path, flags, mode) {
stringToFlags(flags), mode);
};

fs.openFileHandleSync = function(path, flags, mode) {
mode = modeNum(mode, 0o666);
handleError((path = getPathFromURL(path)));
nullCheck(path);
validatePath(path);
validateUint32(mode, 'mode');

return binding.openFileHandle(pathModule.toNamespacedPath(path),
stringToFlags(flags), mode);
};

fs.read = function(fd, buffer, offset, length, position, callback) {
validateUint32(fd, 'fd');
validateBuffer(buffer);
Expand Down
2 changes: 2 additions & 0 deletions src/async_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ namespace node {
#define NODE_ASYNC_NON_CRYPTO_PROVIDER_TYPES(V) \
V(NONE) \
V(DNSCHANNEL) \
V(FILEHANDLE) \
V(FILEHANDLECLOSEREQ) \
V(FSEVENTWRAP) \
V(FSREQWRAP) \
V(GETADDRINFOREQWRAP) \
Expand Down
4 changes: 4 additions & 0 deletions src/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,11 @@ void Environment::RunAndClearNativeImmediates() {
std::vector<NativeImmediateCallback> list;
native_immediate_callbacks_.swap(list);
for (const auto& cb : list) {
v8::TryCatch try_catch(isolate());
cb.cb_(this, cb.data_);
if (try_catch.HasCaught()) {
FatalException(isolate(), try_catch);
}
if (cb.keep_alive_)
cb.keep_alive_->Reset();
if (cb.refed_)
Expand Down
2 changes: 2 additions & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,8 @@ class ModuleWrap;
V(internal_binding_cache_object, v8::Object) \
V(buffer_prototype_object, v8::Object) \
V(context, v8::Context) \
V(fd_constructor_template, v8::ObjectTemplate) \
V(fdclose_constructor_template, v8::ObjectTemplate) \
V(host_import_module_dynamically_callback, v8::Function) \
V(http2ping_constructor_template, v8::ObjectTemplate) \
V(http2stream_constructor_template, v8::ObjectTemplate) \
Expand Down
Loading