diff options
author | Benjamin Coe <ben@npmjs.com> | 2018-08-09 16:52:41 -0700 |
---|---|---|
committer | Benjamin Coe <ben@npmjs.com> | 2018-08-11 12:07:32 -0700 |
commit | bdef1b1eb45e2953e1ff68f0cc9a68ec83573e57 (patch) | |
tree | cf12dfaaa414432980c139066aa977ffc0724760 /src/node_file.cc | |
parent | e0395247c899af101f8a1f76a8554be1ff14040a (diff) | |
download | android-node-v8-bdef1b1eb45e2953e1ff68f0cc9a68ec83573e57.tar.gz android-node-v8-bdef1b1eb45e2953e1ff68f0cc9a68ec83573e57.tar.bz2 android-node-v8-bdef1b1eb45e2953e1ff68f0cc9a68ec83573e57.zip |
fs: implement mkdir recursive (mkdirp)
Implements mkdirp functionality in node_file.cc. The Benefit
of implementing in C++ layer is that the logic is more easily
shared between the Promise and callback implementation and
there are notable performance improvements.
This commit is part of the Tooling Group Initiative.
Refs: https://github.com/nodejs/user-feedback/pull/70
PR-URL: https://github.com/nodejs/node/pull/21875
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Jon Moss <me@jonathanmoss.me>
Reviewed-By: Ron Korving <ron@ronkorving.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Anatoli Papirovski <apapirovski@mac.com>
Reviewed-By: Sam Ruby <rubys@intertwingly.net>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Diffstat (limited to 'src/node_file.cc')
-rw-r--r-- | src/node_file.cc | 158 |
1 files changed, 151 insertions, 7 deletions
diff --git a/src/node_file.cc b/src/node_file.cc index b0effaff7b..714dec157b 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -76,6 +76,16 @@ using v8::Value; # define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif +#ifndef S_ISDIR +# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif + +#ifdef __POSIX__ +const char* kPathSeparator = "/"; +#else +const char* kPathSeparator = "\\/"; +#endif + #define GET_OFFSET(a) ((a)->IsNumber() ? (a).As<Integer>()->Value() : -1) #define TRACE_NAME(name) "fs.sync." #name #define GET_TRACE_ENABLED \ @@ -1148,11 +1158,137 @@ static void RMDir(const FunctionCallbackInfo<Value>& args) { } } +int MKDirpSync(uv_loop_t* loop, uv_fs_t* req, const std::string& path, int mode, + uv_fs_cb cb = nullptr) { + FSContinuationData continuation_data(req, mode, cb); + continuation_data.PushPath(std::move(path)); + + while (continuation_data.paths.size() > 0) { + std::string next_path = continuation_data.PopPath(); + int err = uv_fs_mkdir(loop, req, next_path.c_str(), mode, nullptr); + while (true) { + switch (err) { + case 0: + if (continuation_data.paths.size() == 0) { + return 0; + } + break; + case UV_ENOENT: { + std::string dirname = next_path.substr(0, + next_path.find_last_of(kPathSeparator)); + if (dirname != next_path) { + continuation_data.PushPath(std::move(next_path)); + continuation_data.PushPath(std::move(dirname)); + } else if (continuation_data.paths.size() == 0) { + err = UV_EEXIST; + continue; + } + break; + } + case UV_EPERM: { + return err; + } + default: + uv_fs_req_cleanup(req); + err = uv_fs_stat(loop, req, next_path.c_str(), nullptr); + if (err == 0 && !S_ISDIR(req->statbuf.st_mode)) return UV_EEXIST; + if (err < 0) return err; + break; + } + break; + } + uv_fs_req_cleanup(req); + } + + return 0; +} + +int MKDirpAsync(uv_loop_t* loop, + uv_fs_t* req, + const char* path, + int mode, + uv_fs_cb cb) { + FSReqBase* req_wrap = FSReqBase::from_req(req); + // on the first iteration of algorithm, stash state information. + if (req_wrap->continuation_data == nullptr) { + req_wrap->continuation_data = std::unique_ptr<FSContinuationData>{ + new FSContinuationData(req, mode, cb)}; + req_wrap->continuation_data->PushPath(std::move(path)); + } + + // on each iteration of algorithm, mkdir directory on top of stack. + std::string next_path = req_wrap->continuation_data->PopPath(); + int err = uv_fs_mkdir(loop, req, next_path.c_str(), mode, + uv_fs_callback_t{[](uv_fs_t* req) { + FSReqBase* req_wrap = FSReqBase::from_req(req); + Environment* env = req_wrap->env(); + uv_loop_t* loop = env->event_loop(); + std::string path = req->path; + int err = req->result; + + while (true) { + switch (err) { + case 0: { + if (req_wrap->continuation_data->paths.size() == 0) { + req_wrap->continuation_data->Done(0); + } else { + uv_fs_req_cleanup(req); + MKDirpAsync(loop, req, path.c_str(), + req_wrap->continuation_data->mode, nullptr); + } + break; + } + case UV_ENOENT: { + std::string dirname = path.substr(0, + path.find_last_of(kPathSeparator)); + if (dirname != path) { + req_wrap->continuation_data->PushPath(std::move(path)); + req_wrap->continuation_data->PushPath(std::move(dirname)); + } else if (req_wrap->continuation_data->paths.size() == 0) { + err = UV_EEXIST; + continue; + } + uv_fs_req_cleanup(req); + MKDirpAsync(loop, req, path.c_str(), + req_wrap->continuation_data->mode, nullptr); + break; + } + case UV_EPERM: { + req_wrap->continuation_data->Done(err); + break; + } + default: + if (err == UV_EEXIST && + req_wrap->continuation_data->paths.size() > 0) { + uv_fs_req_cleanup(req); + MKDirpAsync(loop, req, path.c_str(), + req_wrap->continuation_data->mode, nullptr); + } else { + // verify that the path pointed to is actually a directory. + uv_fs_req_cleanup(req); + int err = uv_fs_stat(loop, req, path.c_str(), + uv_fs_callback_t{[](uv_fs_t* req) { + FSReqBase* req_wrap = FSReqBase::from_req(req); + int err = req->result; + if (err == 0 && !S_ISDIR(req->statbuf.st_mode)) err = UV_EEXIST; + req_wrap->continuation_data->Done(err); + }}); + if (err < 0) req_wrap->continuation_data->Done(err); + } + break; + } + break; + } + }}); + + return err; +} + static void MKDir(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); const int argc = args.Length(); - CHECK_GE(argc, 3); + CHECK_GE(argc, 4); BufferValue path(env->isolate(), args[0]); CHECK_NOT_NULL(*path); @@ -1160,16 +1296,24 @@ static void MKDir(const FunctionCallbackInfo<Value>& args) { CHECK(args[1]->IsInt32()); const int mode = args[1].As<Int32>()->Value(); - FSReqBase* req_wrap_async = GetReqWrap(env, args[2]); + CHECK(args[2]->IsBoolean()); + bool mkdirp = args[2]->IsTrue(); + + FSReqBase* req_wrap_async = GetReqWrap(env, args[3]); if (req_wrap_async != nullptr) { // mkdir(path, mode, req) - AsyncCall(env, req_wrap_async, args, "mkdir", UTF8, AfterNoArgs, - uv_fs_mkdir, *path, mode); + AsyncCall(env, req_wrap_async, args, "mkdir", UTF8, + AfterNoArgs, mkdirp ? MKDirpAsync : uv_fs_mkdir, *path, mode); } else { // mkdir(path, mode, undefined, ctx) - CHECK_EQ(argc, 4); + CHECK_EQ(argc, 5); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(mkdir); - SyncCall(env, args[3], &req_wrap_sync, "mkdir", - uv_fs_mkdir, *path, mode); + if (mkdirp) { + SyncCall(env, args[4], &req_wrap_sync, "mkdir", + MKDirpSync, *path, mode); + } else { + SyncCall(env, args[4], &req_wrap_sync, "mkdir", + uv_fs_mkdir, *path, mode); + } FS_SYNC_TRACE_END(mkdir); } } |