Skip to content

Commit fdeace6

Browse files
author
Benjamin Coe
committed
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 99793b8 commit fdeace6

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
@@ -2042,7 +2042,7 @@ changes:
20422042

20432043
Synchronous lstat(2).
20442044

2045-
## fs.mkdir(path[, mode], callback)
2045+
## fs.mkdir(path[, options], callback)
20462046
<!-- YAML
20472047
added: v0.1.8
20482048
changes:
@@ -2061,16 +2061,29 @@ changes:
20612061
-->
20622062

20632063
* `path` {string|Buffer|URL}
2064-
* `mode` {integer} Not supported on Windows. **Default:** `0o777`.
2064+
* `options` {Object|integer}
2065+
* `recursive` {boolean} **Default:** `false`
2066+
* `mode` {integer} Not supported on Windows. **Default:** `0o777`.
20652067
* `callback` {Function}
20662068
* `err` {Error}
20672069

20682070
Asynchronously creates a directory. No arguments other than a possible exception
20692071
are given to the completion callback.
20702072

2073+
The optional `options` argument can be an integer specifying mode (permission
2074+
and sticky bits), or an object with a `mode` property and a `recursive`
2075+
property indicating whether parent folders should be created.
2076+
2077+
```js
2078+
// Creates /tmp/a/apple, regardless of whether `/tmp` and /tmp/a exist.
2079+
fs.mkdir('/tmp/a/apple', { recursive: true }, (err) => {
2080+
if (err) throw err;
2081+
});
2082+
```
2083+
20712084
See also: mkdir(2).
20722085

2073-
## fs.mkdirSync(path[, mode])
2086+
## fs.mkdirSync(path[, options])
20742087
<!-- YAML
20752088
added: v0.1.21
20762089
changes:
@@ -2081,7 +2094,9 @@ changes:
20812094
-->
20822095

20832096
* `path` {string|Buffer|URL}
2084-
* `mode` {integer} Not supported on Windows. **Default:** `0o777`.
2097+
* `options` {Object|integer}
2098+
* `recursive` {boolean} **Default:** `false`
2099+
* `mode` {integer} Not supported on Windows. **Default:** `0o777`.
20852100

20862101
Synchronously creates a directory. Returns `undefined`.
20872102
This is the synchronous version of [`fs.mkdir()`][].
@@ -3974,18 +3989,24 @@ changes:
39743989
Asynchronous lstat(2). The `Promise` is resolved with the [`fs.Stats`][] object
39753990
for the given symbolic link `path`.
39763991

3977-
### fsPromises.mkdir(path[, mode])
3992+
### fsPromises.mkdir(path[, options])
39783993
<!-- YAML
39793994
added: v10.0.0
39803995
-->
39813996

39823997
* `path` {string|Buffer|URL}
3983-
* `mode` {integer} **Default:** `0o777`
3998+
* `options` {Object|integer}
3999+
* `recursive` {boolean} **Default:** `false`
4000+
* `mode` {integer} Not supported on Windows. **Default:** `0o777`.
39844001
* Returns: {Promise}
39854002

39864003
Asynchronously creates a directory then resolves the `Promise` with no
39874004
arguments upon success.
39884005

4006+
The optional `options` argument can be an integer specifying mode (permission
4007+
and sticky bits), or an object with a `mode` property and a `recursive`
4008+
property indicating whether parent folders should be created.
4009+
39894010
### fsPromises.mkdtemp(prefix[, options])
39904011
<!-- YAML
39914012
added: v10.0.0
@@ -4622,7 +4643,7 @@ the file contents.
46224643
[`fs.ftruncate()`]: #fs_fs_ftruncate_fd_len_callback
46234644
[`fs.futimes()`]: #fs_fs_futimes_fd_atime_mtime_callback
46244645
[`fs.lstat()`]: #fs_fs_lstat_path_options_callback
4625-
[`fs.mkdir()`]: #fs_fs_mkdir_path_mode_callback
4646+
[`fs.mkdir()`]: #fs_fs_mkdir_path_options_callback
46264647
[`fs.mkdtemp()`]: #fs_fs_mkdtemp_prefix_options_callback
46274648
[`fs.open()`]: #fs_fs_open_path_flags_mode_callback
46284649
[`fs.read()`]: #fs_fs_read_fd_buffer_offset_length_position_callback

lib/fs.js

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

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

710-
if (arguments.length < 3) {
711-
callback = makeCallback(mode);
712-
mode = 0o777;
713-
} else {
714-
callback = makeCallback(callback);
715-
mode = validateMode(mode, 'mode', 0o777);
716-
}
720+
validatePath(path);
721+
if (typeof recursive !== 'boolean')
722+
throw new ERR_INVALID_ARG_TYPE('recursive', 'boolean', recursive);
717723

718724
const req = new FSReqWrap();
719725
req.oncomplete = callback;
720-
binding.mkdir(pathModule.toNamespacedPath(path), mode, req);
726+
binding.mkdir(pathModule.toNamespacedPath(path),
727+
validateMode(mode, 'mode', 0o777), recursive, req);
721728
}
722729

723-
function mkdirSync(path, mode) {
730+
function mkdirSync(path, options) {
731+
if (typeof options === 'number' || typeof options === 'string') {
732+
options = { mode: options };
733+
}
724734
path = getPathFromURL(path);
735+
const {
736+
recursive = false,
737+
mode = 0o777
738+
} = options || {};
739+
725740
validatePath(path);
726-
mode = validateMode(mode, 'mode', 0o777);
741+
if (typeof recursive !== 'boolean')
742+
throw new ERR_INVALID_ARG_TYPE('recursive', 'boolean', recursive);
743+
727744
const ctx = { path };
728-
binding.mkdir(pathModule.toNamespacedPath(path), mode, undefined, ctx);
745+
binding.mkdir(pathModule.toNamespacedPath(path),
746+
validateMode(mode, 'mode', 0o777), recursive, undefined,
747+
ctx);
729748
handleErrorFromBinding(ctx);
730749
}
731750

lib/internal/fs/promises.js

+15-3
Original file line numberDiff line numberDiff line change
@@ -283,11 +283,23 @@ async function fsync(handle) {
283283
return binding.fsync(handle.fd, kUsePromises);
284284
}
285285

286-
async function mkdir(path, mode) {
286+
async function mkdir(path, options) {
287+
if (typeof options === 'number' || typeof options === 'string') {
288+
options = { mode: options };
289+
}
290+
const {
291+
recursive = false,
292+
mode = 0o777
293+
} = options || {};
287294
path = getPathFromURL(path);
295+
288296
validatePath(path);
289-
mode = validateMode(mode, 'mode', 0o777);
290-
return binding.mkdir(pathModule.toNamespacedPath(path), mode, kUsePromises);
297+
if (typeof recursive !== 'boolean')
298+
throw new ERR_INVALID_ARG_TYPE('recursive', 'boolean', recursive);
299+
300+
return binding.mkdir(pathModule.toNamespacedPath(path),
301+
validateMode(mode, 'mode', 0o777), recursive,
302+
kUsePromises);
291303
}
292304

293305
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 \
@@ -1148,28 +1158,162 @@ static void RMDir(const FunctionCallbackInfo<Value>& args) {
11481158
}
11491159
}
11501160

1161+
int MKDirpSync(uv_loop_t* loop, uv_fs_t* req, const std::string& path, int mode,
1162+
uv_fs_cb cb = nullptr) {
1163+
FSContinuationData continuation_data(req, mode, cb);
1164+
continuation_data.PushPath(std::move(path));
1165+
1166+
while (continuation_data.paths.size() > 0) {
1167+
std::string next_path = continuation_data.PopPath();
1168+
int err = uv_fs_mkdir(loop, req, next_path.c_str(), mode, nullptr);
1169+
while (true) {
1170+
switch (err) {
1171+
case 0:
1172+
if (continuation_data.paths.size() == 0) {
1173+
return 0;
1174+
}
1175+
break;
1176+
case UV_ENOENT: {
1177+
std::string dirname = next_path.substr(0,
1178+
next_path.find_last_of(kPathSeparator));
1179+
if (dirname != next_path) {
1180+
continuation_data.PushPath(std::move(next_path));
1181+
continuation_data.PushPath(std::move(dirname));
1182+
} else if (continuation_data.paths.size() == 0) {
1183+
err = UV_EEXIST;
1184+
continue;
1185+
}
1186+
break;
1187+
}
1188+
case UV_EPERM: {
1189+
return err;
1190+
}
1191+
default:
1192+
uv_fs_req_cleanup(req);
1193+
err = uv_fs_stat(loop, req, next_path.c_str(), nullptr);
1194+
if (err == 0 && !S_ISDIR(req->statbuf.st_mode)) return UV_EEXIST;
1195+
if (err < 0) return err;
1196+
break;
1197+
}
1198+
break;
1199+
}
1200+
uv_fs_req_cleanup(req);
1201+
}
1202+
1203+
return 0;
1204+
}
1205+
1206+
int MKDirpAsync(uv_loop_t* loop,
1207+
uv_fs_t* req,
1208+
const char* path,
1209+
int mode,
1210+
uv_fs_cb cb) {
1211+
FSReqBase* req_wrap = FSReqBase::from_req(req);
1212+
// on the first iteration of algorithm, stash state information.
1213+
if (req_wrap->continuation_data == nullptr) {
1214+
req_wrap->continuation_data = std::unique_ptr<FSContinuationData>{
1215+
new FSContinuationData(req, mode, cb)};
1216+
req_wrap->continuation_data->PushPath(std::move(path));
1217+
}
1218+
1219+
// on each iteration of algorithm, mkdir directory on top of stack.
1220+
std::string next_path = req_wrap->continuation_data->PopPath();
1221+
int err = uv_fs_mkdir(loop, req, next_path.c_str(), mode,
1222+
uv_fs_callback_t{[](uv_fs_t* req) {
1223+
FSReqBase* req_wrap = FSReqBase::from_req(req);
1224+
Environment* env = req_wrap->env();
1225+
uv_loop_t* loop = env->event_loop();
1226+
std::string path = req->path;
1227+
int err = req->result;
1228+
1229+
while (true) {
1230+
switch (err) {
1231+
case 0: {
1232+
if (req_wrap->continuation_data->paths.size() == 0) {
1233+
req_wrap->continuation_data->Done(0);
1234+
} else {
1235+
uv_fs_req_cleanup(req);
1236+
MKDirpAsync(loop, req, path.c_str(),
1237+
req_wrap->continuation_data->mode, nullptr);
1238+
}
1239+
break;
1240+
}
1241+
case UV_ENOENT: {
1242+
std::string dirname = path.substr(0,
1243+
path.find_last_of(kPathSeparator));
1244+
if (dirname != path) {
1245+
req_wrap->continuation_data->PushPath(std::move(path));
1246+
req_wrap->continuation_data->PushPath(std::move(dirname));
1247+
} else if (req_wrap->continuation_data->paths.size() == 0) {
1248+
err = UV_EEXIST;
1249+
continue;
1250+
}
1251+
uv_fs_req_cleanup(req);
1252+
MKDirpAsync(loop, req, path.c_str(),
1253+
req_wrap->continuation_data->mode, nullptr);
1254+
break;
1255+
}
1256+
case UV_EPERM: {
1257+
req_wrap->continuation_data->Done(err);
1258+
break;
1259+
}
1260+
default:
1261+
if (err == UV_EEXIST &&
1262+
req_wrap->continuation_data->paths.size() > 0) {
1263+
uv_fs_req_cleanup(req);
1264+
MKDirpAsync(loop, req, path.c_str(),
1265+
req_wrap->continuation_data->mode, nullptr);
1266+
} else {
1267+
// verify that the path pointed to is actually a directory.
1268+
uv_fs_req_cleanup(req);
1269+
int err = uv_fs_stat(loop, req, path.c_str(),
1270+
uv_fs_callback_t{[](uv_fs_t* req) {
1271+
FSReqBase* req_wrap = FSReqBase::from_req(req);
1272+
int err = req->result;
1273+
if (err == 0 && !S_ISDIR(req->statbuf.st_mode)) err = UV_EEXIST;
1274+
req_wrap->continuation_data->Done(err);
1275+
}});
1276+
if (err < 0) req_wrap->continuation_data->Done(err);
1277+
}
1278+
break;
1279+
}
1280+
break;
1281+
}
1282+
}});
1283+
1284+
return err;
1285+
}
1286+
11511287
static void MKDir(const FunctionCallbackInfo<Value>& args) {
11521288
Environment* env = Environment::GetCurrent(args);
11531289

11541290
const int argc = args.Length();
1155-
CHECK_GE(argc, 3);
1291+
CHECK_GE(argc, 4);
11561292

11571293
BufferValue path(env->isolate(), args[0]);
11581294
CHECK_NOT_NULL(*path);
11591295

11601296
CHECK(args[1]->IsInt32());
11611297
const int mode = args[1].As<Int32>()->Value();
11621298

1163-
FSReqBase* req_wrap_async = GetReqWrap(env, args[2]);
1299+
CHECK(args[2]->IsBoolean());
1300+
bool mkdirp = args[2]->IsTrue();
1301+
1302+
FSReqBase* req_wrap_async = GetReqWrap(env, args[3]);
11641303
if (req_wrap_async != nullptr) { // mkdir(path, mode, req)
1165-
AsyncCall(env, req_wrap_async, args, "mkdir", UTF8, AfterNoArgs,
1166-
uv_fs_mkdir, *path, mode);
1304+
AsyncCall(env, req_wrap_async, args, "mkdir", UTF8,
1305+
AfterNoArgs, mkdirp ? MKDirpAsync : uv_fs_mkdir, *path, mode);
11671306
} else { // mkdir(path, mode, undefined, ctx)
1168-
CHECK_EQ(argc, 4);
1307+
CHECK_EQ(argc, 5);
11691308
FSReqWrapSync req_wrap_sync;
11701309
FS_SYNC_TRACE_BEGIN(mkdir);
1171-
SyncCall(env, args[3], &req_wrap_sync, "mkdir",
1172-
uv_fs_mkdir, *path, mode);
1310+
if (mkdirp) {
1311+
SyncCall(env, args[4], &req_wrap_sync, "mkdir",
1312+
MKDirpSync, *path, mode);
1313+
} else {
1314+
SyncCall(env, args[4], &req_wrap_sync, "mkdir",
1315+
uv_fs_mkdir, *path, mode);
1316+
}
11731317
FS_SYNC_TRACE_END(mkdir);
11741318
}
11751319
}

0 commit comments

Comments
 (0)