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 21namespace node { 22 23namespace fs_dir { 24 25using fs::FSReqAfterScope; 26using fs::FSReqBase; 27using fs::FSReqWrapSync; 28using fs::GetReqWrap; 29 30using v8::Array; 31using v8::Context; 32using v8::FunctionCallbackInfo; 33using v8::FunctionTemplate; 34using v8::HandleScope; 35using v8::Integer; 36using v8::Isolate; 37using v8::Local; 38using v8::MaybeLocal; 39using v8::Null; 40using v8::Number; 41using v8::Object; 42using v8::ObjectTemplate; 43using v8::Value; 44 45static 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 97DirHandle::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 106DirHandle* 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 117void DirHandle::New(const FunctionCallbackInfo<Value>& args) { 118 CHECK(args.IsConstructCall()); 119} 120 121DirHandle::~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 127void 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. 135inline 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 173void 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 182void 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 209static 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 239static 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 280void 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 343void 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 360static 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 397void 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 417void 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 428NODE_BINDING_CONTEXT_AWARE_INTERNAL(fs_dir, node::fs_dir::Initialize) 429NODE_BINDING_EXTERNAL_REFERENCE(fs_dir, 430 node::fs_dir::RegisterExternalReferences) 431