GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: node_dir.cc Lines: 162 182 89.0 %
Date: 2022-08-17 04:19:55 Branches: 47 100 47.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
#define TRACE_NAME(name) "fs_dir.sync." #name
46
#define GET_TRACE_ENABLED                                                      \
47
  (*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED                                 \
48
  (TRACING_CATEGORY_NODE2(fs_dir, sync)) != 0)
49
#define FS_DIR_SYNC_TRACE_BEGIN(syscall, ...)                                  \
50
  if (GET_TRACE_ENABLED)                                                       \
51
  TRACE_EVENT_BEGIN(TRACING_CATEGORY_NODE2(fs_dir, sync), TRACE_NAME(syscall), \
52
  ##__VA_ARGS__);
53
#define FS_DIR_SYNC_TRACE_END(syscall, ...)                                    \
54
  if (GET_TRACE_ENABLED)                                                       \
55
  TRACE_EVENT_END(TRACING_CATEGORY_NODE2(fs_dir, sync), TRACE_NAME(syscall),   \
56
  ##__VA_ARGS__);
57
58
178
DirHandle::DirHandle(Environment* env, Local<Object> obj, uv_dir_t* dir)
59
    : AsyncWrap(env, obj, AsyncWrap::PROVIDER_DIRHANDLE),
60
178
      dir_(dir) {
61
178
  MakeWeak();
62
63
178
  dir_->nentries = 0;
64
178
  dir_->dirents = nullptr;
65
178
}
66
67
178
DirHandle* DirHandle::New(Environment* env, uv_dir_t* dir) {
68
  Local<Object> obj;
69
178
  if (!env->dir_instance_template()
70
178
          ->NewInstance(env->context())
71
178
          .ToLocal(&obj)) {
72
    return nullptr;
73
  }
74
75
178
  return new DirHandle(env, obj, dir);
76
}
77
78
void DirHandle::New(const FunctionCallbackInfo<Value>& args) {
79
  CHECK(args.IsConstructCall());
80
}
81
82
712
DirHandle::~DirHandle() {
83
356
  CHECK(!closing_);  // We should not be deleting while explicitly closing!
84
356
  GCClose();         // Close synchronously and emit warning
85
356
  CHECK(closed_);    // We have to be closed at the point
86
712
}
87
88
void DirHandle::MemoryInfo(MemoryTracker* tracker) const {
89
  tracker->TrackFieldWithSize("dir", sizeof(*dir_));
90
}
91
92
// Close the directory handle if it hasn't already been closed. A process
93
// warning will be emitted using a SetImmediate to avoid calling back to
94
// JS during GC. If closing the fd fails at this point, a fatal exception
95
// will crash the process immediately.
96
178
inline void DirHandle::GCClose() {
97
178
  if (closed_) return;
98
  uv_fs_t req;
99
12
  int ret = uv_fs_closedir(nullptr, &req, dir_, nullptr);
100
12
  uv_fs_req_cleanup(&req);
101
12
  closing_ = false;
102
12
  closed_ = true;
103
104
  struct err_detail { int ret; };
105
106
12
  err_detail detail { ret };
107
108
12
  if (ret < 0) {
109
    // Do not unref this
110
    env()->SetImmediate([detail](Environment* env) {
111
      const char* msg = "Closing directory handle on garbage collection failed";
112
      // This exception will end up being fatal for the process because
113
      // it is being thrown from within the SetImmediate handler and
114
      // there is no JS stack to bubble it to. In other words, tearing
115
      // down the process is the only reasonable thing we can do here.
116
      HandleScope handle_scope(env->isolate());
117
      env->ThrowUVException(detail.ret, "close", msg);
118
    });
119
    return;
120
  }
121
122
  // If the close was successful, we still want to emit a process warning
123
  // to notify that the file descriptor was gc'd. We want to be noisy about
124
  // this because not explicitly closing the DirHandle is a bug.
125
126
12
  env()->SetImmediate([](Environment* env) {
127
    ProcessEmitWarning(env,
128
2
                       "Closing directory handle on garbage collection");
129
2
  }, CallbackFlags::kUnrefed);
130
}
131
132
82
void AfterClose(uv_fs_t* req) {
133
82
  FSReqBase* req_wrap = FSReqBase::from_req(req);
134
164
  FSReqAfterScope after(req_wrap, req);
135
136
82
  if (after.Proceed())
137
164
    req_wrap->Resolve(Undefined(req_wrap->env()->isolate()));
138
82
}
139
140
166
void DirHandle::Close(const FunctionCallbackInfo<Value>& args) {
141
166
  Environment* env = Environment::GetCurrent(args);
142
143
166
  const int argc = args.Length();
144
166
  CHECK_GE(argc, 1);
145
146
  DirHandle* dir;
147
166
  ASSIGN_OR_RETURN_UNWRAP(&dir, args.Holder());
148
149
166
  dir->closing_ = false;
150
166
  dir->closed_ = true;
151
152
166
  FSReqBase* req_wrap_async = GetReqWrap(args, 0);
153
166
  if (req_wrap_async != nullptr) {  // close(req)
154
82
    AsyncCall(env, req_wrap_async, args, "closedir", UTF8, AfterClose,
155
              uv_fs_closedir, dir->dir());
156
  } else {  // close(undefined, ctx)
157
84
    CHECK_EQ(argc, 2);
158
168
    FSReqWrapSync req_wrap_sync;
159

84
    FS_DIR_SYNC_TRACE_BEGIN(closedir);
160
168
    SyncCall(env, args[1], &req_wrap_sync, "closedir", uv_fs_closedir,
161
             dir->dir());
162

84
    FS_DIR_SYNC_TRACE_END(closedir);
163
  }
164
}
165
166
155
static MaybeLocal<Array> DirentListToArray(
167
    Environment* env,
168
    uv_dirent_t* ents,
169
    int num,
170
    enum encoding encoding,
171
    Local<Value>* err_out) {
172
310
  MaybeStackBuffer<Local<Value>, 64> entries(num * 2);
173
174
  // Return an array of all read filenames.
175
155
  int j = 0;
176
646
  for (int i = 0; i < num; i++) {
177
    Local<Value> filename;
178
    Local<Value> error;
179
491
    const size_t namelen = strlen(ents[i].name);
180
491
    if (!StringBytes::Encode(env->isolate(),
181
491
                             ents[i].name,
182
                             namelen,
183
                             encoding,
184
982
                             &error).ToLocal(&filename)) {
185
      *err_out = error;
186
      return MaybeLocal<Array>();
187
    }
188
189
491
    entries[j++] = filename;
190
982
    entries[j++] = Integer::New(env->isolate(), ents[i].type);
191
  }
192
193
310
  return Array::New(env->isolate(), entries.out(), j);
194
}
195
196
144
static void AfterDirRead(uv_fs_t* req) {
197
144
  BaseObjectPtr<FSReqBase> req_wrap { FSReqBase::from_req(req) };
198
144
  FSReqAfterScope after(req_wrap.get(), req);
199
200
144
  if (!after.Proceed()) {
201
    return;
202
  }
203
204
144
  Environment* env = req_wrap->env();
205
144
  Isolate* isolate = env->isolate();
206
207
144
  if (req->result == 0) {
208
    // Done
209
64
    Local<Value> done = Null(isolate);
210
64
    after.Clear();
211
64
    req_wrap->Resolve(done);
212
64
    return;
213
  }
214
215
80
  uv_dir_t* dir = static_cast<uv_dir_t*>(req->ptr);
216
217
  Local<Value> error;
218
  Local<Array> js_array;
219
80
  if (!DirentListToArray(env,
220
                         dir->dirents,
221
80
                         static_cast<int>(req->result),
222
                         req_wrap->encoding(),
223
80
                         &error)
224
80
           .ToLocal(&js_array)) {
225
    // Clear libuv resources *before* delivering results to JS land because
226
    // that can schedule another operation on the same uv_dir_t. Ditto below.
227
    after.Clear();
228
    return req_wrap->Reject(error);
229
  }
230
231
80
  after.Clear();
232
80
  req_wrap->Resolve(js_array);
233
}
234
235
236
291
void DirHandle::Read(const FunctionCallbackInfo<Value>& args) {
237
291
  Environment* env = Environment::GetCurrent(args);
238
291
  Isolate* isolate = env->isolate();
239
240
291
  const int argc = args.Length();
241
291
  CHECK_GE(argc, 3);
242
243
291
  const enum encoding encoding = ParseEncoding(isolate, args[0], UTF8);
244
245
  DirHandle* dir;
246
363
  ASSIGN_OR_RETURN_UNWRAP(&dir, args.Holder());
247
248
291
  CHECK(args[1]->IsNumber());
249
582
  uint64_t buffer_size = static_cast<uint64_t>(args[1].As<Number>()->Value());
250
251
291
  if (buffer_size != dir->dirents_.size()) {
252
163
    dir->dirents_.resize(buffer_size);
253
163
    dir->dir_->nentries = buffer_size;
254
163
    dir->dir_->dirents = dir->dirents_.data();
255
  }
256
257
291
  FSReqBase* req_wrap_async = GetReqWrap(args, 2);
258
291
  if (req_wrap_async != nullptr) {  // dir.read(encoding, bufferSize, req)
259
144
    AsyncCall(env, req_wrap_async, args, "readdir", encoding,
260
              AfterDirRead, uv_fs_readdir, dir->dir());
261
  } else {  // dir.read(encoding, bufferSize, undefined, ctx)
262
147
    CHECK_EQ(argc, 4);
263
147
    FSReqWrapSync req_wrap_sync;
264

147
    FS_DIR_SYNC_TRACE_BEGIN(readdir);
265
294
    int err = SyncCall(env, args[3], &req_wrap_sync, "readdir", uv_fs_readdir,
266
                       dir->dir());
267

147
    FS_DIR_SYNC_TRACE_END(readdir);
268
147
    if (err < 0) {
269
      return;  // syscall failed, no need to continue, error info is in ctx
270
    }
271
272
147
    if (req_wrap_sync.req.result == 0) {
273
      // Done
274
72
      Local<Value> done = Null(isolate);
275
72
      args.GetReturnValue().Set(done);
276
72
      return;
277
    }
278
279
75
    CHECK_GE(req_wrap_sync.req.result, 0);
280
281
    Local<Value> error;
282
    Local<Array> js_array;
283
75
    if (!DirentListToArray(env,
284
75
                           dir->dir()->dirents,
285
75
                           static_cast<int>(req_wrap_sync.req.result),
286
                           encoding,
287
75
                           &error)
288
75
             .ToLocal(&js_array)) {
289
      Local<Object> ctx = args[2].As<Object>();
290
      USE(ctx->Set(env->context(), env->error_string(), error));
291
      return;
292
    }
293
294
150
    args.GetReturnValue().Set(js_array);
295
  }
296
}
297
298
85
void AfterOpenDir(uv_fs_t* req) {
299
85
  FSReqBase* req_wrap = FSReqBase::from_req(req);
300
85
  FSReqAfterScope after(req_wrap, req);
301
302
85
  if (!after.Proceed()) {
303
1
    return;
304
  }
305
306
84
  Environment* env = req_wrap->env();
307
308
84
  uv_dir_t* dir = static_cast<uv_dir_t*>(req->ptr);
309
84
  DirHandle* handle = DirHandle::New(env, dir);
310
311
168
  req_wrap->Resolve(handle->object().As<Value>());
312
}
313
314
180
static void OpenDir(const FunctionCallbackInfo<Value>& args) {
315
180
  Environment* env = Environment::GetCurrent(args);
316
180
  Isolate* isolate = env->isolate();
317
318
180
  const int argc = args.Length();
319
180
  CHECK_GE(argc, 3);
320
321
180
  BufferValue path(isolate, args[0]);
322
180
  CHECK_NOT_NULL(*path);
323
324
180
  const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8);
325
326
180
  FSReqBase* req_wrap_async = GetReqWrap(args, 2);
327
180
  if (req_wrap_async != nullptr) {  // openDir(path, encoding, req)
328
85
    AsyncCall(env, req_wrap_async, args, "opendir", encoding, AfterOpenDir,
329
              uv_fs_opendir, *path);
330
  } else {  // openDir(path, encoding, undefined, ctx)
331
95
    CHECK_EQ(argc, 4);
332
95
    FSReqWrapSync req_wrap_sync;
333

95
    FS_DIR_SYNC_TRACE_BEGIN(opendir);
334
190
    int result = SyncCall(env, args[3], &req_wrap_sync, "opendir",
335
                          uv_fs_opendir, *path);
336

95
    FS_DIR_SYNC_TRACE_END(opendir);
337
95
    if (result < 0) {
338
1
      return;  // syscall failed, no need to continue, error info is in ctx
339
    }
340
341
94
    uv_fs_t* req = &req_wrap_sync.req;
342
94
    uv_dir_t* dir = static_cast<uv_dir_t*>(req->ptr);
343
94
    DirHandle* handle = DirHandle::New(env, dir);
344
345
188
    args.GetReturnValue().Set(handle->object().As<Value>());
346
  }
347
}
348
349
772
void Initialize(Local<Object> target,
350
                Local<Value> unused,
351
                Local<Context> context,
352
                void* priv) {
353
772
  Environment* env = Environment::GetCurrent(context);
354
772
  Isolate* isolate = env->isolate();
355
356
772
  SetMethod(context, target, "opendir", OpenDir);
357
358
  // Create FunctionTemplate for DirHandle
359
772
  Local<FunctionTemplate> dir = NewFunctionTemplate(isolate, DirHandle::New);
360
772
  dir->Inherit(AsyncWrap::GetConstructorTemplate(env));
361
772
  SetProtoMethod(isolate, dir, "read", DirHandle::Read);
362
772
  SetProtoMethod(isolate, dir, "close", DirHandle::Close);
363
772
  Local<ObjectTemplate> dirt = dir->InstanceTemplate();
364
772
  dirt->SetInternalFieldCount(DirHandle::kInternalFieldCount);
365
772
  SetConstructorFunction(context, target, "DirHandle", dir);
366
772
  env->set_dir_instance_template(dirt);
367
772
}
368
369
5345
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
370
5345
  registry->Register(OpenDir);
371
5345
  registry->Register(DirHandle::New);
372
5345
  registry->Register(DirHandle::Read);
373
5345
  registry->Register(DirHandle::Close);
374
5345
}
375
376
}  // namespace fs_dir
377
378
}  // end namespace node
379
380
5417
NODE_MODULE_CONTEXT_AWARE_INTERNAL(fs_dir, node::fs_dir::Initialize)
381
5345
NODE_MODULE_EXTERNAL_REFERENCE(fs_dir, node::fs_dir::RegisterExternalReferences)