xref: /third_party/node/src/node_dir.cc (revision 1cb0ef41)
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
21namespace node {
22
23namespace fs_dir {
24
25using fs::FSReqAfterScope;
26using fs::FSReqBase;
27using fs::FSReqWrapSync;
28using fs::GetReqWrap;
29
30using v8::Array;
31using v8::Context;
32using v8::FunctionCallbackInfo;
33using v8::FunctionTemplate;
34using v8::HandleScope;
35using v8::Integer;
36using v8::Isolate;
37using v8::Local;
38using v8::MaybeLocal;
39using v8::Null;
40using v8::Number;
41using v8::Object;
42using v8::ObjectTemplate;
43using v8::Value;
44
45static const char* get_dir_func_name_by_type(uv_fs_type req_type) {
46  switch (req_type) {
47#define FS_TYPE_TO_NAME(type, name)                                            \
48  case UV_FS_##type:                                                           \
49    return name;
50    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
97DirHandle::DirHandle(Environment* env, Local<Object> obj, uv_dir_t* dir)
98    : AsyncWrap(env, obj, AsyncWrap::PROVIDER_DIRHANDLE),
99      dir_(dir) {
100  MakeWeak();
101
102  dir_->nentries = 0;
103  dir_->dirents = nullptr;
104}
105
106DirHandle* DirHandle::New(Environment* env, uv_dir_t* dir) {
107  Local<Object> obj;
108  if (!env->dir_instance_template()
109          ->NewInstance(env->context())
110          .ToLocal(&obj)) {
111    return nullptr;
112  }
113
114  return new DirHandle(env, obj, dir);
115}
116
117void DirHandle::New(const FunctionCallbackInfo<Value>& args) {
118  CHECK(args.IsConstructCall());
119}
120
121DirHandle::~DirHandle() {
122  CHECK(!closing_);  // We should not be deleting while explicitly closing!
123  GCClose();         // Close synchronously and emit warning
124  CHECK(closed_);    // We have to be closed at the point
125}
126
127void 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.
135inline void DirHandle::GCClose() {
136  if (closed_) return;
137  uv_fs_t req;
138  FS_DIR_SYNC_TRACE_BEGIN(closedir);
139  int ret = uv_fs_closedir(nullptr, &req, dir_, nullptr);
140  FS_DIR_SYNC_TRACE_END(closedir);
141  uv_fs_req_cleanup(&req);
142  closing_ = false;
143  closed_ = true;
144
145  struct err_detail { int ret; };
146
147  err_detail detail { ret };
148
149  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  env()->SetImmediate([](Environment* env) {
168    ProcessEmitWarning(env,
169                       "Closing directory handle on garbage collection");
170  }, CallbackFlags::kUnrefed);
171}
172
173void AfterClose(uv_fs_t* req) {
174  FSReqBase* req_wrap = FSReqBase::from_req(req);
175  FSReqAfterScope after(req_wrap, req);
176  FS_DIR_ASYNC_TRACE_END1(
177      req->fs_type, req_wrap, "result", static_cast<int>(req->result))
178  if (after.Proceed())
179    req_wrap->Resolve(Undefined(req_wrap->env()->isolate()));
180}
181
182void DirHandle::Close(const FunctionCallbackInfo<Value>& args) {
183  Environment* env = Environment::GetCurrent(args);
184
185  const int argc = args.Length();
186  CHECK_GE(argc, 1);
187
188  DirHandle* dir;
189  ASSIGN_OR_RETURN_UNWRAP(&dir, args.Holder());
190
191  dir->closing_ = false;
192  dir->closed_ = true;
193
194  FSReqBase* req_wrap_async = GetReqWrap(args, 0);
195  if (req_wrap_async != nullptr) {  // close(req)
196    FS_DIR_ASYNC_TRACE_BEGIN0(UV_FS_CLOSEDIR, req_wrap_async)
197    AsyncCall(env, req_wrap_async, args, "closedir", UTF8, AfterClose,
198              uv_fs_closedir, dir->dir());
199  } else {  // close(undefined, ctx)
200    CHECK_EQ(argc, 2);
201    FSReqWrapSync req_wrap_sync;
202    FS_DIR_SYNC_TRACE_BEGIN(closedir);
203    SyncCall(env, args[1], &req_wrap_sync, "closedir", uv_fs_closedir,
204             dir->dir());
205    FS_DIR_SYNC_TRACE_END(closedir);
206  }
207}
208
209static MaybeLocal<Array> DirentListToArray(
210    Environment* env,
211    uv_dirent_t* ents,
212    int num,
213    enum encoding encoding,
214    Local<Value>* err_out) {
215  MaybeStackBuffer<Local<Value>, 64> entries(num * 2);
216
217  // Return an array of all read filenames.
218  int j = 0;
219  for (int i = 0; i < num; i++) {
220    Local<Value> filename;
221    Local<Value> error;
222    const size_t namelen = strlen(ents[i].name);
223    if (!StringBytes::Encode(env->isolate(),
224                             ents[i].name,
225                             namelen,
226                             encoding,
227                             &error).ToLocal(&filename)) {
228      *err_out = error;
229      return MaybeLocal<Array>();
230    }
231
232    entries[j++] = filename;
233    entries[j++] = Integer::New(env->isolate(), ents[i].type);
234  }
235
236  return Array::New(env->isolate(), entries.out(), j);
237}
238
239static void AfterDirRead(uv_fs_t* req) {
240  BaseObjectPtr<FSReqBase> req_wrap { FSReqBase::from_req(req) };
241  FSReqAfterScope after(req_wrap.get(), req);
242  FS_DIR_ASYNC_TRACE_END1(
243      req->fs_type, req_wrap, "result", static_cast<int>(req->result))
244  if (!after.Proceed()) {
245    return;
246  }
247
248  Environment* env = req_wrap->env();
249  Isolate* isolate = env->isolate();
250
251  if (req->result == 0) {
252    // Done
253    Local<Value> done = Null(isolate);
254    after.Clear();
255    req_wrap->Resolve(done);
256    return;
257  }
258
259  uv_dir_t* dir = static_cast<uv_dir_t*>(req->ptr);
260
261  Local<Value> error;
262  Local<Array> js_array;
263  if (!DirentListToArray(env,
264                         dir->dirents,
265                         static_cast<int>(req->result),
266                         req_wrap->encoding(),
267                         &error)
268           .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  after.Clear();
276  req_wrap->Resolve(js_array);
277}
278
279
280void DirHandle::Read(const FunctionCallbackInfo<Value>& args) {
281  Environment* env = Environment::GetCurrent(args);
282  Isolate* isolate = env->isolate();
283
284  const int argc = args.Length();
285  CHECK_GE(argc, 3);
286
287  const enum encoding encoding = ParseEncoding(isolate, args[0], UTF8);
288
289  DirHandle* dir;
290  ASSIGN_OR_RETURN_UNWRAP(&dir, args.Holder());
291
292  CHECK(args[1]->IsNumber());
293  uint64_t buffer_size = static_cast<uint64_t>(args[1].As<Number>()->Value());
294
295  if (buffer_size != dir->dirents_.size()) {
296    dir->dirents_.resize(buffer_size);
297    dir->dir_->nentries = buffer_size;
298    dir->dir_->dirents = dir->dirents_.data();
299  }
300
301  FSReqBase* req_wrap_async = GetReqWrap(args, 2);
302  if (req_wrap_async != nullptr) {  // dir.read(encoding, bufferSize, req)
303    FS_DIR_ASYNC_TRACE_BEGIN0(UV_FS_READDIR, req_wrap_async)
304    AsyncCall(env, req_wrap_async, args, "readdir", encoding,
305              AfterDirRead, uv_fs_readdir, dir->dir());
306  } else {  // dir.read(encoding, bufferSize, undefined, ctx)
307    CHECK_EQ(argc, 4);
308    FSReqWrapSync req_wrap_sync;
309    FS_DIR_SYNC_TRACE_BEGIN(readdir);
310    int err = SyncCall(env, args[3], &req_wrap_sync, "readdir", uv_fs_readdir,
311                       dir->dir());
312    FS_DIR_SYNC_TRACE_END(readdir);
313    if (err < 0) {
314      return;  // syscall failed, no need to continue, error info is in ctx
315    }
316
317    if (req_wrap_sync.req.result == 0) {
318      // Done
319      Local<Value> done = Null(isolate);
320      args.GetReturnValue().Set(done);
321      return;
322    }
323
324    CHECK_GE(req_wrap_sync.req.result, 0);
325
326    Local<Value> error;
327    Local<Array> js_array;
328    if (!DirentListToArray(env,
329                           dir->dir()->dirents,
330                           static_cast<int>(req_wrap_sync.req.result),
331                           encoding,
332                           &error)
333             .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    args.GetReturnValue().Set(js_array);
340  }
341}
342
343void AfterOpenDir(uv_fs_t* req) {
344  FSReqBase* req_wrap = FSReqBase::from_req(req);
345  FSReqAfterScope after(req_wrap, req);
346  FS_DIR_ASYNC_TRACE_END1(
347      req->fs_type, req_wrap, "result", static_cast<int>(req->result))
348  if (!after.Proceed()) {
349    return;
350  }
351
352  Environment* env = req_wrap->env();
353
354  uv_dir_t* dir = static_cast<uv_dir_t*>(req->ptr);
355  DirHandle* handle = DirHandle::New(env, dir);
356
357  req_wrap->Resolve(handle->object().As<Value>());
358}
359
360static void OpenDir(const FunctionCallbackInfo<Value>& args) {
361  Environment* env = Environment::GetCurrent(args);
362  Isolate* isolate = env->isolate();
363
364  const int argc = args.Length();
365  CHECK_GE(argc, 3);
366
367  BufferValue path(isolate, args[0]);
368  CHECK_NOT_NULL(*path);
369
370  const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8);
371
372  FSReqBase* req_wrap_async = GetReqWrap(args, 2);
373  if (req_wrap_async != nullptr) {  // openDir(path, encoding, req)
374    FS_DIR_ASYNC_TRACE_BEGIN1(
375        UV_FS_OPENDIR, req_wrap_async, "path", TRACE_STR_COPY(*path))
376    AsyncCall(env, req_wrap_async, args, "opendir", encoding, AfterOpenDir,
377              uv_fs_opendir, *path);
378  } else {  // openDir(path, encoding, undefined, ctx)
379    CHECK_EQ(argc, 4);
380    FSReqWrapSync req_wrap_sync;
381    FS_DIR_SYNC_TRACE_BEGIN(opendir);
382    int result = SyncCall(env, args[3], &req_wrap_sync, "opendir",
383                          uv_fs_opendir, *path);
384    FS_DIR_SYNC_TRACE_END(opendir);
385    if (result < 0) {
386      return;  // syscall failed, no need to continue, error info is in ctx
387    }
388
389    uv_fs_t* req = &req_wrap_sync.req;
390    uv_dir_t* dir = static_cast<uv_dir_t*>(req->ptr);
391    DirHandle* handle = DirHandle::New(env, dir);
392
393    args.GetReturnValue().Set(handle->object().As<Value>());
394  }
395}
396
397void Initialize(Local<Object> target,
398                Local<Value> unused,
399                Local<Context> context,
400                void* priv) {
401  Environment* env = Environment::GetCurrent(context);
402  Isolate* isolate = env->isolate();
403
404  SetMethod(context, target, "opendir", OpenDir);
405
406  // Create FunctionTemplate for DirHandle
407  Local<FunctionTemplate> dir = NewFunctionTemplate(isolate, DirHandle::New);
408  dir->Inherit(AsyncWrap::GetConstructorTemplate(env));
409  SetProtoMethod(isolate, dir, "read", DirHandle::Read);
410  SetProtoMethod(isolate, dir, "close", DirHandle::Close);
411  Local<ObjectTemplate> dirt = dir->InstanceTemplate();
412  dirt->SetInternalFieldCount(DirHandle::kInternalFieldCount);
413  SetConstructorFunction(context, target, "DirHandle", dir);
414  env->set_dir_instance_template(dirt);
415}
416
417void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
418  registry->Register(OpenDir);
419  registry->Register(DirHandle::New);
420  registry->Register(DirHandle::Read);
421  registry->Register(DirHandle::Close);
422}
423
424}  // namespace fs_dir
425
426}  // end namespace node
427
428NODE_BINDING_CONTEXT_AWARE_INTERNAL(fs_dir, node::fs_dir::Initialize)
429NODE_BINDING_EXTERNAL_REFERENCE(fs_dir,
430                                node::fs_dir::RegisterExternalReferences)
431