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