Skip to content

Commit 7e45daf

Browse files
Benjamin Coetargos
Benjamin Coe
authored andcommitted
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: nodejs/user-feedback#70 PR-URL: #21875 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Jon Moss <[email protected]> Reviewed-By: Ron Korving <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: Sam Ruby <[email protected]> Reviewed-By: Joyee Cheung <[email protected]>
1 parent 0519689 commit 7e45daf

File tree

8 files changed

+441
-36
lines changed

8 files changed

+441
-36
lines changed

benchmark/fs/bench-mkdirp.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const fs = require('fs');
5+
const tmpdir = require('../../test/common/tmpdir');
6+
tmpdir.refresh();
7+
let dirc = 0;
8+
9+
const bench = common.createBenchmark(main, {
10+
n: [1e4],
11+
});
12+
13+
function main({ n }) {
14+
bench.start();
15+
(function r(cntr) {
16+
if (cntr-- <= 0)
17+
return bench.end(n);
18+
const pathname = `${tmpdir.path}/${++dirc}/${++dirc}/${++dirc}/${++dirc}`;
19+
fs.mkdir(pathname, { createParents: true }, (err) => {
20+
r(cntr);
21+
});
22+
}(n));
23+
}

doc/api/fs.md

+28-7
Original file line numberDiff line numberDiff line change
@@ -2139,7 +2139,7 @@ changes:
21392139

21402140
Synchronous lstat(2).
21412141

2142-
## fs.mkdir(path[, mode], callback)
2142+
## fs.mkdir(path[, options], callback)
21432143
<!-- YAML
21442144
added: v0.1.8
21452145
changes:
@@ -2158,16 +2158,29 @@ changes:
21582158
-->
21592159

21602160
* `path` {string|Buffer|URL}
2161-
* `mode` {integer} Not supported on Windows. **Default:** `0o777`.
2161+
* `options` {Object|integer}
2162+
* `recursive` {boolean} **Default:** `false`
2163+
* `mode` {integer} Not supported on Windows. **Default:** `0o777`.
21622164
* `callback` {Function}
21632165
* `err` {Error}
21642166

21652167
Asynchronously creates a directory. No arguments other than a possible exception
21662168
are given to the completion callback.
21672169

2170+
The optional `options` argument can be an integer specifying mode (permission
2171+
and sticky bits), or an object with a `mode` property and a `recursive`
2172+
property indicating whether parent folders should be created.
2173+
2174+
```js
2175+
// Creates /tmp/a/apple, regardless of whether `/tmp` and /tmp/a exist.
2176+
fs.mkdir('/tmp/a/apple', { recursive: true }, (err) => {
2177+
if (err) throw err;
2178+
});
2179+
```
2180+
21682181
See also: mkdir(2).
21692182

2170-
## fs.mkdirSync(path[, mode])
2183+
## fs.mkdirSync(path[, options])
21712184
<!-- YAML
21722185
added: v0.1.21
21732186
changes:
@@ -2178,7 +2191,9 @@ changes:
21782191
-->
21792192

21802193
* `path` {string|Buffer|URL}
2181-
* `mode` {integer} Not supported on Windows. **Default:** `0o777`.
2194+
* `options` {Object|integer}
2195+
* `recursive` {boolean} **Default:** `false`
2196+
* `mode` {integer} Not supported on Windows. **Default:** `0o777`.
21822197

21832198
Synchronously creates a directory. Returns `undefined`.
21842199
This is the synchronous version of [`fs.mkdir()`][].
@@ -4106,18 +4121,24 @@ changes:
41064121
Asynchronous lstat(2). The `Promise` is resolved with the [`fs.Stats`][] object
41074122
for the given symbolic link `path`.
41084123

4109-
### fsPromises.mkdir(path[, mode])
4124+
### fsPromises.mkdir(path[, options])
41104125
<!-- YAML
41114126
added: v10.0.0
41124127
-->
41134128

41144129
* `path` {string|Buffer|URL}
4115-
* `mode` {integer} **Default:** `0o777`
4130+
* `options` {Object|integer}
4131+
* `recursive` {boolean} **Default:** `false`
4132+
* `mode` {integer} Not supported on Windows. **Default:** `0o777`.
41164133
* Returns: {Promise}
41174134

41184135
Asynchronously creates a directory then resolves the `Promise` with no
41194136
arguments upon success.
41204137

4138+
The optional `options` argument can be an integer specifying mode (permission
4139+
and sticky bits), or an object with a `mode` property and a `recursive`
4140+
property indicating whether parent folders should be created.
4141+
41214142
### fsPromises.mkdtemp(prefix[, options])
41224143
<!-- YAML
41234144
added: v10.0.0
@@ -4763,7 +4784,7 @@ the file contents.
47634784
[`fs.ftruncate()`]: #fs_fs_ftruncate_fd_len_callback
47644785
[`fs.futimes()`]: #fs_fs_futimes_fd_atime_mtime_callback
47654786
[`fs.lstat()`]: #fs_fs_lstat_path_options_callback
4766-
[`fs.mkdir()`]: #fs_fs_mkdir_path_mode_callback
4787+
[`fs.mkdir()`]: #fs_fs_mkdir_path_options_callback
47674788
[`fs.mkdtemp()`]: #fs_fs_mkdtemp_prefix_options_callback
47684789
[`fs.open()`]: #fs_fs_open_path_flags_mode_callback
47694790
[`fs.read()`]: #fs_fs_read_fd_buffer_offset_length_position_callback

lib/fs.js

+32-13
Original file line numberDiff line numberDiff line change
@@ -705,29 +705,48 @@ function fsyncSync(fd) {
705705
handleErrorFromBinding(ctx);
706706
}
707707

708-
function mkdir(path, mode, callback) {
708+
function mkdir(path, options, callback) {
709+
if (typeof options === 'function') {
710+
callback = options;
711+
options = {};
712+
} else if (typeof options === 'number' || typeof options === 'string') {
713+
options = { mode: options };
714+
}
715+
const {
716+
recursive = false,
717+
mode = 0o777
718+
} = options || {};
719+
callback = makeCallback(callback);
709720
path = getPathFromURL(path);
710-
validatePath(path);
711721

712-
if (arguments.length < 3) {
713-
callback = makeCallback(mode);
714-
mode = 0o777;
715-
} else {
716-
callback = makeCallback(callback);
717-
mode = validateMode(mode, 'mode', 0o777);
718-
}
722+
validatePath(path);
723+
if (typeof recursive !== 'boolean')
724+
throw new ERR_INVALID_ARG_TYPE('recursive', 'boolean', recursive);
719725

720726
const req = new FSReqWrap();
721727
req.oncomplete = callback;
722-
binding.mkdir(pathModule.toNamespacedPath(path), mode, req);
728+
binding.mkdir(pathModule.toNamespacedPath(path),
729+
validateMode(mode, 'mode', 0o777), recursive, req);
723730
}
724731

725-
function mkdirSync(path, mode) {
732+
function mkdirSync(path, options) {
733+
if (typeof options === 'number' || typeof options === 'string') {
734+
options = { mode: options };
735+
}
726736
path = getPathFromURL(path);
737+
const {
738+
recursive = false,
739+
mode = 0o777
740+
} = options || {};
741+
727742
validatePath(path);
728-
mode = validateMode(mode, 'mode', 0o777);
743+
if (typeof recursive !== 'boolean')
744+
throw new ERR_INVALID_ARG_TYPE('recursive', 'boolean', recursive);
745+
729746
const ctx = { path };
730-
binding.mkdir(pathModule.toNamespacedPath(path), mode, undefined, ctx);
747+
binding.mkdir(pathModule.toNamespacedPath(path),
748+
validateMode(mode, 'mode', 0o777), recursive, undefined,
749+
ctx);
731750
handleErrorFromBinding(ctx);
732751
}
733752

lib/internal/fs/promises.js

+15-3
Original file line numberDiff line numberDiff line change
@@ -287,11 +287,23 @@ async function fsync(handle) {
287287
return binding.fsync(handle.fd, kUsePromises);
288288
}
289289

290-
async function mkdir(path, mode) {
290+
async function mkdir(path, options) {
291+
if (typeof options === 'number' || typeof options === 'string') {
292+
options = { mode: options };
293+
}
294+
const {
295+
recursive = false,
296+
mode = 0o777
297+
} = options || {};
291298
path = getPathFromURL(path);
299+
292300
validatePath(path);
293-
mode = validateMode(mode, 'mode', 0o777);
294-
return binding.mkdir(pathModule.toNamespacedPath(path), mode, kUsePromises);
301+
if (typeof recursive !== 'boolean')
302+
throw new ERR_INVALID_ARG_TYPE('recursive', 'boolean', recursive);
303+
304+
return binding.mkdir(pathModule.toNamespacedPath(path),
305+
validateMode(mode, 'mode', 0o777), recursive,
306+
kUsePromises);
295307
}
296308

297309
async function readdir(path, options) {

src/node_file.cc

+151-7
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,16 @@ using v8::Value;
7676
# define MIN(a, b) ((a) < (b) ? (a) : (b))
7777
#endif
7878

79+
#ifndef S_ISDIR
80+
# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
81+
#endif
82+
83+
#ifdef __POSIX__
84+
const char* kPathSeparator = "/";
85+
#else
86+
const char* kPathSeparator = "\\/";
87+
#endif
88+
7989
#define GET_OFFSET(a) ((a)->IsNumber() ? (a).As<Integer>()->Value() : -1)
8090
#define TRACE_NAME(name) "fs.sync." #name
8191
#define GET_TRACE_ENABLED \
@@ -1236,28 +1246,162 @@ static void RMDir(const FunctionCallbackInfo<Value>& args) {
12361246
}
12371247
}
12381248

1249+
int MKDirpSync(uv_loop_t* loop, uv_fs_t* req, const std::string& path, int mode,
1250+
uv_fs_cb cb = nullptr) {
1251+
FSContinuationData continuation_data(req, mode, cb);
1252+
continuation_data.PushPath(std::move(path));
1253+
1254+
while (continuation_data.paths.size() > 0) {
1255+
std::string next_path = continuation_data.PopPath();
1256+
int err = uv_fs_mkdir(loop, req, next_path.c_str(), mode, nullptr);
1257+
while (true) {
1258+
switch (err) {
1259+
case 0:
1260+
if (continuation_data.paths.size() == 0) {
1261+
return 0;
1262+
}
1263+
break;
1264+
case UV_ENOENT: {
1265+
std::string dirname = next_path.substr(0,
1266+
next_path.find_last_of(kPathSeparator));
1267+
if (dirname != next_path) {
1268+
continuation_data.PushPath(std::move(next_path));
1269+
continuation_data.PushPath(std::move(dirname));
1270+
} else if (continuation_data.paths.size() == 0) {
1271+
err = UV_EEXIST;
1272+
continue;
1273+
}
1274+
break;
1275+
}
1276+
case UV_EPERM: {
1277+
return err;
1278+
}
1279+
default:
1280+
uv_fs_req_cleanup(req);
1281+
err = uv_fs_stat(loop, req, next_path.c_str(), nullptr);
1282+
if (err == 0 && !S_ISDIR(req->statbuf.st_mode)) return UV_EEXIST;
1283+
if (err < 0) return err;
1284+
break;
1285+
}
1286+
break;
1287+
}
1288+
uv_fs_req_cleanup(req);
1289+
}
1290+
1291+
return 0;
1292+
}
1293+
1294+
int MKDirpAsync(uv_loop_t* loop,
1295+
uv_fs_t* req,
1296+
const char* path,
1297+
int mode,
1298+
uv_fs_cb cb) {
1299+
FSReqBase* req_wrap = FSReqBase::from_req(req);
1300+
// on the first iteration of algorithm, stash state information.
1301+
if (req_wrap->continuation_data == nullptr) {
1302+
req_wrap->continuation_data = std::unique_ptr<FSContinuationData>{
1303+
new FSContinuationData(req, mode, cb)};
1304+
req_wrap->continuation_data->PushPath(std::move(path));
1305+
}
1306+
1307+
// on each iteration of algorithm, mkdir directory on top of stack.
1308+
std::string next_path = req_wrap->continuation_data->PopPath();
1309+
int err = uv_fs_mkdir(loop, req, next_path.c_str(), mode,
1310+
uv_fs_callback_t{[](uv_fs_t* req) {
1311+
FSReqBase* req_wrap = FSReqBase::from_req(req);
1312+
Environment* env = req_wrap->env();
1313+
uv_loop_t* loop = env->event_loop();
1314+
std::string path = req->path;
1315+
int err = req->result;
1316+
1317+
while (true) {
1318+
switch (err) {
1319+
case 0: {
1320+
if (req_wrap->continuation_data->paths.size() == 0) {
1321+
req_wrap->continuation_data->Done(0);
1322+
} else {
1323+
uv_fs_req_cleanup(req);
1324+
MKDirpAsync(loop, req, path.c_str(),
1325+
req_wrap->continuation_data->mode, nullptr);
1326+
}
1327+
break;
1328+
}
1329+
case UV_ENOENT: {
1330+
std::string dirname = path.substr(0,
1331+
path.find_last_of(kPathSeparator));
1332+
if (dirname != path) {
1333+
req_wrap->continuation_data->PushPath(std::move(path));
1334+
req_wrap->continuation_data->PushPath(std::move(dirname));
1335+
} else if (req_wrap->continuation_data->paths.size() == 0) {
1336+
err = UV_EEXIST;
1337+
continue;
1338+
}
1339+
uv_fs_req_cleanup(req);
1340+
MKDirpAsync(loop, req, path.c_str(),
1341+
req_wrap->continuation_data->mode, nullptr);
1342+
break;
1343+
}
1344+
case UV_EPERM: {
1345+
req_wrap->continuation_data->Done(err);
1346+
break;
1347+
}
1348+
default:
1349+
if (err == UV_EEXIST &&
1350+
req_wrap->continuation_data->paths.size() > 0) {
1351+
uv_fs_req_cleanup(req);
1352+
MKDirpAsync(loop, req, path.c_str(),
1353+
req_wrap->continuation_data->mode, nullptr);
1354+
} else {
1355+
// verify that the path pointed to is actually a directory.
1356+
uv_fs_req_cleanup(req);
1357+
int err = uv_fs_stat(loop, req, path.c_str(),
1358+
uv_fs_callback_t{[](uv_fs_t* req) {
1359+
FSReqBase* req_wrap = FSReqBase::from_req(req);
1360+
int err = req->result;
1361+
if (err == 0 && !S_ISDIR(req->statbuf.st_mode)) err = UV_EEXIST;
1362+
req_wrap->continuation_data->Done(err);
1363+
}});
1364+
if (err < 0) req_wrap->continuation_data->Done(err);
1365+
}
1366+
break;
1367+
}
1368+
break;
1369+
}
1370+
}});
1371+
1372+
return err;
1373+
}
1374+
12391375
static void MKDir(const FunctionCallbackInfo<Value>& args) {
12401376
Environment* env = Environment::GetCurrent(args);
12411377

12421378
const int argc = args.Length();
1243-
CHECK_GE(argc, 3);
1379+
CHECK_GE(argc, 4);
12441380

12451381
BufferValue path(env->isolate(), args[0]);
12461382
CHECK_NOT_NULL(*path);
12471383

12481384
CHECK(args[1]->IsInt32());
12491385
const int mode = args[1].As<Int32>()->Value();
12501386

1251-
FSReqBase* req_wrap_async = GetReqWrap(env, args[2]);
1387+
CHECK(args[2]->IsBoolean());
1388+
bool mkdirp = args[2]->IsTrue();
1389+
1390+
FSReqBase* req_wrap_async = GetReqWrap(env, args[3]);
12521391
if (req_wrap_async != nullptr) { // mkdir(path, mode, req)
1253-
AsyncCall(env, req_wrap_async, args, "mkdir", UTF8, AfterNoArgs,
1254-
uv_fs_mkdir, *path, mode);
1392+
AsyncCall(env, req_wrap_async, args, "mkdir", UTF8,
1393+
AfterNoArgs, mkdirp ? MKDirpAsync : uv_fs_mkdir, *path, mode);
12551394
} else { // mkdir(path, mode, undefined, ctx)
1256-
CHECK_EQ(argc, 4);
1395+
CHECK_EQ(argc, 5);
12571396
FSReqWrapSync req_wrap_sync;
12581397
FS_SYNC_TRACE_BEGIN(mkdir);
1259-
SyncCall(env, args[3], &req_wrap_sync, "mkdir",
1260-
uv_fs_mkdir, *path, mode);
1398+
if (mkdirp) {
1399+
SyncCall(env, args[4], &req_wrap_sync, "mkdir",
1400+
MKDirpSync, *path, mode);
1401+
} else {
1402+
SyncCall(env, args[4], &req_wrap_sync, "mkdir",
1403+
uv_fs_mkdir, *path, mode);
1404+
}
12611405
FS_SYNC_TRACE_END(mkdir);
12621406
}
12631407
}

0 commit comments

Comments
 (0)