Skip to content

Commit 6f883e7

Browse files
author
shisama
committed
fs: implement fs.rmdir recursive
Added recursive option into fs.rmdir and fs.rmdirSync to delete a forder with sub folders or files.
1 parent 1f6c4ba commit 6f883e7

6 files changed

Lines changed: 304 additions & 16 deletions

File tree

‎doc/api/fs.md‎

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2880,7 +2880,7 @@ changes:
28802880

28812881
Synchronous rename(2). Returns `undefined`.
28822882

2883-
## fs.rmdir(path, callback)
2883+
## fs.rmdir(path[, options], callback)
28842884
<!-- YAML
28852885
added: v0.0.2
28862886
changes:
@@ -2899,6 +2899,8 @@ changes:
28992899
-->
29002900

29012901
* `path` {string|Buffer|URL}
2902+
* `options` {Object}
2903+
* `recursive` {boolean} **Default:** `false`
29022904
* `callback` {Function}
29032905
* `err` {Error}
29042906

@@ -2908,7 +2910,17 @@ to the completion callback.
29082910
Using `fs.rmdir()` on a file (not a directory) results in an `ENOENT` error on
29092911
Windows and an `ENOTDIR` error on POSIX.
29102912

2911-
## fs.rmdirSync(path)
2913+
The optional `options` argument can be an object with a `recursive` property
2914+
indicating whether a folder with sub folders or files should be deleted.
2915+
2916+
```js
2917+
// Delete /path/to/foler, regardless of whether sub folders or files exist.
2918+
fs.rmdir('/path/to/folder', { recursive: true }, (err) => {
2919+
if (err) throw err;
2920+
});
2921+
```
2922+
2923+
## fs.rmdirSync(path[, options])
29122924
<!-- YAML
29132925
added: v0.1.21
29142926
changes:
@@ -2919,6 +2931,8 @@ changes:
29192931
-->
29202932

29212933
* `path` {string|Buffer|URL}
2934+
* `options` {Object}
2935+
* `recursive` {boolean} **Default:** `false`
29222936

29232937
Synchronous rmdir(2). Returns `undefined`.
29242938

@@ -4336,12 +4350,14 @@ added: v10.0.0
43364350
Renames `oldPath` to `newPath` and resolves the `Promise` with no arguments
43374351
upon success.
43384352

4339-
### fsPromises.rmdir(path)
4353+
### fsPromises.rmdir(path[, options])
43404354
<!-- YAML
43414355
added: v10.0.0
43424356
-->
43434357

43444358
* `path` {string|Buffer|URL}
4359+
* `options` {Object}
4360+
* `recursive` {boolean} **Default:** `false`
43454361
* Returns: {Promise}
43464362

43474363
Removes the directory identified by `path` then resolves the `Promise` with
@@ -4835,7 +4851,7 @@ the file contents.
48354851
[`fs.readFile()`]: #fs_fs_readfile_path_options_callback
48364852
[`fs.readFileSync()`]: #fs_fs_readfilesync_path_options
48374853
[`fs.realpath()`]: #fs_fs_realpath_path_options_callback
4838-
[`fs.rmdir()`]: #fs_fs_rmdir_path_callback
4854+
[`fs.rmdir()`]: #fs_fs_rmdir_path_options_callback
48394855
[`fs.stat()`]: #fs_fs_stat_path_options_callback
48404856
[`fs.symlink()`]: #fs_fs_symlink_target_path_type_callback
48414857
[`fs.utimes()`]: #fs_fs_utimes_path_atime_mtime_callback

‎lib/fs.js‎

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -686,20 +686,34 @@ function ftruncateSync(fd, len = 0) {
686686
handleErrorFromBinding(ctx);
687687
}
688688

689-
function rmdir(path, callback) {
689+
function rmdir(path, options, callback) {
690+
if (typeof options === 'function') {
691+
callback = options;
692+
options = {};
693+
}
694+
const {
695+
recursive = false
696+
} = options || {};
690697
callback = makeCallback(callback);
691698
path = toPathIfFileURL(path);
692699
validatePath(path);
700+
if (typeof recursive !== 'boolean')
701+
throw new ERR_INVALID_ARG_TYPE('recursive', 'boolean', recursive);
693702
const req = new FSReqCallback();
694703
req.oncomplete = callback;
695-
binding.rmdir(pathModule.toNamespacedPath(path), req);
704+
binding.rmdir(pathModule.toNamespacedPath(path), recursive, req);
696705
}
697706

698-
function rmdirSync(path) {
707+
function rmdirSync(path, options) {
699708
path = toPathIfFileURL(path);
709+
const {
710+
recursive = false
711+
} = options || {};
700712
validatePath(path);
713+
if (typeof recursive !== 'boolean')
714+
throw new ERR_INVALID_ARG_TYPE('recursive', 'boolean', recursive);
701715
const ctx = { path };
702-
binding.rmdir(pathModule.toNamespacedPath(path), undefined, ctx);
716+
binding.rmdir(pathModule.toNamespacedPath(path), recursive, undefined, ctx);
703717
handleErrorFromBinding(ctx);
704718
}
705719

‎lib/internal/fs/promises.js‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,10 +278,17 @@ async function ftruncate(handle, len = 0) {
278278
return binding.ftruncate(handle.fd, len, kUsePromises);
279279
}
280280

281-
async function rmdir(path) {
281+
async function rmdir(path, options) {
282+
const {
283+
recursive = false
284+
} = options || {};
282285
path = toPathIfFileURL(path);
283286
validatePath(path);
284-
return binding.rmdir(pathModule.toNamespacedPath(path), kUsePromises);
287+
if (typeof recursive !== 'boolean')
288+
throw new ERR_INVALID_ARG_TYPE('recursive', 'boolean', recursive);
289+
return binding.rmdir(pathModule.toNamespacedPath(path),
290+
recursive,
291+
kUsePromises);
285292
}
286293

287294
async function fdatasync(handle) {

‎src/node_file.cc‎

Lines changed: 131 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1234,25 +1234,150 @@ static void Unlink(const FunctionCallbackInfo<Value>& args) {
12341234
}
12351235
}
12361236

1237+
int RMDirrSync(uv_loop_t* loop, uv_fs_t* req, const std::string& path,
1238+
uv_fs_cb cb = nullptr) {
1239+
FSContinuationData continuation_data(req, 0, cb);
1240+
continuation_data.PushPath(std::move(path));
1241+
1242+
while (continuation_data.paths.size() > 0) {
1243+
std::string next_path = continuation_data.PopPath();
1244+
int err = uv_fs_rmdir(loop, req, next_path.c_str(), nullptr);
1245+
switch (err) {
1246+
case 0:
1247+
if (continuation_data.paths.size() == 0) {
1248+
return 0;
1249+
}
1250+
break;
1251+
case UV_ENOTEMPTY: {
1252+
uv_fs_scandir(loop, req, next_path.c_str(), 0, nullptr);
1253+
1254+
uv_dirent_t entry;
1255+
uv_fs_scandir_next(req, &entry);
1256+
1257+
std::string dirname(next_path + '/' + entry.name);
1258+
if (next_path != dirname) {
1259+
continuation_data.PushPath(std::move(next_path));
1260+
continuation_data.PushPath(std::move(dirname));
1261+
} else if (continuation_data.paths.size() == 0) {
1262+
err = UV_EEXIST;
1263+
}
1264+
break;
1265+
}
1266+
case UV_ENOTDIR: {
1267+
uv_fs_unlink(loop, req, next_path.c_str(), nullptr);
1268+
break;
1269+
}
1270+
case UV_EPERM: {
1271+
return err;
1272+
}
1273+
default:
1274+
uv_fs_req_cleanup(req);
1275+
break;
1276+
}
1277+
uv_fs_req_cleanup(req);
1278+
}
1279+
1280+
return 0;
1281+
}
1282+
1283+
int RMDirrAsync(uv_loop_t* loop,
1284+
uv_fs_t* req,
1285+
const char* path,
1286+
uv_fs_cb cb) {
1287+
FSReqBase* req_wrap = FSReqBase::from_req(req);
1288+
// on the first iteration of algorithm, stash state information.
1289+
if (req_wrap->continuation_data == nullptr) {
1290+
req_wrap->continuation_data = std::unique_ptr<FSContinuationData>{
1291+
new FSContinuationData(req, 0, cb)};
1292+
req_wrap->continuation_data->PushPath(std::move(path));
1293+
}
1294+
1295+
std::string next_path = req_wrap->continuation_data->PopPath();
1296+
int err = uv_fs_rmdir(loop, req, next_path.c_str(),
1297+
uv_fs_callback_t{[](uv_fs_t* req) {
1298+
FSReqBase* req_wrap = FSReqBase::from_req(req);
1299+
Environment* env = req_wrap->env();
1300+
uv_loop_t* loop = env->event_loop();
1301+
std::string path = req->path;
1302+
int err = req->result;
1303+
switch (err) {
1304+
case 0: {
1305+
if (req_wrap->continuation_data->paths.size() == 0) {
1306+
req_wrap->continuation_data->Done(0);
1307+
} else {
1308+
uv_fs_req_cleanup(req);
1309+
RMDirrAsync(loop, req, path.c_str(), nullptr);
1310+
}
1311+
break;
1312+
}
1313+
case UV_ENOTEMPTY: {
1314+
uv_fs_req_cleanup(req);
1315+
uv_fs_scandir(loop, req, path.c_str(), 0, nullptr);
1316+
1317+
uv_dirent_t entry;
1318+
uv_fs_scandir_next(req, &entry);
1319+
1320+
std::string dirname(path + '/' + entry.name);
1321+
if (dirname != path) {
1322+
req_wrap->continuation_data->PushPath(std::move(path));
1323+
req_wrap->continuation_data->PushPath(std::move(dirname));
1324+
} else if (req_wrap->continuation_data->paths.size() == 0) {
1325+
err = UV_EEXIST;
1326+
break;
1327+
}
1328+
uv_fs_req_cleanup(req);
1329+
RMDirrAsync(loop, req, path.c_str(), nullptr);
1330+
break;
1331+
}
1332+
case UV_ENOTDIR: {
1333+
uv_fs_req_cleanup(req);
1334+
uv_fs_unlink(loop, req, path.c_str(), nullptr);
1335+
RMDirrAsync(loop, req, path.c_str(), nullptr);
1336+
break;
1337+
}
1338+
case UV_EPERM: {
1339+
req_wrap->continuation_data->Done(err);
1340+
break;
1341+
}
1342+
default:
1343+
if (req_wrap->continuation_data->paths.size() > 0) {
1344+
uv_fs_req_cleanup(req);
1345+
RMDirrAsync(loop, req, path.c_str(), nullptr);
1346+
}
1347+
break;
1348+
}
1349+
}});
1350+
1351+
return err;
1352+
}
1353+
12371354
static void RMDir(const FunctionCallbackInfo<Value>& args) {
12381355
Environment* env = Environment::GetCurrent(args);
12391356

12401357
const int argc = args.Length();
1241-
CHECK_GE(argc, 2);
1358+
CHECK_GE(argc, 3);
12421359

12431360
BufferValue path(env->isolate(), args[0]);
12441361
CHECK_NOT_NULL(*path);
12451362

1246-
FSReqBase* req_wrap_async = GetReqWrap(env, args[1]); // rmdir(path, req)
1363+
CHECK(args[1]->IsBoolean());
1364+
bool recursive = args[1]->IsTrue();
1365+
1366+
FSReqBase* req_wrap_async = GetReqWrap(env, args[2]); // rmdir(path, req)
12471367
if (req_wrap_async != nullptr) {
12481368
AsyncCall(env, req_wrap_async, args, "rmdir", UTF8, AfterNoArgs,
1249-
uv_fs_rmdir, *path);
1369+
recursive ? RMDirrAsync : uv_fs_rmdir, *path);
12501370
} else { // rmdir(path, undefined, ctx)
1251-
CHECK_EQ(argc, 3);
1371+
CHECK_EQ(argc, 4);
12521372
FSReqWrapSync req_wrap_sync;
12531373
FS_SYNC_TRACE_BEGIN(rmdir);
1254-
SyncCall(env, args[2], &req_wrap_sync, "rmdir",
1255-
uv_fs_rmdir, *path);
1374+
if (recursive) {
1375+
SyncCall(env, args[3], &req_wrap_sync, "rmdir",
1376+
RMDirrSync, *path);
1377+
} else {
1378+
SyncCall(env, args[3], &req_wrap_sync, "rmdir",
1379+
uv_fs_rmdir, *path);
1380+
}
12561381
FS_SYNC_TRACE_END(rmdir);
12571382
}
12581383
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const path = require('path');
6+
const fs = require('fs');
7+
const tmpdir = require('../common/tmpdir');
8+
tmpdir.refresh();
9+
10+
const paramdir = path.join(tmpdir.path, 'dir');
11+
12+
// fs.rmdir - recursive: true
13+
{
14+
const d = path.join(tmpdir.path, 'dir', 'test_rmdir');
15+
// Make sure the directory does not exist
16+
assert(!fs.existsSync(d));
17+
// Create the directory now
18+
fs.mkdirSync(d, { recursive: true });
19+
assert(fs.existsSync(d));
20+
// Create files
21+
fs.writeFileSync(path.join(d, 'test.txt'), 'test');
22+
23+
fs.rmdir(paramdir, { recursive: true }, common.mustCall((err) => {
24+
assert.ifError(err);
25+
assert(!fs.existsSync(d));
26+
}));
27+
}
28+
29+
// fs.rmdirSync - recursive: true
30+
{
31+
const d = path.join(tmpdir.path, 'dir', 'test_rmdirSync');
32+
// Make sure the directory does not exist
33+
assert(!fs.existsSync(d));
34+
// Create the directory now
35+
fs.mkdirSync(d, { recursive: true });
36+
assert(fs.existsSync(d));
37+
// Create files
38+
fs.writeFileSync(path.join(d, 'test.txt'), 'test');
39+
40+
fs.rmdirSync(paramdir, { recursive: true });
41+
assert(!fs.existsSync(d));
42+
}
43+
44+
// fs.promises.rmdir - recursive: true
45+
{
46+
const d = path.join(tmpdir.path, 'dir', 'test_promises_rmdir');
47+
// Make sure the directory does not exist
48+
assert(!fs.existsSync(d));
49+
// Create the directory now
50+
fs.mkdirSync(d, { recursive: true });
51+
assert(fs.existsSync(d));
52+
// Create files
53+
fs.writeFileSync(path.join(d, 'test.txt'), 'test');
54+
55+
async () => {
56+
await fs.promises.rmdir(paramdir, { recursive: true });
57+
assert(!fs.existsSync(d));
58+
};
59+
}
60+
61+
// recursive: false
62+
{
63+
const d = path.join(tmpdir.path, 'dir', 'test_rmdir_recursive_false');
64+
// Make sure the directory does not exist
65+
assert(!fs.existsSync(d));
66+
// Create the directory now
67+
fs.mkdirSync(d, { recursive: true });
68+
assert(fs.existsSync(d));
69+
70+
// fs.rmdir
71+
fs.rmdir(paramdir, { recursive: false }, common.mustCall((err) => {
72+
assert.strictEqual(err.code, 'ENOTEMPTY');
73+
}));
74+
75+
// fs.rmdirSync
76+
common.expectsError(
77+
() => fs.rmdirSync(paramdir, { recursive: false }),
78+
{
79+
code: 'ENOTEMPTY'
80+
}
81+
);
82+
83+
// fs.promises.rmdir
84+
assert.rejects(
85+
fs.promises.rmdir(paramdir, { recursive: false }),
86+
{
87+
code: 'ENOTEMPTY'
88+
}
89+
);
90+
}

0 commit comments

Comments
 (0)