fs: return first folder made by mkdir recursive · nodejs/node@ff58854 · GitHub
Skip to content

Commit ff58854

Browse files
bcoeMylesBorins
authored andcommitted
fs: return first folder made by mkdir recursive
mkdir('/foo/bar', { recursive: true }) and mkdirSync will now return the path of the first folder created. This matches more closely mkdirp's behavior, and is useful for setting folder permissions. PR-URL: #31530 Refs: #31481 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Rich Trott <rtrott@gmail.com>
1 parent 607ac90 commit ff58854

7 files changed

Lines changed: 225 additions & 23 deletions

File tree

doc/api/fs.md

Lines changed: 9 additions & 5 deletions

lib/fs.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -855,10 +855,13 @@ function mkdirSync(path, options) {
855855
throw new ERR_INVALID_ARG_TYPE('recursive', 'boolean', recursive);
856856

857857
const ctx = { path };
858-
binding.mkdir(pathModule.toNamespacedPath(path),
859-
parseMode(mode, 'mode', 0o777), recursive, undefined,
860-
ctx);
858+
const result = binding.mkdir(pathModule.toNamespacedPath(path),
859+
parseMode(mode, 'mode', 0o777), recursive,
860+
undefined, ctx);
861861
handleErrorFromBinding(ctx);
862+
if (recursive) {
863+
return result;
864+
}
862865
}
863866

864867
function readdir(path, options, callback) {

src/inspector_profiler.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,9 @@ void V8ProfilerConnection::V8ProfilerSessionDelegate::SendMessageToFrontend(
107107
}
108108

109109
static bool EnsureDirectory(const std::string& directory, const char* type) {
110-
uv_fs_t req;
111-
int ret = fs::MKDirpSync(nullptr, &req, directory, 0777, nullptr);
112-
uv_fs_req_cleanup(&req);
110+
fs::FSReqWrapSync req_wrap_sync;
111+
int ret = fs::MKDirpSync(nullptr, &req_wrap_sync.req, directory, 0777,
112+
nullptr);
113113
if (ret < 0 && ret != UV_EEXIST) {
114114
char err_buf[128];
115115
uv_err_name_r(ret, err_buf, sizeof(err_buf));

src/node_file-inl.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ void FSContinuationData::PushPath(const std::string& path) {
2121
paths_.push_back(path);
2222
}
2323

24+
void FSContinuationData::MaybeSetFirstPath(const std::string& path) {
25+
if (first_path_.empty()) {
26+
first_path_ = path;
27+
}
28+
}
29+
2430
std::string FSContinuationData::PopPath() {
2531
CHECK_GT(paths_.size(), 0);
2632
std::string path = std::move(paths_.back());

src/node_file.cc

Lines changed: 93 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,43 @@ void AfterOpenFileHandle(uv_fs_t* req) {
586586
}
587587
}
588588

589+
// Reverse the logic applied by path.toNamespacedPath() to create a
590+
// namespace-prefixed path.
591+
void FromNamespacedPath(std::string* path) {
592+
#ifdef _WIN32
593+
if (path->compare(0, 8, "\\\\?\\UNC\\", 8) == 0) {
594+
*path = path->substr(8);
595+
path->insert(0, "\\\\");
596+
} else if (path->compare(0, 4, "\\\\?\\", 4) == 0) {
597+
*path = path->substr(4);
598+
}
599+
#endif
600+
}
601+
602+
void AfterMkdirp(uv_fs_t* req) {
603+
FSReqBase* req_wrap = FSReqBase::from_req(req);
604+
FSReqAfterScope after(req_wrap, req);
605+
606+
MaybeLocal<Value> path;
607+
Local<Value> error;
608+
609+
if (after.Proceed()) {
610+
if (!req_wrap->continuation_data()->first_path().empty()) {
611+
std::string first_path(req_wrap->continuation_data()->first_path());
612+
FromNamespacedPath(&first_path);
613+
path = StringBytes::Encode(req_wrap->env()->isolate(), first_path.c_str(),
614+
req_wrap->encoding(),
615+
&error);
616+
if (path.IsEmpty())
617+
req_wrap->Reject(error);
618+
else
619+
req_wrap->Resolve(path.ToLocalChecked());
620+
} else {
621+
req_wrap->Resolve(Undefined(req_wrap->env()->isolate()));
622+
}
623+
}
624+
}
625+
589626
void AfterStringPath(uv_fs_t* req) {
590627
FSReqBase* req_wrap = FSReqBase::from_req(req);
591628
FSReqAfterScope after(req_wrap, req);
@@ -1213,18 +1250,25 @@ int MKDirpSync(uv_loop_t* loop,
12131250
const std::string& path,
12141251
int mode,
12151252
uv_fs_cb cb) {
1216-
FSContinuationData continuation_data(req, mode, cb);
1217-
continuation_data.PushPath(std::move(path));
1253+
FSReqWrapSync* req_wrap = ContainerOf(&FSReqWrapSync::req, req);
1254+
1255+
// on the first iteration of algorithm, stash state information.
1256+
if (req_wrap->continuation_data() == nullptr) {
1257+
req_wrap->set_continuation_data(
1258+
std::make_unique<FSContinuationData>(req, mode, cb));
1259+
req_wrap->continuation_data()->PushPath(std::move(path));
1260+
}
12181261

1219-
while (continuation_data.paths().size() > 0) {
1220-
std::string next_path = continuation_data.PopPath();
1262+
while (req_wrap->continuation_data()->paths().size() > 0) {
1263+
std::string next_path = req_wrap->continuation_data()->PopPath();
12211264
int err = uv_fs_mkdir(loop, req, next_path.c_str(), mode, nullptr);
12221265
while (true) {
12231266
switch (err) {
12241267
// Note: uv_fs_req_cleanup in terminal paths will be called by
12251268
// ~FSReqWrapSync():
12261269
case 0:
1227-
if (continuation_data.paths().size() == 0) {
1270+
req_wrap->continuation_data()->MaybeSetFirstPath(next_path);
1271+
if (req_wrap->continuation_data()->paths().size() == 0) {
12281272
return 0;
12291273
}
12301274
break;
@@ -1237,9 +1281,9 @@ int MKDirpSync(uv_loop_t* loop,
12371281
std::string dirname = next_path.substr(0,
12381282
next_path.find_last_of(kPathSeparator));
12391283
if (dirname != next_path) {
1240-
continuation_data.PushPath(std::move(next_path));
1241-
continuation_data.PushPath(std::move(dirname));
1242-
} else if (continuation_data.paths().size() == 0) {
1284+
req_wrap->continuation_data()->PushPath(std::move(next_path));
1285+
req_wrap->continuation_data()->PushPath(std::move(dirname));
1286+
} else if (req_wrap->continuation_data()->paths().size() == 0) {
12431287
err = UV_EEXIST;
12441288
continue;
12451289
}
@@ -1251,7 +1295,8 @@ int MKDirpSync(uv_loop_t* loop,
12511295
err = uv_fs_stat(loop, req, next_path.c_str(), nullptr);
12521296
if (err == 0 && !S_ISDIR(req->statbuf.st_mode)) {
12531297
uv_fs_req_cleanup(req);
1254-
if (orig_err == UV_EEXIST && continuation_data.paths().size() > 0) {
1298+
if (orig_err == UV_EEXIST &&
1299+
req_wrap->continuation_data()->paths().size() > 0) {
12551300
return UV_ENOTDIR;
12561301
}
12571302
return UV_EEXIST;
@@ -1296,8 +1341,10 @@ int MKDirpAsync(uv_loop_t* loop,
12961341
// FSReqAfterScope::~FSReqAfterScope()
12971342
case 0: {
12981343
if (req_wrap->continuation_data()->paths().size() == 0) {
1344+
req_wrap->continuation_data()->MaybeSetFirstPath(path);
12991345
req_wrap->continuation_data()->Done(0);
13001346
} else {
1347+
req_wrap->continuation_data()->MaybeSetFirstPath(path);
13011348
uv_fs_req_cleanup(req);
13021349
MKDirpAsync(loop, req, path.c_str(),
13031350
req_wrap->continuation_data()->mode(), nullptr);
@@ -1360,6 +1407,25 @@ int MKDirpAsync(uv_loop_t* loop,
13601407
return err;
13611408
}
13621409

1410+
int CallMKDirpSync(Environment* env, const FunctionCallbackInfo<Value>& args,
1411+
FSReqWrapSync* req_wrap, const char* path, int mode) {
1412+
env->PrintSyncTrace();
1413+
int err = MKDirpSync(env->event_loop(), &req_wrap->req, path, mode,
1414+
nullptr);
1415+
if (err < 0) {
1416+
v8::Local<v8::Context> context = env->context();
1417+
v8::Local<v8::Object> ctx_obj = args[4].As<v8::Object>();
1418+
v8::Isolate* isolate = env->isolate();
1419+
ctx_obj->Set(context,
1420+
env->errno_string(),
1421+
v8::Integer::New(isolate, err)).Check();
1422+
ctx_obj->Set(context,
1423+
env->syscall_string(),
1424+
OneByteString(isolate, "mkdir")).Check();
1425+
}
1426+
return err;
1427+
}
1428+
13631429
static void MKDir(const FunctionCallbackInfo<Value>& args) {
13641430
Environment* env = Environment::GetCurrent(args);
13651431

@@ -1378,14 +1444,29 @@ static void MKDir(const FunctionCallbackInfo<Value>& args) {
13781444
FSReqBase* req_wrap_async = GetReqWrap(env, args[3]);
13791445
if (req_wrap_async != nullptr) { // mkdir(path, mode, req)
13801446
AsyncCall(env, req_wrap_async, args, "mkdir", UTF8,
1381-
AfterNoArgs, mkdirp ? MKDirpAsync : uv_fs_mkdir, *path, mode);
1447+
mkdirp ? AfterMkdirp : AfterNoArgs,
1448+
mkdirp ? MKDirpAsync : uv_fs_mkdir, *path, mode);
13821449
} else { // mkdir(path, mode, undefined, ctx)
13831450
CHECK_EQ(argc, 5);
13841451
FSReqWrapSync req_wrap_sync;
13851452
FS_SYNC_TRACE_BEGIN(mkdir);
13861453
if (mkdirp) {
1387-
SyncCall(env, args[4], &req_wrap_sync, "mkdir",
1388-
MKDirpSync, *path, mode);
1454+
int err = CallMKDirpSync(env, args, &req_wrap_sync, *path, mode);
1455+
if (err == 0 &&
1456+
!req_wrap_sync.continuation_data()->first_path().empty()) {
1457+
Local<Value> error;
1458+
std::string first_path(req_wrap_sync.continuation_data()->first_path());
1459+
FromNamespacedPath(&first_path);
1460+
MaybeLocal<Value> path = StringBytes::Encode(env->isolate(),
1461+
first_path.c_str(),
1462+
UTF8, &error);
1463+
if (path.IsEmpty()) {
1464+
Local<Object> ctx = args[4].As<Object>();
1465+
ctx->Set(env->context(), env->error_string(), error).Check();
1466+
return;
1467+
}
1468+
args.GetReturnValue().Set(path.ToLocalChecked());
1469+
}
13891470
} else {
13901471
SyncCall(env, args[4], &req_wrap_sync, "mkdir",
13911472
uv_fs_mkdir, *path, mode);

src/node_file.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ class FSContinuationData : public MemoryRetainer {
1919
inline void PushPath(std::string&& path);
2020
inline void PushPath(const std::string& path);
2121
inline std::string PopPath();
22+
// Used by mkdirp to track the first path created:
23+
inline void MaybeSetFirstPath(const std::string& path);
2224
inline void Done(int result);
2325

2426
int mode() const { return mode_; }
2527
const std::vector<std::string>& paths() const { return paths_; }
28+
const std::string& first_path() const { return first_path_; }
2629

2730
void MemoryInfo(MemoryTracker* tracker) const override;
2831
SET_MEMORY_INFO_NAME(FSContinuationData)
@@ -33,6 +36,7 @@ class FSContinuationData : public MemoryRetainer {
3336
uv_fs_t* req_;
3437
int mode_;
3538
std::vector<std::string> paths_;
39+
std::string first_path_;
3640
};
3741

3842
class FSReqBase : public ReqWrap<uv_fs_t> {
@@ -306,8 +310,18 @@ class FSReqWrapSync {
306310
~FSReqWrapSync() { uv_fs_req_cleanup(&req); }
307311
uv_fs_t req;
308312

313+
FSContinuationData* continuation_data() const {
314+
return continuation_data_.get();
315+
}
316+
void set_continuation_data(std::unique_ptr<FSContinuationData> data) {
317+
continuation_data_ = std::move(data);
318+
}
319+
309320
FSReqWrapSync(const FSReqWrapSync&) = delete;
310321
FSReqWrapSync& operator=(const FSReqWrapSync&) = delete;
322+
323+
private:
324+
std::unique_ptr<FSContinuationData> continuation_data_;
311325
};
312326

313327
// TODO(addaleax): Currently, callers check the return value and assume

test/parallel/test-fs-mkdir.js

Lines changed: 94 additions & 0 deletions

0 commit comments

Comments
 (0)