Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit ff58854

Browse filesBrowse 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
Copy full SHA for ff58854

File tree

Expand file treeCollapse file tree

7 files changed

+225
-23
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

7 files changed

+225
-23
lines changed
Open diff view settings
Collapse file

‎doc/api/fs.md‎

Copy file name to clipboardExpand all lines: doc/api/fs.md
+9-5Lines changed: 9 additions & 5 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -2463,8 +2463,10 @@ changes:
24632463
* `callback` {Function}
24642464
* `err` {Error}
24652465

2466-
Asynchronously creates a directory. No arguments other than a possible exception
2467-
are given to the completion callback.
2466+
Asynchronously creates a directory.
2467+
2468+
The callback is given a possible exception and, if `recursive` is `true`, the
2469+
first folder path created, `(err, [path])`.
24682470

24692471
The optional `options` argument can be an integer specifying mode (permission
24702472
and sticky bits), or an object with a `mode` property and a `recursive`
@@ -2508,8 +2510,10 @@ changes:
25082510
* `options` {Object|integer}
25092511
* `recursive` {boolean} **Default:** `false`
25102512
* `mode` {string|integer} Not supported on Windows. **Default:** `0o777`.
2513+
* Returns: {string|undefined}
25112514

2512-
Synchronously creates a directory. Returns `undefined`.
2515+
Synchronously creates a directory. Returns `undefined`, or if `recursive` is
2516+
`true`, the first folder path created.
25132517
This is the synchronous version of [`fs.mkdir()`][].
25142518

25152519
See also: mkdir(2).
@@ -4815,8 +4819,8 @@ added: v10.0.0
48154819
* `mode` {string|integer} Not supported on Windows. **Default:** `0o777`.
48164820
* Returns: {Promise}
48174821

4818-
Asynchronously creates a directory then resolves the `Promise` with no
4819-
arguments upon success.
4822+
Asynchronously creates a directory then resolves the `Promise` with either no
4823+
arguments, or the first folder path created if `recursive` is `true`.
48204824

48214825
The optional `options` argument can be an integer specifying mode (permission
48224826
and sticky bits), or an object with a `mode` property and a `recursive`
Collapse file

‎lib/fs.js‎

Copy file name to clipboardExpand all lines: lib/fs.js
+6-3Lines 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) {
Collapse file

‎src/inspector_profiler.cc‎

Copy file name to clipboardExpand all lines: src/inspector_profiler.cc
+3-3Lines 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));
Collapse file

‎src/node_file-inl.h‎

Copy file name to clipboardExpand all lines: src/node_file-inl.h
+6Lines 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());
Collapse file

‎src/node_file.cc‎

Copy file name to clipboardExpand all lines: src/node_file.cc
+93-12Lines 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);
Collapse file

‎src/node_file.h‎

Copy file name to clipboardExpand all lines: src/node_file.h
+14Lines 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
Collapse file

‎test/parallel/test-fs-mkdir.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-fs-mkdir.js
+94Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,100 @@ if (common.isMainThread && (common.isLinux || common.isOSX)) {
245245
});
246246
}
247247

248+
// `mkdirp` returns first folder created, when all folders are new.
249+
{
250+
const dir1 = nextdir();
251+
const dir2 = nextdir();
252+
const firstPathCreated = path.join(tmpdir.path, dir1);
253+
const pathname = path.join(tmpdir.path, dir1, dir2);
254+
255+
fs.mkdir(pathname, { recursive: true }, common.mustCall(function(err, path) {
256+
assert.strictEqual(err, null);
257+
assert.strictEqual(fs.existsSync(pathname), true);
258+
assert.strictEqual(fs.statSync(pathname).isDirectory(), true);
259+
assert.strictEqual(path, firstPathCreated);
260+
}));
261+
}
262+
263+
// `mkdirp` returns first folder created, when last folder is new.
264+
{
265+
const dir1 = nextdir();
266+
const dir2 = nextdir();
267+
const pathname = path.join(tmpdir.path, dir1, dir2);
268+
fs.mkdirSync(path.join(tmpdir.path, dir1));
269+
fs.mkdir(pathname, { recursive: true }, common.mustCall(function(err, path) {
270+
assert.strictEqual(err, null);
271+
assert.strictEqual(fs.existsSync(pathname), true);
272+
assert.strictEqual(fs.statSync(pathname).isDirectory(), true);
273+
assert.strictEqual(path, pathname);
274+
}));
275+
}
276+
277+
// `mkdirp` returns undefined, when no new folders are created.
278+
{
279+
const dir1 = nextdir();
280+
const dir2 = nextdir();
281+
const pathname = path.join(tmpdir.path, dir1, dir2);
282+
fs.mkdirSync(path.join(tmpdir.path, dir1, dir2), { recursive: true });
283+
fs.mkdir(pathname, { recursive: true }, common.mustCall(function(err, path) {
284+
assert.strictEqual(err, null);
285+
assert.strictEqual(fs.existsSync(pathname), true);
286+
assert.strictEqual(fs.statSync(pathname).isDirectory(), true);
287+
assert.strictEqual(path, undefined);
288+
}));
289+
}
290+
291+
// `mkdirp.sync` returns first folder created, when all folders are new.
292+
{
293+
const dir1 = nextdir();
294+
const dir2 = nextdir();
295+
const firstPathCreated = path.join(tmpdir.path, dir1);
296+
const pathname = path.join(tmpdir.path, dir1, dir2);
297+
const p = fs.mkdirSync(pathname, { recursive: true });
298+
assert.strictEqual(fs.existsSync(pathname), true);
299+
assert.strictEqual(fs.statSync(pathname).isDirectory(), true);
300+
assert.strictEqual(p, firstPathCreated);
301+
}
302+
303+
// `mkdirp.sync` returns first folder created, when last folder is new.
304+
{
305+
const dir1 = nextdir();
306+
const dir2 = nextdir();
307+
const pathname = path.join(tmpdir.path, dir1, dir2);
308+
fs.mkdirSync(path.join(tmpdir.path, dir1), { recursive: true });
309+
const p = fs.mkdirSync(pathname, { recursive: true });
310+
assert.strictEqual(fs.existsSync(pathname), true);
311+
assert.strictEqual(fs.statSync(pathname).isDirectory(), true);
312+
assert.strictEqual(p, pathname);
313+
}
314+
315+
// `mkdirp.sync` returns undefined, when no new folders are created.
316+
{
317+
const dir1 = nextdir();
318+
const dir2 = nextdir();
319+
const pathname = path.join(tmpdir.path, dir1, dir2);
320+
fs.mkdirSync(path.join(tmpdir.path, dir1, dir2), { recursive: true });
321+
const p = fs.mkdirSync(pathname, { recursive: true });
322+
assert.strictEqual(fs.existsSync(pathname), true);
323+
assert.strictEqual(fs.statSync(pathname).isDirectory(), true);
324+
assert.strictEqual(p, undefined);
325+
}
326+
327+
// `mkdirp.promises` returns first folder created, when all folders are new.
328+
{
329+
const dir1 = nextdir();
330+
const dir2 = nextdir();
331+
const firstPathCreated = path.join(tmpdir.path, dir1);
332+
const pathname = path.join(tmpdir.path, dir1, dir2);
333+
async function testCase() {
334+
const p = await fs.promises.mkdir(pathname, { recursive: true });
335+
assert.strictEqual(fs.existsSync(pathname), true);
336+
assert.strictEqual(fs.statSync(pathname).isDirectory(), true);
337+
assert.strictEqual(p, firstPathCreated);
338+
}
339+
testCase();
340+
}
341+
248342
// Keep the event loop alive so the async mkdir() requests
249343
// have a chance to run (since they don't ref the event loop).
250344
process.nextTick(() => {});

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.