• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #include "node_dir.h"
2 #include "node_file-inl.h"
3 #include "node_process-inl.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::Value;
43 
44 #define TRACE_NAME(name) "fs_dir.sync." #name
45 #define GET_TRACE_ENABLED                                                      \
46   (*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED                                 \
47   (TRACING_CATEGORY_NODE2(fs_dir, sync)) != 0)
48 #define FS_DIR_SYNC_TRACE_BEGIN(syscall, ...)                                  \
49   if (GET_TRACE_ENABLED)                                                       \
50   TRACE_EVENT_BEGIN(TRACING_CATEGORY_NODE2(fs_dir, sync), TRACE_NAME(syscall), \
51   ##__VA_ARGS__);
52 #define FS_DIR_SYNC_TRACE_END(syscall, ...)                                    \
53   if (GET_TRACE_ENABLED)                                                       \
54   TRACE_EVENT_END(TRACING_CATEGORY_NODE2(fs_dir, sync), TRACE_NAME(syscall),   \
55   ##__VA_ARGS__);
56 
DirHandle(Environment * env,Local<Object> obj,uv_dir_t * dir)57 DirHandle::DirHandle(Environment* env, Local<Object> obj, uv_dir_t* dir)
58     : AsyncWrap(env, obj, AsyncWrap::PROVIDER_DIRHANDLE),
59       dir_(dir) {
60   MakeWeak();
61 
62   dir_->nentries = 0;
63   dir_->dirents = nullptr;
64 }
65 
New(Environment * env,uv_dir_t * dir)66 DirHandle* DirHandle::New(Environment* env, uv_dir_t* dir) {
67   Local<Object> obj;
68   if (!env->dir_instance_template()
69           ->NewInstance(env->context())
70           .ToLocal(&obj)) {
71     return nullptr;
72   }
73 
74   return new DirHandle(env, obj, dir);
75 }
76 
New(const FunctionCallbackInfo<Value> & args)77 void DirHandle::New(const FunctionCallbackInfo<Value>& args) {
78   CHECK(args.IsConstructCall());
79 }
80 
~DirHandle()81 DirHandle::~DirHandle() {
82   CHECK(!closing_);  // We should not be deleting while explicitly closing!
83   GCClose();         // Close synchronously and emit warning
84   CHECK(closed_);    // We have to be closed at the point
85 }
86 
MemoryInfo(MemoryTracker * tracker) const87 void DirHandle::MemoryInfo(MemoryTracker* tracker) const {
88   tracker->TrackFieldWithSize("dir", sizeof(*dir_));
89 }
90 
91 // Close the directory handle if it hasn't already been closed. A process
92 // warning will be emitted using a SetImmediate to avoid calling back to
93 // JS during GC. If closing the fd fails at this point, a fatal exception
94 // will crash the process immediately.
GCClose()95 inline void DirHandle::GCClose() {
96   if (closed_) return;
97   uv_fs_t req;
98   int ret = uv_fs_closedir(nullptr, &req, dir_, nullptr);
99   uv_fs_req_cleanup(&req);
100   closing_ = false;
101   closed_ = true;
102 
103   struct err_detail { int ret; };
104 
105   err_detail detail { ret };
106 
107   if (ret < 0) {
108     // Do not unref this
109     env()->SetImmediate([detail](Environment* env) {
110       const char* msg = "Closing directory handle on garbage collection failed";
111       // This exception will end up being fatal for the process because
112       // it is being thrown from within the SetImmediate handler and
113       // there is no JS stack to bubble it to. In other words, tearing
114       // down the process is the only reasonable thing we can do here.
115       HandleScope handle_scope(env->isolate());
116       env->ThrowUVException(detail.ret, "close", msg);
117     });
118     return;
119   }
120 
121   // If the close was successful, we still want to emit a process warning
122   // to notify that the file descriptor was gc'd. We want to be noisy about
123   // this because not explicitly closing the DirHandle is a bug.
124 
125   env()->SetImmediate([](Environment* env) {
126     ProcessEmitWarning(env,
127                        "Closing directory handle on garbage collection");
128   }, CallbackFlags::kUnrefed);
129 }
130 
AfterClose(uv_fs_t * req)131 void AfterClose(uv_fs_t* req) {
132   FSReqBase* req_wrap = FSReqBase::from_req(req);
133   FSReqAfterScope after(req_wrap, req);
134 
135   if (after.Proceed())
136     req_wrap->Resolve(Undefined(req_wrap->env()->isolate()));
137 }
138 
Close(const FunctionCallbackInfo<Value> & args)139 void DirHandle::Close(const FunctionCallbackInfo<Value>& args) {
140   Environment* env = Environment::GetCurrent(args);
141 
142   const int argc = args.Length();
143   CHECK_GE(argc, 1);
144 
145   DirHandle* dir;
146   ASSIGN_OR_RETURN_UNWRAP(&dir, args.Holder());
147 
148   dir->closing_ = false;
149   dir->closed_ = true;
150 
151   FSReqBase* req_wrap_async = GetReqWrap(args, 0);
152   if (req_wrap_async != nullptr) {  // close(req)
153     AsyncCall(env, req_wrap_async, args, "closedir", UTF8, AfterClose,
154               uv_fs_closedir, dir->dir());
155   } else {  // close(undefined, ctx)
156     CHECK_EQ(argc, 2);
157     FSReqWrapSync req_wrap_sync;
158     FS_DIR_SYNC_TRACE_BEGIN(closedir);
159     SyncCall(env, args[1], &req_wrap_sync, "closedir", uv_fs_closedir,
160              dir->dir());
161     FS_DIR_SYNC_TRACE_END(closedir);
162   }
163 }
164 
DirentListToArray(Environment * env,uv_dirent_t * ents,int num,enum encoding encoding,Local<Value> * err_out)165 static MaybeLocal<Array> DirentListToArray(
166     Environment* env,
167     uv_dirent_t* ents,
168     int num,
169     enum encoding encoding,
170     Local<Value>* err_out) {
171   MaybeStackBuffer<Local<Value>, 64> entries(num * 2);
172 
173   // Return an array of all read filenames.
174   int j = 0;
175   for (int i = 0; i < num; i++) {
176     Local<Value> filename;
177     Local<Value> error;
178     const size_t namelen = strlen(ents[i].name);
179     if (!StringBytes::Encode(env->isolate(),
180                              ents[i].name,
181                              namelen,
182                              encoding,
183                              &error).ToLocal(&filename)) {
184       *err_out = error;
185       return MaybeLocal<Array>();
186     }
187 
188     entries[j++] = filename;
189     entries[j++] = Integer::New(env->isolate(), ents[i].type);
190   }
191 
192   return Array::New(env->isolate(), entries.out(), j);
193 }
194 
AfterDirRead(uv_fs_t * req)195 static void AfterDirRead(uv_fs_t* req) {
196   BaseObjectPtr<FSReqBase> req_wrap { FSReqBase::from_req(req) };
197   FSReqAfterScope after(req_wrap.get(), req);
198 
199   if (!after.Proceed()) {
200     return;
201   }
202 
203   Environment* env = req_wrap->env();
204   Isolate* isolate = env->isolate();
205 
206   if (req->result == 0) {
207     // Done
208     Local<Value> done = Null(isolate);
209     after.Clear();
210     req_wrap->Resolve(done);
211     return;
212   }
213 
214   uv_dir_t* dir = static_cast<uv_dir_t*>(req->ptr);
215 
216   Local<Value> error;
217   Local<Array> js_array;
218   if (!DirentListToArray(env,
219                          dir->dirents,
220                          req->result,
221                          req_wrap->encoding(),
222                          &error).ToLocal(&js_array)) {
223     // Clear libuv resources *before* delivering results to JS land because
224     // that can schedule another operation on the same uv_dir_t. Ditto below.
225     after.Clear();
226     return req_wrap->Reject(error);
227   }
228 
229   after.Clear();
230   req_wrap->Resolve(js_array);
231 }
232 
233 
Read(const FunctionCallbackInfo<Value> & args)234 void DirHandle::Read(const FunctionCallbackInfo<Value>& args) {
235   Environment* env = Environment::GetCurrent(args);
236   Isolate* isolate = env->isolate();
237 
238   const int argc = args.Length();
239   CHECK_GE(argc, 3);
240 
241   const enum encoding encoding = ParseEncoding(isolate, args[0], UTF8);
242 
243   DirHandle* dir;
244   ASSIGN_OR_RETURN_UNWRAP(&dir, args.Holder());
245 
246   CHECK(args[1]->IsNumber());
247   uint64_t buffer_size = args[1].As<Number>()->Value();
248 
249   if (buffer_size != dir->dirents_.size()) {
250     dir->dirents_.resize(buffer_size);
251     dir->dir_->nentries = buffer_size;
252     dir->dir_->dirents = dir->dirents_.data();
253   }
254 
255   FSReqBase* req_wrap_async = GetReqWrap(args, 2);
256   if (req_wrap_async != nullptr) {  // dir.read(encoding, bufferSize, req)
257     AsyncCall(env, req_wrap_async, args, "readdir", encoding,
258               AfterDirRead, uv_fs_readdir, dir->dir());
259   } else {  // dir.read(encoding, bufferSize, undefined, ctx)
260     CHECK_EQ(argc, 4);
261     FSReqWrapSync req_wrap_sync;
262     FS_DIR_SYNC_TRACE_BEGIN(readdir);
263     int err = SyncCall(env, args[3], &req_wrap_sync, "readdir", uv_fs_readdir,
264                        dir->dir());
265     FS_DIR_SYNC_TRACE_END(readdir);
266     if (err < 0) {
267       return;  // syscall failed, no need to continue, error info is in ctx
268     }
269 
270     if (req_wrap_sync.req.result == 0) {
271       // Done
272       Local<Value> done = Null(isolate);
273       args.GetReturnValue().Set(done);
274       return;
275     }
276 
277     CHECK_GE(req_wrap_sync.req.result, 0);
278 
279     Local<Value> error;
280     Local<Array> js_array;
281     if (!DirentListToArray(env,
282                            dir->dir()->dirents,
283                            req_wrap_sync.req.result,
284                            encoding,
285                            &error).ToLocal(&js_array)) {
286       Local<Object> ctx = args[2].As<Object>();
287       USE(ctx->Set(env->context(), env->error_string(), error));
288       return;
289     }
290 
291     args.GetReturnValue().Set(js_array);
292   }
293 }
294 
AfterOpenDir(uv_fs_t * req)295 void AfterOpenDir(uv_fs_t* req) {
296   FSReqBase* req_wrap = FSReqBase::from_req(req);
297   FSReqAfterScope after(req_wrap, req);
298 
299   if (!after.Proceed()) {
300     return;
301   }
302 
303   Environment* env = req_wrap->env();
304 
305   uv_dir_t* dir = static_cast<uv_dir_t*>(req->ptr);
306   DirHandle* handle = DirHandle::New(env, dir);
307 
308   req_wrap->Resolve(handle->object().As<Value>());
309 }
310 
OpenDir(const FunctionCallbackInfo<Value> & args)311 static void OpenDir(const FunctionCallbackInfo<Value>& args) {
312   Environment* env = Environment::GetCurrent(args);
313   Isolate* isolate = env->isolate();
314 
315   const int argc = args.Length();
316   CHECK_GE(argc, 3);
317 
318   BufferValue path(isolate, args[0]);
319   CHECK_NOT_NULL(*path);
320 
321   const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8);
322 
323   FSReqBase* req_wrap_async = GetReqWrap(args, 2);
324   if (req_wrap_async != nullptr) {  // openDir(path, encoding, req)
325     AsyncCall(env, req_wrap_async, args, "opendir", encoding, AfterOpenDir,
326               uv_fs_opendir, *path);
327   } else {  // openDir(path, encoding, undefined, ctx)
328     CHECK_EQ(argc, 4);
329     FSReqWrapSync req_wrap_sync;
330     FS_DIR_SYNC_TRACE_BEGIN(opendir);
331     int result = SyncCall(env, args[3], &req_wrap_sync, "opendir",
332                           uv_fs_opendir, *path);
333     FS_DIR_SYNC_TRACE_END(opendir);
334     if (result < 0) {
335       return;  // syscall failed, no need to continue, error info is in ctx
336     }
337 
338     uv_fs_t* req = &req_wrap_sync.req;
339     uv_dir_t* dir = static_cast<uv_dir_t*>(req->ptr);
340     DirHandle* handle = DirHandle::New(env, dir);
341 
342     args.GetReturnValue().Set(handle->object().As<Value>());
343   }
344 }
345 
Initialize(Local<Object> target,Local<Value> unused,Local<Context> context,void * priv)346 void Initialize(Local<Object> target,
347                 Local<Value> unused,
348                 Local<Context> context,
349                 void* priv) {
350   Environment* env = Environment::GetCurrent(context);
351 
352   env->SetMethod(target, "opendir", OpenDir);
353 
354   // Create FunctionTemplate for DirHandle
355   Local<FunctionTemplate> dir = env->NewFunctionTemplate(DirHandle::New);
356   dir->Inherit(AsyncWrap::GetConstructorTemplate(env));
357   env->SetProtoMethod(dir, "read", DirHandle::Read);
358   env->SetProtoMethod(dir, "close", DirHandle::Close);
359   Local<ObjectTemplate> dirt = dir->InstanceTemplate();
360   dirt->SetInternalFieldCount(DirHandle::kInternalFieldCount);
361   env->SetConstructorFunction(target, "DirHandle", dir);
362   env->set_dir_instance_template(dirt);
363 }
364 
365 }  // namespace fs_dir
366 
367 }  // end namespace node
368 
369 NODE_MODULE_CONTEXT_AWARE_INTERNAL(fs_dir, node::fs_dir::Initialize)
370