• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #include "node_dir.h"
2 #include "node_file-inl.h"
3 #include "node_process.h"
4 #include "memory_tracker-inl.h"
5 #include "util.h"
6 
7 #include "tracing/trace_event.h"
8 
9 #include "string_bytes.h"
10 
11 #include <fcntl.h>
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <cstring>
15 #include <cerrno>
16 #include <climits>
17 
18 #include <memory>
19 
20 namespace node {
21 
22 namespace fs_dir {
23 
24 using fs::FSReqAfterScope;
25 using fs::FSReqBase;
26 using fs::FSReqWrapSync;
27 using fs::GetReqWrap;
28 
29 using v8::Array;
30 using v8::Context;
31 using v8::FunctionCallbackInfo;
32 using v8::FunctionTemplate;
33 using v8::HandleScope;
34 using v8::Integer;
35 using v8::Isolate;
36 using v8::Local;
37 using v8::MaybeLocal;
38 using v8::Null;
39 using v8::Number;
40 using v8::Object;
41 using v8::ObjectTemplate;
42 using v8::String;
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 
DirHandle(Environment * env,Local<Object> obj,uv_dir_t * dir)58 DirHandle::DirHandle(Environment* env, Local<Object> obj, uv_dir_t* dir)
59     : AsyncWrap(env, obj, AsyncWrap::PROVIDER_DIRHANDLE),
60       dir_(dir) {
61   MakeWeak();
62 
63   dir_->nentries = 0;
64   dir_->dirents = nullptr;
65 }
66 
New(Environment * env,uv_dir_t * dir)67 DirHandle* DirHandle::New(Environment* env, uv_dir_t* dir) {
68   Local<Object> obj;
69   if (!env->dir_instance_template()
70           ->NewInstance(env->context())
71           .ToLocal(&obj)) {
72     return nullptr;
73   }
74 
75   return new DirHandle(env, obj, dir);
76 }
77 
New(const FunctionCallbackInfo<Value> & args)78 void DirHandle::New(const FunctionCallbackInfo<Value>& args) {
79   CHECK(args.IsConstructCall());
80 }
81 
~DirHandle()82 DirHandle::~DirHandle() {
83   CHECK(!closing_);  // We should not be deleting while explicitly closing!
84   GCClose();         // Close synchronously and emit warning
85   CHECK(closed_);    // We have to be closed at the point
86 }
87 
MemoryInfo(MemoryTracker * tracker) const88 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.
GCClose()96 inline void DirHandle::GCClose() {
97   if (closed_) return;
98   uv_fs_t req;
99   int ret = uv_fs_closedir(nullptr, &req, dir_, nullptr);
100   uv_fs_req_cleanup(&req);
101   closing_ = false;
102   closed_ = true;
103 
104   struct err_detail { int ret; };
105 
106   err_detail detail { ret };
107 
108   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   env()->SetImmediate([](Environment* env) {
127     ProcessEmitWarning(env,
128                        "Closing directory handle on garbage collection");
129   }, CallbackFlags::kUnrefed);
130 }
131 
AfterClose(uv_fs_t * req)132 void AfterClose(uv_fs_t* req) {
133   FSReqBase* req_wrap = FSReqBase::from_req(req);
134   FSReqAfterScope after(req_wrap, req);
135 
136   if (after.Proceed())
137     req_wrap->Resolve(Undefined(req_wrap->env()->isolate()));
138 }
139 
Close(const FunctionCallbackInfo<Value> & args)140 void DirHandle::Close(const FunctionCallbackInfo<Value>& args) {
141   Environment* env = Environment::GetCurrent(args);
142 
143   const int argc = args.Length();
144   CHECK_GE(argc, 1);
145 
146   DirHandle* dir;
147   ASSIGN_OR_RETURN_UNWRAP(&dir, args.Holder());
148 
149   dir->closing_ = false;
150   dir->closed_ = true;
151 
152   FSReqBase* req_wrap_async = GetReqWrap(env, args[0]);
153   if (req_wrap_async != nullptr) {  // close(req)
154     AsyncCall(env, req_wrap_async, args, "closedir", UTF8, AfterClose,
155               uv_fs_closedir, dir->dir());
156   } else {  // close(undefined, ctx)
157     CHECK_EQ(argc, 2);
158     FSReqWrapSync req_wrap_sync;
159     FS_DIR_SYNC_TRACE_BEGIN(closedir);
160     SyncCall(env, args[1], &req_wrap_sync, "closedir", uv_fs_closedir,
161              dir->dir());
162     FS_DIR_SYNC_TRACE_END(closedir);
163   }
164 }
165 
DirentListToArray(Environment * env,uv_dirent_t * ents,int num,enum encoding encoding,Local<Value> * err_out)166 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   MaybeStackBuffer<Local<Value>, 64> entries(num * 2);
173 
174   // Return an array of all read filenames.
175   int j = 0;
176   for (int i = 0; i < num; i++) {
177     Local<Value> filename;
178     Local<Value> error;
179     const size_t namelen = strlen(ents[i].name);
180     if (!StringBytes::Encode(env->isolate(),
181                              ents[i].name,
182                              namelen,
183                              encoding,
184                              &error).ToLocal(&filename)) {
185       *err_out = error;
186       return MaybeLocal<Array>();
187     }
188 
189     entries[j++] = filename;
190     entries[j++] = Integer::New(env->isolate(), ents[i].type);
191   }
192 
193   return Array::New(env->isolate(), entries.out(), j);
194 }
195 
AfterDirRead(uv_fs_t * req)196 static void AfterDirRead(uv_fs_t* req) {
197   BaseObjectPtr<FSReqBase> req_wrap { FSReqBase::from_req(req) };
198   FSReqAfterScope after(req_wrap.get(), req);
199 
200   if (!after.Proceed()) {
201     return;
202   }
203 
204   Environment* env = req_wrap->env();
205   Isolate* isolate = env->isolate();
206 
207   if (req->result == 0) {
208     // Done
209     Local<Value> done = Null(isolate);
210     after.Clear();
211     req_wrap->Resolve(done);
212     return;
213   }
214 
215   uv_dir_t* dir = static_cast<uv_dir_t*>(req->ptr);
216 
217   Local<Value> error;
218   Local<Array> js_array;
219   if (!DirentListToArray(env,
220                          dir->dirents,
221                          req->result,
222                          req_wrap->encoding(),
223                          &error).ToLocal(&js_array)) {
224     // Clear libuv resources *before* delivering results to JS land because
225     // that can schedule another operation on the same uv_dir_t. Ditto below.
226     after.Clear();
227     return req_wrap->Reject(error);
228   }
229 
230   after.Clear();
231   req_wrap->Resolve(js_array);
232 }
233 
234 
Read(const FunctionCallbackInfo<Value> & args)235 void DirHandle::Read(const FunctionCallbackInfo<Value>& args) {
236   Environment* env = Environment::GetCurrent(args);
237   Isolate* isolate = env->isolate();
238 
239   const int argc = args.Length();
240   CHECK_GE(argc, 3);
241 
242   const enum encoding encoding = ParseEncoding(isolate, args[0], UTF8);
243 
244   DirHandle* dir;
245   ASSIGN_OR_RETURN_UNWRAP(&dir, args.Holder());
246 
247   CHECK(args[1]->IsNumber());
248   uint64_t buffer_size = args[1].As<Number>()->Value();
249 
250   if (buffer_size != dir->dirents_.size()) {
251     dir->dirents_.resize(buffer_size);
252     dir->dir_->nentries = buffer_size;
253     dir->dir_->dirents = dir->dirents_.data();
254   }
255 
256   FSReqBase* req_wrap_async = GetReqWrap(env, args[2]);
257   if (req_wrap_async != nullptr) {  // dir.read(encoding, bufferSize, req)
258     AsyncCall(env, req_wrap_async, args, "readdir", encoding,
259               AfterDirRead, uv_fs_readdir, dir->dir());
260   } else {  // dir.read(encoding, bufferSize, undefined, ctx)
261     CHECK_EQ(argc, 4);
262     FSReqWrapSync req_wrap_sync;
263     FS_DIR_SYNC_TRACE_BEGIN(readdir);
264     int err = SyncCall(env, args[3], &req_wrap_sync, "readdir", uv_fs_readdir,
265                        dir->dir());
266     FS_DIR_SYNC_TRACE_END(readdir);
267     if (err < 0) {
268       return;  // syscall failed, no need to continue, error info is in ctx
269     }
270 
271     if (req_wrap_sync.req.result == 0) {
272       // Done
273       Local<Value> done = Null(isolate);
274       args.GetReturnValue().Set(done);
275       return;
276     }
277 
278     CHECK_GE(req_wrap_sync.req.result, 0);
279 
280     Local<Value> error;
281     Local<Array> js_array;
282     if (!DirentListToArray(env,
283                            dir->dir()->dirents,
284                            req_wrap_sync.req.result,
285                            encoding,
286                            &error).ToLocal(&js_array)) {
287       Local<Object> ctx = args[2].As<Object>();
288       USE(ctx->Set(env->context(), env->error_string(), error));
289       return;
290     }
291 
292     args.GetReturnValue().Set(js_array);
293   }
294 }
295 
AfterOpenDir(uv_fs_t * req)296 void AfterOpenDir(uv_fs_t* req) {
297   FSReqBase* req_wrap = FSReqBase::from_req(req);
298   FSReqAfterScope after(req_wrap, req);
299 
300   if (!after.Proceed()) {
301     return;
302   }
303 
304   Environment* env = req_wrap->env();
305 
306   uv_dir_t* dir = static_cast<uv_dir_t*>(req->ptr);
307   DirHandle* handle = DirHandle::New(env, dir);
308 
309   req_wrap->Resolve(handle->object().As<Value>());
310 }
311 
OpenDir(const FunctionCallbackInfo<Value> & args)312 static void OpenDir(const FunctionCallbackInfo<Value>& args) {
313   Environment* env = Environment::GetCurrent(args);
314   Isolate* isolate = env->isolate();
315 
316   const int argc = args.Length();
317   CHECK_GE(argc, 3);
318 
319   BufferValue path(isolate, args[0]);
320   CHECK_NOT_NULL(*path);
321 
322   const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8);
323 
324   FSReqBase* req_wrap_async = GetReqWrap(env, args[2]);
325   if (req_wrap_async != nullptr) {  // openDir(path, encoding, req)
326     AsyncCall(env, req_wrap_async, args, "opendir", encoding, AfterOpenDir,
327               uv_fs_opendir, *path);
328   } else {  // openDir(path, encoding, undefined, ctx)
329     CHECK_EQ(argc, 4);
330     FSReqWrapSync req_wrap_sync;
331     FS_DIR_SYNC_TRACE_BEGIN(opendir);
332     int result = SyncCall(env, args[3], &req_wrap_sync, "opendir",
333                           uv_fs_opendir, *path);
334     FS_DIR_SYNC_TRACE_END(opendir);
335     if (result < 0) {
336       return;  // syscall failed, no need to continue, error info is in ctx
337     }
338 
339     uv_fs_t* req = &req_wrap_sync.req;
340     uv_dir_t* dir = static_cast<uv_dir_t*>(req->ptr);
341     DirHandle* handle = DirHandle::New(env, dir);
342 
343     args.GetReturnValue().Set(handle->object().As<Value>());
344   }
345 }
346 
Initialize(Local<Object> target,Local<Value> unused,Local<Context> context,void * priv)347 void Initialize(Local<Object> target,
348                 Local<Value> unused,
349                 Local<Context> context,
350                 void* priv) {
351   Environment* env = Environment::GetCurrent(context);
352   Isolate* isolate = env->isolate();
353 
354   env->SetMethod(target, "opendir", OpenDir);
355 
356   // Create FunctionTemplate for DirHandle
357   Local<FunctionTemplate> dir = env->NewFunctionTemplate(DirHandle::New);
358   dir->Inherit(AsyncWrap::GetConstructorTemplate(env));
359   env->SetProtoMethod(dir, "read", DirHandle::Read);
360   env->SetProtoMethod(dir, "close", DirHandle::Close);
361   Local<ObjectTemplate> dirt = dir->InstanceTemplate();
362   dirt->SetInternalFieldCount(DirHandle::kInternalFieldCount);
363   Local<String> handleString =
364        FIXED_ONE_BYTE_STRING(isolate, "DirHandle");
365   dir->SetClassName(handleString);
366   target
367       ->Set(context, handleString,
368             dir->GetFunction(env->context()).ToLocalChecked())
369       .FromJust();
370   env->set_dir_instance_template(dirt);
371 }
372 
373 }  // namespace fs_dir
374 
375 }  // end namespace node
376 
377 NODE_MODULE_CONTEXT_AWARE_INTERNAL(fs_dir, node::fs_dir::Initialize)
378