• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
get_dir_func_name_by_type(uv_fs_type req_type)45 static 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 
DirHandle(Environment * env,Local<Object> obj,uv_dir_t * dir)97 DirHandle::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 
New(Environment * env,uv_dir_t * dir)106 DirHandle* 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 
New(const FunctionCallbackInfo<Value> & args)117 void DirHandle::New(const FunctionCallbackInfo<Value>& args) {
118   CHECK(args.IsConstructCall());
119 }
120 
~DirHandle()121 DirHandle::~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 
MemoryInfo(MemoryTracker * tracker) const127 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.
GCClose()135 inline 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 
AfterClose(uv_fs_t * req)173 void 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 
Close(const FunctionCallbackInfo<Value> & args)182 void 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 
DirentListToArray(Environment * env,uv_dirent_t * ents,int num,enum encoding encoding,Local<Value> * err_out)209 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   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 
AfterDirRead(uv_fs_t * req)239 static 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 
Read(const FunctionCallbackInfo<Value> & args)280 void 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 
AfterOpenDir(uv_fs_t * req)343 void 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 
OpenDir(const FunctionCallbackInfo<Value> & args)360 static 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 
Initialize(Local<Object> target,Local<Value> unused,Local<Context> context,void * priv)397 void 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 
RegisterExternalReferences(ExternalReferenceRegistry * registry)417 void 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 
428 NODE_BINDING_CONTEXT_AWARE_INTERNAL(fs_dir, node::fs_dir::Initialize)
429 NODE_BINDING_EXTERNAL_REFERENCE(fs_dir,
430                                 node::fs_dir::RegisterExternalReferences)
431