Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
fs: implement fs.rmdir recurisve
Add `clearDir` options into fs.rmdir and fs.rmdirSync to delete a forder
with sub folders or files.
  • Loading branch information
zero1five committed Jun 11, 2019
commit f400dc2f70589b0fb8a1f5a50d407799e9d673e8
14 changes: 10 additions & 4 deletions doc/api/fs.md
Original file line number Diff line number Diff line change
Expand Up @@ -2927,7 +2927,7 @@ changes:

Synchronous rename(2). Returns `undefined`.

## fs.rmdir(path, callback)
## fs.rmdir(path[, options], callback)
<!-- YAML
added: v0.0.2
changes:
Expand All @@ -2946,6 +2946,8 @@ changes:
-->

* `path` {string|Buffer|URL}
* `options` {Object}
* `clearDir` {boolean} delete non-empty folders **Default:** `false
* `callback` {Function}
* `err` {Error}

Expand All @@ -2955,7 +2957,7 @@ to the completion callback.
Using `fs.rmdir()` on a file (not a directory) results in an `ENOENT` error on
Windows and an `ENOTDIR` error on POSIX.

## fs.rmdirSync(path)
## ## fs.rmdirSync(path[, options])
<!-- YAML
added: v0.1.21
changes:
Expand All @@ -2966,6 +2968,8 @@ changes:
-->

* `path` {string|Buffer|URL}
* `options` {Object}
* `clearDir` {boolean} **Default:** `false`

Synchronous rmdir(2). Returns `undefined`.

Expand Down Expand Up @@ -4464,12 +4468,14 @@ added: v10.0.0
Renames `oldPath` to `newPath` and resolves the `Promise` with no arguments
upon success.

### fsPromises.rmdir(path)
### fsPromises.rmdir(path[, options])
<!-- YAML
added: v10.0.0
-->

* `path` {string|Buffer|URL}
* `options` {Object}
* `clearDir` {boolean} **Default:** `false`
* Returns: {Promise}

Removes the directory identified by `path` then resolves the `Promise` with
Expand Down Expand Up @@ -4963,7 +4969,7 @@ the file contents.
[`fs.readdir()`]: #fs_fs_readdir_path_options_callback
[`fs.readdirSync()`]: #fs_fs_readdirsync_path_options
[`fs.realpath()`]: #fs_fs_realpath_path_options_callback
[`fs.rmdir()`]: #fs_fs_rmdir_path_callback
[`fs.rmdir()`]: #fs_fs_rmdir_path_options_callback
[`fs.stat()`]: #fs_fs_stat_path_options_callback
[`fs.symlink()`]: #fs_fs_symlink_target_path_type_callback
[`fs.utimes()`]: #fs_fs_utimes_path_atime_mtime_callback
Expand Down
161 changes: 154 additions & 7 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// 'Software'), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
Expand All @@ -11,7 +11,7 @@
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
Expand Down Expand Up @@ -678,21 +678,168 @@ function ftruncateSync(fd, len = 0) {
handleErrorFromBinding(ctx);
}

function rmdir(path, callback) {
callback = makeCallback(callback);
path = getValidatedPath(path);
// Delte All directory
function rmDirAll(path, callback) {
let n = 0;
let errState = null;

function next(err) {
errState = errState || err;
if (--n === 0)
rmEmptyDir(path, callback);
}

function rmFile(path, isDirectory, callback) {
if (isDirectory) {
return _rmdir(path, callback);
}

unlink(path, (err) => {
if (err) {
if (err.code === 'ENOENT')
return callback(null);
if (err.code === 'EPERM')
return _rmdir(path, err, callback);
// Normally it doesn't equal 'EISDIR'
if (err.code === 'EISDIR')
return _rmdir(path, err, callback);
}
return callback(err);
});
}

function _rmdir(path, originalEr, callback) {
if (typeof originalEr === 'function') {
callback = originalEr;
originalEr = null;
}

// Check if it is empty through error.code
rmdir(path, function(err) {
if (err && (err.code === 'ENOTEMPTY' ||
err.code === 'EEXIST' ||
err.code === 'EPERM'))
_rmkids(path, callback);
else if (err && err.code === 'ENOTDIR')
callback(originalEr);
else
callback(err);
});
}

function _rmkids(path, callback) {
readdir(path, { withFileTypes: true }, (err, files) => {
if (err)
return callback(err);

let n = files.length;
if (n === 0)
return rmEmptyDir(path, callback);

let errState;
files.forEach((dirent) => {
const fp = pathModule.join(path, dirent.name);
rmFile(fp, dirent.isDirectory(), (err) => {
if (errState)
return;
if (err)
return callback(errState = err);
if (--n === 0)
return rmEmptyDir(path, callback);
});
});
});
}

readdir(path, { withFileTypes: true }, (err, files) => {
if (err)
return callback(err);

n = files.length;
if (n === 0)
return rmEmptyDir(path, callback);

files.forEach((dirent) => {
const fp = pathModule.join(path, dirent.name);
rmFile(fp, dirent.isDirectory(), (err) => {
if (err && err.code === 'ENOENT')
err = null;
next(err);
});
});
});
}

// Delete empty directory
function rmEmptyDir(path, callback) {
const req = new FSReqCallback();
req.oncomplete = callback;
req.oncomplete = makeCallback(callback);
binding.rmdir(pathModule.toNamespacedPath(path), req);
}

function rmdirSync(path) {
function rmdir(path, options, callback) {
callback = maybeCallback(callback || options);
options = getOptions(options, { clearDir: false });
path = getValidatedPath(path);

const { clearDir } = options;

if (typeof clearDir !== 'boolean')
throw new ERR_INVALID_ARG_TYPE('clearDir', 'boolean', clearDir);

if (clearDir) {
rmDirAll(path, callback);
} else {
rmEmptyDir(path, callback);
}
}

// Delte All directory sync
function rmDirAllSync(path) {
// Non-directory throws an error directly to user
const files = readdirSync(path, { withFileTypes: true });
const n = files.length;

if (n === 0)
return rmEmptyDirSync(path);

for (let i = 0; i < n; i++) {
const dirent = files[i];
const fp = pathModule.join(path, dirent.name);
if (dirent.isDirectory()) {
rmDirAllSync(fp);
} else {
unlinkSync(fp);
}
}

// Try again or more?
rmDirAllSync(path);
}

// Delte empty directory sync
function rmEmptyDirSync(path) {
const ctx = { path };
binding.rmdir(pathModule.toNamespacedPath(path), undefined, ctx);
handleErrorFromBinding(ctx);
}

function rmdirSync(path, options) {
path = getValidatedPath(path);
options = getOptions(options, { clearDir: false });

const { clearDir } = options;

if (typeof clearDir !== 'boolean')
throw new ERR_INVALID_ARG_TYPE('clearDir', 'boolean', clearDir);

if (clearDir) {
rmDirAllSync(path);
} else {
rmEmptyDirSync(path);
}
}

function fdatasync(fd, callback) {
validateUint32(fd, 'fd');
const req = new FSReqCallback();
Expand Down
19 changes: 18 additions & 1 deletion lib/internal/fs/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ const { kUsePromises } = binding;

const getDirectoryEntriesPromise = promisify(getDirents);

let rmdirPromise;

class FileHandle {
constructor(filehandle) {
this[kHandle] = filehandle;
Expand Down Expand Up @@ -274,8 +276,23 @@ async function ftruncate(handle, len = 0) {
return binding.ftruncate(handle.fd, len, kUsePromises);
}

async function rmdir(path) {
async function rmdir(path, options) {
path = getValidatedPath(path);
options = getOptions(options, { clearDir: false });
const { clearDir } = options;

if (typeof clearDir !== 'boolean')
throw new ERR_INVALID_ARG_TYPE('clearDir', 'boolean', clearDir);

// If implement them in lib, can only import promisify for rmdir.
if (clearDir) {
if (rmdirPromise === undefined) {
const rmdir = require('fs').rmdir;
rmdirPromise = promisify(rmdir);
}
return rmdirPromise(path, options);
}

return binding.rmdir(pathModule.toNamespacedPath(path), kUsePromises);
}

Expand Down
95 changes: 95 additions & 0 deletions test/parallel/test-fs-rmdir-clearDir.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
'use strict';

const common = require('../common');
const assert = require('assert');
const path = require('path');
const fs = require('fs');
const tmpdir = require('../common/tmpdir');

const tmpPath = (dir) => path.join(tmpdir.path, dir);

tmpdir.refresh();

// fs.rmdir - clearDir: true
{
const paramdir = tmpPath('rmdir');
const d = path.join(paramdir, 'test_rmdir');
// Make sure the directory does not exist
assert(!fs.existsSync(d));
// Create the directory now
fs.mkdirSync(d, { recursive: true });
assert(fs.existsSync(d));
// Create files
fs.writeFileSync(path.join(d, 'test.txt'), 'test');

fs.rmdir(paramdir, { clearDir: true }, common.mustCall((err) => {
assert.ifError(err);
assert(!fs.existsSync(d));
}));
}

// fs.rmdirSync - clearDir: true
{
const paramdir = tmpPath('rmdirSync');
const d = path.join(paramdir, 'test_rmdirSync');
// Make sure the directory does not exist
assert(!fs.existsSync(d));
// Create the directory now
fs.mkdirSync(d, { recursive: true });
assert(fs.existsSync(d));
// Create files
fs.writeFileSync(path.join(d, 'test.txt'), 'test');

fs.rmdirSync(paramdir, { clearDir: true });
assert(!fs.existsSync(d));
}

// fs.promises.rmdir - clearDir: true
{
const paramdir = tmpPath('rmdirPromise');
const d = path.join(paramdir, 'test_promises_rmdir');
// Make sure the directory does not exist
assert(!fs.existsSync(d));
// Create the directory now
fs.mkdirSync(d, { recursive: true });
assert(fs.existsSync(d));
// Create files
fs.writeFileSync(path.join(d, 'test.txt'), 'test');

(async () => {
await fs.promises.rmdir(paramdir, { clearDir: true });
assert(!fs.existsSync(d));
})();
}

// clearDir: false
{
const paramdir = tmpPath('options');
const d = path.join(paramdir, 'dir', 'test_rmdir_recursive_false');
// Make sure the directory does not exist
assert(!fs.existsSync(d));
// Create the directory now
fs.mkdirSync(d, { recursive: true });
assert(fs.existsSync(d));

// fs.rmdir
fs.rmdir(paramdir, { clearDir: false }, common.mustCall((err) => {
assert.strictEqual(err.code, 'ENOTEMPTY');
}));

// fs.rmdirSync
common.expectsError(
() => fs.rmdirSync(paramdir, { clearDir: false }),
{
code: 'ENOTEMPTY'
}
);

// fs.promises.rmdir
assert.rejects(
fs.promises.rmdir(paramdir, { clearDir: false }),
{
code: 'ENOTEMPTY'
}
);
}