GeezerVison is showing github.com — fs: return first folder made by mkdir recursive · nodejs/node@ff58854
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file 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 number | Diff line number | Diff line change |
|---|
@@ -2463,8 +2463,10 @@ changes: | 2463 | 2463 | * `callback` {Function} | 2464 | 2464 | * `err` {Error} | 2465 | 2465 |
| 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])`. | 2468 | 2470 |
| 2469 | 2471 | The optional `options` argument can be an integer specifying mode (permission | 2470 | 2472 | and sticky bits), or an object with a `mode` property and a `recursive` | @@ -2508,8 +2510,10 @@ changes: | 2508 | 2510 | * `options` {Object|integer} | 2509 | 2511 | * `recursive` {boolean} **Default:** `false` | 2510 | 2512 | * `mode` {string|integer} Not supported on Windows. **Default:** `0o777`. | | 2513 | +* Returns: {string|undefined} | 2511 | 2514 |
| 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. | 2513 | 2517 | This is the synchronous version of [`fs.mkdir()`][]. | 2514 | 2518 |
| 2515 | 2519 | See also: mkdir(2). | @@ -4815,8 +4819,8 @@ added: v10.0.0 | 4815 | 4819 | * `mode` {string|integer} Not supported on Windows. **Default:** `0o777`. | 4816 | 4820 | * Returns: {Promise} | 4817 | 4821 |
| 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`. | 4820 | 4824 |
| 4821 | 4825 | The optional `options` argument can be an integer specifying mode (permission | 4822 | 4826 | and sticky bits), or an object with a `mode` property and a `recursive` | |
Collapse file Copy file name to clipboardExpand all lines: lib/fs.js +6-3Lines changed: 6 additions & 3 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -855,10 +855,13 @@ function mkdirSync(path, options) { | 855 | 855 | throw new ERR_INVALID_ARG_TYPE('recursive', 'boolean', recursive); | 856 | 856 |
| 857 | 857 | 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); | 861 | 861 | handleErrorFromBinding(ctx); | | 862 | + if (recursive) { | | 863 | + return result; | | 864 | + } | 862 | 865 | } | 863 | 866 |
| 864 | 867 | function readdir(path, options, callback) { | |
Collapse file Copy file name to clipboardExpand all lines: src/inspector_profiler.cc +3-3Lines changed: 3 additions & 3 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -107,9 +107,9 @@ void V8ProfilerConnection::V8ProfilerSessionDelegate::SendMessageToFrontend( | 107 | 107 | } | 108 | 108 |
| 109 | 109 | 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); | 113 | 113 | if (ret < 0 && ret != UV_EEXIST) { | 114 | 114 | char err_buf[128]; | 115 | 115 | uv_err_name_r(ret, err_buf, sizeof(err_buf)); | |
Collapse file Copy file name to clipboardExpand all lines: src/node_file-inl.h +6Lines changed: 6 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -21,6 +21,12 @@ void FSContinuationData::PushPath(const std::string& path) { | 21 | 21 | paths_.push_back(path); | 22 | 22 | } | 23 | 23 |
| | 24 | +void FSContinuationData::MaybeSetFirstPath(const std::string& path) { | | 25 | + if (first_path_.empty()) { | | 26 | + first_path_ = path; | | 27 | + } | | 28 | +} | | 29 | + | 24 | 30 | std::string FSContinuationData::PopPath() { | 25 | 31 | CHECK_GT(paths_.size(), 0); | 26 | 32 | std::string path = std::move(paths_.back()); | |
Collapse file Copy file name to clipboardExpand all lines: src/node_file.cc +93-12Lines changed: 93 additions & 12 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -586,6 +586,43 @@ void AfterOpenFileHandle(uv_fs_t* req) { | 586 | 586 | } | 587 | 587 | } | 588 | 588 |
| | 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 | + | 589 | 626 | void AfterStringPath(uv_fs_t* req) { | 590 | 627 | FSReqBase* req_wrap = FSReqBase::from_req(req); | 591 | 628 | FSReqAfterScope after(req_wrap, req); | @@ -1213,18 +1250,25 @@ int MKDirpSync(uv_loop_t* loop, | 1213 | 1250 | const std::string& path, | 1214 | 1251 | int mode, | 1215 | 1252 | 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 | + } | 1218 | 1261 |
| 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(); | 1221 | 1264 | int err = uv_fs_mkdir(loop, req, next_path.c_str(), mode, nullptr); | 1222 | 1265 | while (true) { | 1223 | 1266 | switch (err) { | 1224 | 1267 | // Note: uv_fs_req_cleanup in terminal paths will be called by | 1225 | 1268 | // ~FSReqWrapSync(): | 1226 | 1269 | 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) { | 1228 | 1272 | return 0; | 1229 | 1273 | } | 1230 | 1274 | break; | @@ -1237,9 +1281,9 @@ int MKDirpSync(uv_loop_t* loop, | 1237 | 1281 | std::string dirname = next_path.substr(0, | 1238 | 1282 | next_path.find_last_of(kPathSeparator)); | 1239 | 1283 | 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) { | 1243 | 1287 | err = UV_EEXIST; | 1244 | 1288 | continue; | 1245 | 1289 | } | @@ -1251,7 +1295,8 @@ int MKDirpSync(uv_loop_t* loop, | 1251 | 1295 | err = uv_fs_stat(loop, req, next_path.c_str(), nullptr); | 1252 | 1296 | if (err == 0 && !S_ISDIR(req->statbuf.st_mode)) { | 1253 | 1297 | 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) { | 1255 | 1300 | return UV_ENOTDIR; | 1256 | 1301 | } | 1257 | 1302 | return UV_EEXIST; | @@ -1296,8 +1341,10 @@ int MKDirpAsync(uv_loop_t* loop, | 1296 | 1341 | // FSReqAfterScope::~FSReqAfterScope() | 1297 | 1342 | case 0: { | 1298 | 1343 | if (req_wrap->continuation_data()->paths().size() == 0) { | | 1344 | + req_wrap->continuation_data()->MaybeSetFirstPath(path); | 1299 | 1345 | req_wrap->continuation_data()->Done(0); | 1300 | 1346 | } else { | | 1347 | + req_wrap->continuation_data()->MaybeSetFirstPath(path); | 1301 | 1348 | uv_fs_req_cleanup(req); | 1302 | 1349 | MKDirpAsync(loop, req, path.c_str(), | 1303 | 1350 | req_wrap->continuation_data()->mode(), nullptr); | @@ -1360,6 +1407,25 @@ int MKDirpAsync(uv_loop_t* loop, | 1360 | 1407 | return err; | 1361 | 1408 | } | 1362 | 1409 |
| | 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 | + | 1363 | 1429 | static void MKDir(const FunctionCallbackInfo<Value>& args) { | 1364 | 1430 | Environment* env = Environment::GetCurrent(args); | 1365 | 1431 |
| @@ -1378,14 +1444,29 @@ static void MKDir(const FunctionCallbackInfo<Value>& args) { | 1378 | 1444 | FSReqBase* req_wrap_async = GetReqWrap(env, args[3]); | 1379 | 1445 | if (req_wrap_async != nullptr) { // mkdir(path, mode, req) | 1380 | 1446 | 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); | 1382 | 1449 | } else { // mkdir(path, mode, undefined, ctx) | 1383 | 1450 | CHECK_EQ(argc, 5); | 1384 | 1451 | FSReqWrapSync req_wrap_sync; | 1385 | 1452 | FS_SYNC_TRACE_BEGIN(mkdir); | 1386 | 1453 | 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 | + } | 1389 | 1470 | } else { | 1390 | 1471 | SyncCall(env, args[4], &req_wrap_sync, "mkdir", | 1391 | 1472 | uv_fs_mkdir, *path, mode); | |
Collapse file Copy file name to clipboardExpand all lines: src/node_file.h +14Lines changed: 14 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -19,10 +19,13 @@ class FSContinuationData : public MemoryRetainer { | 19 | 19 | inline void PushPath(std::string&& path); | 20 | 20 | inline void PushPath(const std::string& path); | 21 | 21 | inline std::string PopPath(); | | 22 | + // Used by mkdirp to track the first path created: | | 23 | + inline void MaybeSetFirstPath(const std::string& path); | 22 | 24 | inline void Done(int result); | 23 | 25 |
| 24 | 26 | int mode() const { return mode_; } | 25 | 27 | const std::vector<std::string>& paths() const { return paths_; } | | 28 | + const std::string& first_path() const { return first_path_; } | 26 | 29 |
| 27 | 30 | void MemoryInfo(MemoryTracker* tracker) const override; | 28 | 31 | SET_MEMORY_INFO_NAME(FSContinuationData) | @@ -33,6 +36,7 @@ class FSContinuationData : public MemoryRetainer { | 33 | 36 | uv_fs_t* req_; | 34 | 37 | int mode_; | 35 | 38 | std::vector<std::string> paths_; | | 39 | + std::string first_path_; | 36 | 40 | }; | 37 | 41 |
| 38 | 42 | class FSReqBase : public ReqWrap<uv_fs_t> { | @@ -306,8 +310,18 @@ class FSReqWrapSync { | 306 | 310 | ~FSReqWrapSync() { uv_fs_req_cleanup(&req); } | 307 | 311 | uv_fs_t req; | 308 | 312 |
| | 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 | + | 309 | 320 | FSReqWrapSync(const FSReqWrapSync&) = delete; | 310 | 321 | FSReqWrapSync& operator=(const FSReqWrapSync&) = delete; | | 322 | + | | 323 | + private: | | 324 | + std::unique_ptr<FSContinuationData> continuation_data_; | 311 | 325 | }; | 312 | 326 |
| 313 | 327 | // TODO(addaleax): Currently, callers check the return value and assume | |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-fs-mkdir.js +94Lines changed: 94 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -245,6 +245,100 @@ if (common.isMainThread && (common.isLinux || common.isOSX)) { | 245 | 245 | }); | 246 | 246 | } | 247 | 247 |
| | 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 | + | 248 | 342 | // Keep the event loop alive so the async mkdir() requests | 249 | 343 | // have a chance to run (since they don't ref the event loop). | 250 | 344 | process.nextTick(() => {}); |
|