src: report libuv error when openAsBlob cannot stat

`FdEntry::Create()` returned nullptr for any failed `uv_fs_stat()`,
discarding the status, and `BlobFromFilePath()` turned that into
`ERR_INVALID_ARG_VALUE: Unable to open file as blob`. A missing file is
not a malformed argument, and the resulting `TypeError` carried no
`errno`, `syscall`, or `path`, so ENOENT could not be told apart from
any other reason the path was unusable.

Thread the libuv status out of `CreateFdEntry()` and throw a
`UVException` instead, so `fs.openAsBlob()` reports the same error
`fs.stat()` does for the same path.

Fixes: https://github.com/nodejs/node/issues/65514
Signed-off-by: Paul Bouchon <mail@bitpshr.net>
PR-URL: https://github.com/nodejs/node/pull/65517
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
This commit is contained in:
Paul Bouchon authored and GitHub committed 2026-08-28 13:31:48 +00:00
1 parent e5a1ce2125
commit 05447419fb
4 files changed
+33 -7

No files matched your search

+11 -4
View File
@@ -868,13 +868,19 @@ class FdEntry final : public EntryImpl {
// the race
// condition described in the comment above.
public:
static std::unique_ptr<FdEntry> Create(Environment* env, Local<Value> path) {
static std::unique_ptr<FdEntry> Create(Environment* env,
Local<Value> path,
int* status) {
// We're only going to create the FdEntry if the file exists.
uv_fs_t req = uv_fs_t();
auto cleanup = OnScopeLeave([&] { uv_fs_req_cleanup(&req); });
auto buf = std::make_shared<BufferValue>(env->isolate(), path);
if (uv_fs_stat(nullptr, &req, buf->out(), nullptr) < 0) return nullptr;
int err = uv_fs_stat(nullptr, &req, buf->out(), nullptr);
if (err < 0) {
if (status != nullptr) *status = err;
return nullptr;
}
return std::make_unique<FdEntry>(
env, std::move(buf), req.statbuf, 0, req.statbuf.st_size);
@@ -1183,8 +1189,9 @@ std::unique_ptr<DataQueue::Entry> DataQueue::CreateDataQueueEntry(
}
std::unique_ptr<DataQueue::Entry> DataQueue::CreateFdEntry(Environment* env,
Local<Value> path) {
return FdEntry::Create(env, path);
Local<Value> path,
int* status) {
return FdEntry::Create(env, path, status);
}
void DataQueue::Initialize(Environment* env, v8::Local<v8::Object> target) {
+4 -1
View File
@@ -226,8 +226,11 @@ class DataQueue : public MemoryRetainer {
static std::unique_ptr<Entry> CreateDataQueueEntry(
std::shared_ptr<DataQueue> data_queue);
// Returns nullptr if the file cannot be stat'd. When `status` is given, it
// is set to the libuv error code so callers can report why.
static std::unique_ptr<Entry> CreateFdEntry(Environment* env,
v8::Local<v8::Value> path);
v8::Local<v8::Value> path,
int* status = nullptr);
// Creates a Reader for the given queue. If the queue is idempotent,
// any number of readers can be created, all of which are guaranteed
+5 -2
View File
@@ -110,9 +110,12 @@ void BlobFromFilePath(const FunctionCallbackInfo<Value>& args) {
ToNamespacedPath(env, &path);
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());
auto entry = DataQueue::CreateFdEntry(env, args[0]);
int status = 0;
auto entry = DataQueue::CreateFdEntry(env, args[0], &status);
if (entry == nullptr) {
return THROW_ERR_INVALID_ARG_VALUE(env, "Unable to open file as blob");
// The file could not be stat'd. Report the libuv error so callers can tell
// ENOENT apart from any other reason the path could not be used.
return env->ThrowUVException(status, "stat", nullptr, *path);
}
std::vector<std::unique_ptr<DataQueue::Entry>> entries;
+13
View File
@@ -129,6 +129,19 @@ writeFileSync(testfile5, '');
await unlink(testfile5);
})().then(common.mustCall());
(async () => {
// A path that cannot be stat'd reports the underlying libuv error rather
// than a generic argument error, so ENOENT can be told apart from any other
// reason the file could not be used.
// Refs: https://github.com/nodejs/node/issues/65514
const missing = tmpdir.resolve('does-not-exist.txt');
await assert.rejects(async () => openAsBlob(missing), {
code: 'ENOENT',
syscall: 'stat',
path: missing,
});
})().then(common.mustCall());
(async () => {
// We currently do not allow File-backed blobs to be cloned or transferred
// across worker threads. This is largely because the underlying FdEntry