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