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