GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: node_dir.cc Lines: 173 197 87.8 %
Date: 2022-12-07 04:23:16 Branches: 70 140 50.0 %

Line Branch Exec Source
1
#include "node_dir.h"
2
#include "node_external_reference.h"
3
#include "node_file-inl.h"
4
#include "node_process-inl.h"
5
#include "memory_tracker-inl.h"
6
#include "util.h"
7
8
#include "tracing/trace_event.h"
9
10
#include "string_bytes.h"
11
12
#include <fcntl.h>
13
#include <sys/types.h>
14
#include <sys/stat.h>
15
#include <cstring>
16
#include <cerrno>
17
#include <climits>
18
19
#include <memory>
20
21
namespace node {
22
23
namespace fs_dir {
24
25
using fs::FSReqAfterScope;
26
using fs::FSReqBase;
27
using fs::FSReqWrapSync;
28
using fs::GetReqWrap;
29
30
using v8::Array;
31
using v8::Context;
32
using v8::FunctionCallbackInfo;
33
using v8::FunctionTemplate;
34
using v8::HandleScope;
35
using v8::Integer;
36
using v8::Isolate;
37
using v8::Local;
38
using v8::MaybeLocal;
39
using v8::Null;
40
using v8::Number;
41
using v8::Object;
42
using v8::ObjectTemplate;
43
using v8::Value;
44
45
2
static const char* get_dir_func_name_by_type(uv_fs_type req_type) {
46

2
  switch (req_type) {
47
#define FS_TYPE_TO_NAME(type, name)                                            \
48
  case UV_FS_##type:                                                           \
49
    return name;
50
2
    FS_TYPE_TO_NAME(OPENDIR, "opendir")
51
    FS_TYPE_TO_NAME(READDIR, "readdir")
52
    FS_TYPE_TO_NAME(CLOSEDIR, "closedir")
53
#undef FS_TYPE_TO_NAME
54
    default:
55
      return "unknow";
56
  }
57
}
58
59
#define TRACE_NAME(name) "fs_dir.sync." #name
60
#define GET_TRACE_ENABLED                                                      \
61
  (*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(                                \
62
       TRACING_CATEGORY_NODE2(fs_dir, sync)) != 0)
63
#define FS_DIR_SYNC_TRACE_BEGIN(syscall, ...)                                  \
64
  if (GET_TRACE_ENABLED)                                                       \
65
    TRACE_EVENT_BEGIN(TRACING_CATEGORY_NODE2(fs_dir, sync),                    \
66
                      TRACE_NAME(syscall),                                     \
67
                      ##__VA_ARGS__);
68
#define FS_DIR_SYNC_TRACE_END(syscall, ...)                                    \
69
  if (GET_TRACE_ENABLED)                                                       \
70
    TRACE_EVENT_END(TRACING_CATEGORY_NODE2(fs_dir, sync),                      \
71
                    TRACE_NAME(syscall),                                       \
72
                    ##__VA_ARGS__);
73
74
#define FS_DIR_ASYNC_TRACE_BEGIN0(fs_type, id)                                 \
75
  TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(TRACING_CATEGORY_NODE2(fs_dir, async),     \
76
                                    get_dir_func_name_by_type(fs_type),        \
77
                                    id);
78
#define FS_DIR_ASYNC_TRACE_END0(fs_type, id)                                   \
79
  TRACE_EVENT_NESTABLE_ASYNC_END0(TRACING_CATEGORY_NODE2(fs_dir, async),       \
80
                                  get_dir_func_name_by_type(fs_type),          \
81
                                  id);
82
83
#define FS_DIR_ASYNC_TRACE_BEGIN1(fs_type, id, name, value)                    \
84
  TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(TRACING_CATEGORY_NODE2(fs_dir, async),     \
85
                                    get_dir_func_name_by_type(fs_type),        \
86
                                    id,                                        \
87
                                    name,                                      \
88
                                    value);
89
90
#define FS_DIR_ASYNC_TRACE_END1(fs_type, id, name, value)                      \
91
  TRACE_EVENT_NESTABLE_ASYNC_END1(TRACING_CATEGORY_NODE2(fs_dir, async),       \
92
                                  get_dir_func_name_by_type(fs_type),          \
93
                                  id,                                          \
94
                                  name,                                        \
95
                                  value);
96
97
206
DirHandle::DirHandle(Environment* env, Local<Object> obj, uv_dir_t* dir)
98
    : AsyncWrap(env, obj, AsyncWrap::PROVIDER_DIRHANDLE),
99
206
      dir_(dir) {
100
206
  MakeWeak();
101
102
206
  dir_->nentries = 0;
103
206
  dir_->dirents = nullptr;
104
206
}
105
106
206
DirHandle* DirHandle::New(Environment* env, uv_dir_t* dir) {
107
  Local<Object> obj;
108
206
  if (!env->dir_instance_template()
109
206
          ->NewInstance(env->context())
110
206
          .ToLocal(&obj)) {
111
    return nullptr;
112
  }
113
114
206
  return new DirHandle(env, obj, dir);
115
}
116
117
void DirHandle::New(const FunctionCallbackInfo<Value>& args) {
118
  CHECK(args.IsConstructCall());
119
}
120
121
824
DirHandle::~DirHandle() {
122
412
  CHECK(!closing_);  // We should not be deleting while explicitly closing!
123
412
  GCClose();         // Close synchronously and emit warning
124
412
  CHECK(closed_);    // We have to be closed at the point
125
824
}
126
127
void DirHandle::MemoryInfo(MemoryTracker* tracker) const {
128
  tracker->TrackFieldWithSize("dir", sizeof(*dir_));
129
}
130
131
// Close the directory handle if it hasn't already been closed. A process
132
// warning will be emitted using a SetImmediate to avoid calling back to
133
// JS during GC. If closing the fd fails at this point, a fatal exception
134
// will crash the process immediately.
135
206
inline void DirHandle::GCClose() {
136
206
  if (closed_) return;
137
  uv_fs_t req;
138

13
  FS_DIR_SYNC_TRACE_BEGIN(closedir);
139
13
  int ret = uv_fs_closedir(nullptr, &req, dir_, nullptr);
140

13
  FS_DIR_SYNC_TRACE_END(closedir);
141
13
  uv_fs_req_cleanup(&req);
142
13
  closing_ = false;
143
13
  closed_ = true;
144
145
  struct err_detail { int ret; };
146
147
13
  err_detail detail { ret };
148
149
13
  if (ret < 0) {
150
    // Do not unref this
151
    env()->SetImmediate([detail](Environment* env) {
152
      const char* msg = "Closing directory handle on garbage collection failed";
153
      // This exception will end up being fatal for the process because
154
      // it is being thrown from within the SetImmediate handler and
155
      // there is no JS stack to bubble it to. In other words, tearing
156
      // down the process is the only reasonable thing we can do here.
157
      HandleScope handle_scope(env->isolate());
158
      env->ThrowUVException(detail.ret, "close", msg);
159
    });
160
    return;
161
  }
162
163
  // If the close was successful, we still want to emit a process warning
164
  // to notify that the file descriptor was gc'd. We want to be noisy about
165
  // this because not explicitly closing the DirHandle is a bug.
166
167
13
  env()->SetImmediate([](Environment* env) {
168
    ProcessEmitWarning(env,
169
1
                       "Closing directory handle on garbage collection");
170
1
  }, CallbackFlags::kUnrefed);
171
}
172
173
108
void AfterClose(uv_fs_t* req) {
174
108
  FSReqBase* req_wrap = FSReqBase::from_req(req);
175
216
  FSReqAfterScope after(req_wrap, req);
176

116
  FS_DIR_ASYNC_TRACE_END1(
177
      req->fs_type, req_wrap, "result", static_cast<int>(req->result))
178
108
  if (after.Proceed())
179
216
    req_wrap->Resolve(Undefined(req_wrap->env()->isolate()));
180
108
}
181
182
193
void DirHandle::Close(const FunctionCallbackInfo<Value>& args) {
183
193
  Environment* env = Environment::GetCurrent(args);
184
185
193
  const int argc = args.Length();
186
193
  CHECK_GE(argc, 1);
187
188
  DirHandle* dir;
189
193
  ASSIGN_OR_RETURN_UNWRAP(&dir, args.Holder());
190
191
193
  dir->closing_ = false;
192
193
  dir->closed_ = true;
193
194
193
  FSReqBase* req_wrap_async = GetReqWrap(args, 0);
195
193
  if (req_wrap_async != nullptr) {  // close(req)
196

116
    FS_DIR_ASYNC_TRACE_BEGIN0(UV_FS_CLOSEDIR, req_wrap_async)
197
108
    AsyncCall(env, req_wrap_async, args, "closedir", UTF8, AfterClose,
198
              uv_fs_closedir, dir->dir());
199
  } else {  // close(undefined, ctx)
200
85
    CHECK_EQ(argc, 2);
201
170
    FSReqWrapSync req_wrap_sync;
202

85
    FS_DIR_SYNC_TRACE_BEGIN(closedir);
203
170
    SyncCall(env, args[1], &req_wrap_sync, "closedir", uv_fs_closedir,
204
             dir->dir());
205

85
    FS_DIR_SYNC_TRACE_END(closedir);
206
  }
207
}
208
209
172
static MaybeLocal<Array> DirentListToArray(
210
    Environment* env,
211
    uv_dirent_t* ents,
212
    int num,
213
    enum encoding encoding,
214
    Local<Value>* err_out) {
215
344
  MaybeStackBuffer<Local<Value>, 64> entries(num * 2);
216
217
  // Return an array of all read filenames.
218
172
  int j = 0;
219
686
  for (int i = 0; i < num; i++) {
220
    Local<Value> filename;
221
    Local<Value> error;
222
514
    const size_t namelen = strlen(ents[i].name);
223
514
    if (!StringBytes::Encode(env->isolate(),
224
514
                             ents[i].name,
225
                             namelen,
226
                             encoding,
227
1028
                             &error).ToLocal(&filename)) {
228
      *err_out = error;
229
      return MaybeLocal<Array>();
230
    }
231
232
514
    entries[j++] = filename;
233
1028
    entries[j++] = Integer::New(env->isolate(), ents[i].type);
234
  }
235
236
344
  return Array::New(env->isolate(), entries.out(), j);
237
}
238
239
185
static void AfterDirRead(uv_fs_t* req) {
240
185
  BaseObjectPtr<FSReqBase> req_wrap { FSReqBase::from_req(req) };
241
185
  FSReqAfterScope after(req_wrap.get(), req);
242

193
  FS_DIR_ASYNC_TRACE_END1(
243
      req->fs_type, req_wrap, "result", static_cast<int>(req->result))
244
185
  if (!after.Proceed()) {
245
    return;
246
  }
247
248
185
  Environment* env = req_wrap->env();
249
185
  Isolate* isolate = env->isolate();
250
251
185
  if (req->result == 0) {
252
    // Done
253
89
    Local<Value> done = Null(isolate);
254
89
    after.Clear();
255
89
    req_wrap->Resolve(done);
256
89
    return;
257
  }
258
259
96
  uv_dir_t* dir = static_cast<uv_dir_t*>(req->ptr);
260
261
  Local<Value> error;
262
  Local<Array> js_array;
263
96
  if (!DirentListToArray(env,
264
                         dir->dirents,
265
96
                         static_cast<int>(req->result),
266
                         req_wrap->encoding(),
267
96
                         &error)
268
96
           .ToLocal(&js_array)) {
269
    // Clear libuv resources *before* delivering results to JS land because
270
    // that can schedule another operation on the same uv_dir_t. Ditto below.
271
    after.Clear();
272
    return req_wrap->Reject(error);
273
  }
274
275
96
  after.Clear();
276
96
  req_wrap->Resolve(js_array);
277
}
278
279
280
334
void DirHandle::Read(const FunctionCallbackInfo<Value>& args) {
281
334
  Environment* env = Environment::GetCurrent(args);
282
334
  Isolate* isolate = env->isolate();
283
284
334
  const int argc = args.Length();
285
334
  CHECK_GE(argc, 3);
286
287
334
  const enum encoding encoding = ParseEncoding(isolate, args[0], UTF8);
288
289
  DirHandle* dir;
290
407
  ASSIGN_OR_RETURN_UNWRAP(&dir, args.Holder());
291
292
334
  CHECK(args[1]->IsNumber());
293
668
  uint64_t buffer_size = static_cast<uint64_t>(args[1].As<Number>()->Value());
294
295
334
  if (buffer_size != dir->dirents_.size()) {
296
190
    dir->dirents_.resize(buffer_size);
297
190
    dir->dir_->nentries = buffer_size;
298
190
    dir->dir_->dirents = dir->dirents_.data();
299
  }
300
301
334
  FSReqBase* req_wrap_async = GetReqWrap(args, 2);
302
334
  if (req_wrap_async != nullptr) {  // dir.read(encoding, bufferSize, req)
303

193
    FS_DIR_ASYNC_TRACE_BEGIN0(UV_FS_READDIR, req_wrap_async)
304
185
    AsyncCall(env, req_wrap_async, args, "readdir", encoding,
305
              AfterDirRead, uv_fs_readdir, dir->dir());
306
  } else {  // dir.read(encoding, bufferSize, undefined, ctx)
307
149
    CHECK_EQ(argc, 4);
308
149
    FSReqWrapSync req_wrap_sync;
309

149
    FS_DIR_SYNC_TRACE_BEGIN(readdir);
310
298
    int err = SyncCall(env, args[3], &req_wrap_sync, "readdir", uv_fs_readdir,
311
                       dir->dir());
312

149
    FS_DIR_SYNC_TRACE_END(readdir);
313
149
    if (err < 0) {
314
      return;  // syscall failed, no need to continue, error info is in ctx
315
    }
316
317
149
    if (req_wrap_sync.req.result == 0) {
318
      // Done
319
73
      Local<Value> done = Null(isolate);
320
73
      args.GetReturnValue().Set(done);
321
73
      return;
322
    }
323
324
76
    CHECK_GE(req_wrap_sync.req.result, 0);
325
326
    Local<Value> error;
327
    Local<Array> js_array;
328
76
    if (!DirentListToArray(env,
329
76
                           dir->dir()->dirents,
330
76
                           static_cast<int>(req_wrap_sync.req.result),
331
                           encoding,
332
76
                           &error)
333
76
             .ToLocal(&js_array)) {
334
      Local<Object> ctx = args[2].As<Object>();
335
      USE(ctx->Set(env->context(), env->error_string(), error));
336
      return;
337
    }
338
339
152
    args.GetReturnValue().Set(js_array);
340
  }
341
}
342
343
112
void AfterOpenDir(uv_fs_t* req) {
344
112
  FSReqBase* req_wrap = FSReqBase::from_req(req);
345
112
  FSReqAfterScope after(req_wrap, req);
346

121
  FS_DIR_ASYNC_TRACE_END1(
347
      req->fs_type, req_wrap, "result", static_cast<int>(req->result))
348
112
  if (!after.Proceed()) {
349
1
    return;
350
  }
351
352
111
  Environment* env = req_wrap->env();
353
354
111
  uv_dir_t* dir = static_cast<uv_dir_t*>(req->ptr);
355
111
  DirHandle* handle = DirHandle::New(env, dir);
356
357
222
  req_wrap->Resolve(handle->object().As<Value>());
358
}
359
360
208
static void OpenDir(const FunctionCallbackInfo<Value>& args) {
361
208
  Environment* env = Environment::GetCurrent(args);
362
208
  Isolate* isolate = env->isolate();
363
364
208
  const int argc = args.Length();
365
208
  CHECK_GE(argc, 3);
366
367
208
  BufferValue path(isolate, args[0]);
368
208
  CHECK_NOT_NULL(*path);
369
370
208
  const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8);
371
372
208
  FSReqBase* req_wrap_async = GetReqWrap(args, 2);
373
208
  if (req_wrap_async != nullptr) {  // openDir(path, encoding, req)
374

121
    FS_DIR_ASYNC_TRACE_BEGIN1(
375
        UV_FS_OPENDIR, req_wrap_async, "path", TRACE_STR_COPY(*path))
376
112
    AsyncCall(env, req_wrap_async, args, "opendir", encoding, AfterOpenDir,
377
              uv_fs_opendir, *path);
378
  } else {  // openDir(path, encoding, undefined, ctx)
379
96
    CHECK_EQ(argc, 4);
380
96
    FSReqWrapSync req_wrap_sync;
381

96
    FS_DIR_SYNC_TRACE_BEGIN(opendir);
382
192
    int result = SyncCall(env, args[3], &req_wrap_sync, "opendir",
383
                          uv_fs_opendir, *path);
384

96
    FS_DIR_SYNC_TRACE_END(opendir);
385
96
    if (result < 0) {
386
1
      return;  // syscall failed, no need to continue, error info is in ctx
387
    }
388
389
95
    uv_fs_t* req = &req_wrap_sync.req;
390
95
    uv_dir_t* dir = static_cast<uv_dir_t*>(req->ptr);
391
95
    DirHandle* handle = DirHandle::New(env, dir);
392
393
190
    args.GetReturnValue().Set(handle->object().As<Value>());
394
  }
395
}
396
397
800
void Initialize(Local<Object> target,
398
                Local<Value> unused,
399
                Local<Context> context,
400
                void* priv) {
401
800
  Environment* env = Environment::GetCurrent(context);
402
800
  Isolate* isolate = env->isolate();
403
404
800
  SetMethod(context, target, "opendir", OpenDir);
405
406
  // Create FunctionTemplate for DirHandle
407
800
  Local<FunctionTemplate> dir = NewFunctionTemplate(isolate, DirHandle::New);
408
800
  dir->Inherit(AsyncWrap::GetConstructorTemplate(env));
409
800
  SetProtoMethod(isolate, dir, "read", DirHandle::Read);
410
800
  SetProtoMethod(isolate, dir, "close", DirHandle::Close);
411
800
  Local<ObjectTemplate> dirt = dir->InstanceTemplate();
412
800
  dirt->SetInternalFieldCount(DirHandle::kInternalFieldCount);
413
800
  SetConstructorFunction(context, target, "DirHandle", dir);
414
800
  env->set_dir_instance_template(dirt);
415
800
}
416
417
5639
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
418
5639
  registry->Register(OpenDir);
419
5639
  registry->Register(DirHandle::New);
420
5639
  registry->Register(DirHandle::Read);
421
5639
  registry->Register(DirHandle::Close);
422
5639
}
423
424
}  // namespace fs_dir
425
426
}  // end namespace node
427
428
5710
NODE_BINDING_CONTEXT_AWARE_INTERNAL(fs_dir, node::fs_dir::Initialize)
429
5639
NODE_BINDING_EXTERNAL_REFERENCE(fs_dir,
430
                                node::fs_dir::RegisterExternalReferences)