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‎

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

‎doc/api/fs.md‎

Lines changed: 28 additions & 7 deletions
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‎

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

706-
functionmkdir(path,mode,callback){
706+
functionmkdir(path,options,callback){
707+
if(typeofoptions==='function'){
708+
callback=options;
709+
options={};
710+
}elseif(typeofoptions==='number'||typeofoptions==='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(typeofrecursive!=='boolean')
722+
thrownewERR_INVALID_ARG_TYPE('recursive','boolean',recursive);
717723

718724
constreq=newFSReqWrap();
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-
functionmkdirSync(path,mode){
730+
functionmkdirSync(path,options){
731+
if(typeofoptions==='number'||typeofoptions==='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(typeofrecursive!=='boolean')
742+
thrownewERR_INVALID_ARG_TYPE('recursive','boolean',recursive);
743+
727744
constctx={ 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‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -283,11 +283,23 @@ async function fsync(handle){
283283
returnbinding.fsync(handle.fd,kUsePromises);
284284
}
285285

286-
asyncfunctionmkdir(path,mode){
286+
asyncfunctionmkdir(path,options){
287+
if(typeofoptions==='number'||typeofoptions==='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-
returnbinding.mkdir(pathModule.toNamespacedPath(path),mode,kUsePromises);
297+
if(typeofrecursive!=='boolean')
298+
thrownewERR_INVALID_ARG_TYPE('recursive','boolean',recursive);
299+
300+
returnbinding.mkdir(pathModule.toNamespacedPath(path),
301+
validateMode(mode,'mode',0o777),recursive,
302+
kUsePromises);
291303
}
292304

293305
asyncfunctionreaddir(path,options){

‎src/node_file.cc‎

Lines changed: 151 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,16 @@ using v8::Value;
7676
# defineMIN(a, b) ((a) < (b) ? (a) : (b))
7777
#endif
7878

79+
#ifndef S_ISDIR
80+
# defineS_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
81+
#endif
82+
83+
#ifdef __POSIX__
84+
constchar* kPathSeparator = "/";
85+
#else
86+
constchar* kPathSeparator = "\\/";
87+
#endif
88+
7989
#defineGET_OFFSET(a) ((a)->IsNumber() ? (a).As<Integer>()->Value() : -1)
8090
#defineTRACE_NAME(name) "fs.sync." #name
8191
#defineGET_TRACE_ENABLED \
@@ -1148,28 +1158,162 @@ static void RMDir(const FunctionCallbackInfo<Value>& args){
11481158
}
11491159
}
11501160

1161+
intMKDirpSync(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+
case0:
1172+
if (continuation_data.paths.size() == 0){
1173+
return0;
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+
} elseif (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+
return0;
1204+
}
1205+
1206+
intMKDirpAsync(uv_loop_t* loop,
1207+
uv_fs_t* req,
1208+
constchar* 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+
newFSContinuationData(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+
case0:{
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+
} elseif (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
staticvoidMKDir(const FunctionCallbackInfo<Value>& args){
11521288
Environment* env = Environment::GetCurrent(args);
11531289

11541290
constint 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
constint 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)