Skip to content

Commit 9948036

Browse files
committed
fs: remove permissive rmdir recursive
PR-URL: #37216 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Ben Coe <[email protected]> Reviewed-By: Ian Sutherland <[email protected]> Reviewed-By: Rich Trott <[email protected]> Reviewed-By: Michael Dawson <[email protected]>
1 parent 3cc9aec commit 9948036

10 files changed

+167
-43
lines changed

‎doc/api/fs.md‎

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,16 @@ Renames `oldPath` to `newPath`.
10481048
<!-- YAML
10491049
added: v10.0.0
10501050
changes:
1051+
- version: REPLACEME
1052+
pr-url: https://github.com/nodejs/node/pull/37216
1053+
description: "Using `fsPromises.rmdir(path,{recursive: true })` on a `path`
1054+
that is a file is no longer permitted and results in an
1055+
`ENOENT` error on Windows and an `ENOTDIR` error on POSIX."
1056+
- version: REPLACEME
1057+
pr-url: https://github.com/nodejs/node/pull/37216
1058+
description: "Using `fsPromises.rmdir(path,{recursive: true })` on a `path`
1059+
that does not exist is no longer permitted and results in a
1060+
`ENOENT` error."
10511061
- version: REPLACEME
10521062
pr-url: https://github.com/nodejs/node/pull/37302
10531063
description: The `recursive` option is deprecated, using it triggers a
@@ -1075,8 +1085,8 @@ changes:
10751085
represents the number of retries. This option is ignored if the `recursive`
10761086
option is not `true`. **Default:** `0`.
10771087
* `recursive`{boolean} If `true`, perform a recursive directory removal. In
1078-
recursive mode, errors are not reported if `path` does not exist, and
1079-
operations are retried on failure. **Default:** `false`. **Deprecated**.
1088+
recursive mode, operations are retried on failure. **Default:** `false`.
1089+
**Deprecated**.
10801090
* `retryDelay`{integer} The amount of time in milliseconds to wait between
10811091
retries. This option is ignored if the `recursive` option is not `true`.
10821092
**Default:** `100`.
@@ -1088,10 +1098,8 @@ Using `fsPromises.rmdir()` on a file (not a directory) results in the
10881098
promise being rejected with an `ENOENT` error on Windows and an `ENOTDIR`
10891099
error on POSIX.
10901100
1091-
Setting `recursive` to `true` results in behavior similar to the Unix command
1092-
`rm -rf`: an error will not be raised for paths that do not exist, and paths
1093-
that represent files will be deleted. The `recursive` option is deprecated,
1094-
`ENOTDIR` and `ENOENT` will be thrown in the future.
1101+
To get a behavior similar to the `rm -rf` Unix command, use
1102+
[`fsPromises.rm()`][] with options `{recursive: true, force: true }`.
10951103
10961104
### `fsPromises.rm(path[, options])`
10971105
<!-- YAML
@@ -3146,6 +3154,16 @@ rename('oldFile.txt', 'newFile.txt', (err) =>{
31463154
<!--YAML
31473155
added: v0.0.2
31483156
changes:
3157+
- version:REPLACEME
3158+
pr-url: https://github.com/nodejs/node/pull/37216
3159+
description:"Using `fs.rmdir(path,{recursive: true })` on a `path` that is
3160+
a file is no longer permitted and results in an `ENOENT` error
3161+
on Windows and an `ENOTDIR` error on POSIX."
3162+
- version:REPLACEME
3163+
pr-url: https://github.com/nodejs/node/pull/37216
3164+
description:"Using `fs.rmdir(path,{recursive: true })` on a `path` that
3165+
does not exist is no longer permitted and results in a `ENOENT`
3166+
error."
31493167
- version:REPLACEME
31503168
pr-url: https://github.com/nodejs/node/pull/37302
31513169
description: The `recursive` option is deprecated, using it triggers a
@@ -3185,8 +3203,8 @@ changes:
31853203
represents the number ofretries. This option is ignored if the `recursive`
31863204
option is not `true`. **Default:**`0`.
31873205
*`recursive`{boolean} If `true`, perform a recursive directory removal. In
3188-
recursive mode, errors are not reported if`path` does not exist, and
3189-
operations are retried on failure. **Default:**`false`. **Deprecated**.
3206+
recursive mode, operations are retried on failure. **Default:**`false`.
3207+
**Deprecated**.
31903208
*`retryDelay`{integer} The amount of time in milliseconds to wait between
31913209
retries. This option is ignored if the `recursive` option is not `true`.
31923210
**Default:**`100`.
@@ -3199,10 +3217,8 @@ to the completion callback.
31993217
Using `fs.rmdir()` on a file (not a directory) results in an `ENOENT` error on
32003218
Windows and an `ENOTDIR` error on POSIX.
32013219

3202-
Setting `recursive` to `true` results in behavior similar to the Unix command
3203-
`rm -rf`: an error will not be raised for paths that do not exist, and paths
3204-
that represent files will be deleted. The`recursive` option is deprecated,
3205-
`ENOTDIR` and `ENOENT` will be thrown in the future.
3220+
To get a behavior similar to the `rm -rf` Unix command, use [`fs.rm()`][]
3221+
with options `{recursive: true, force: true }`.
32063222

32073223
### `fs.rm(path[, options], callback)`
32083224
<!--YAML
@@ -4777,6 +4793,16 @@ See the POSIX rename(2) documentation for more details.
47774793
<!-- YAML
47784794
added: v0.1.21
47794795
changes:
4796+
- version: REPLACEME
4797+
pr-url: https://github.com/nodejs/node/pull/37216
4798+
description: "Using `fs.rmdirSync(path,{recursive:true })` on a `path`
4799+
that is a file is no longer permitted and results in an
4800+
`ENOENT` error on Windows and an `ENOTDIR` error on POSIX."
4801+
- version: REPLACEME
4802+
pr-url: https://github.com/nodejs/node/pull/37216
4803+
description: "Using `fs.rmdirSync(path,{recursive:true })` on a `path`
4804+
that does not exist is no longer permitted and results in a
4805+
`ENOENT` error."
47804806
- version: REPLACEME
47814807
pr-url: https://github.com/nodejs/node/pull/37302
47824808
description: The `recursive` option is deprecated, using it triggers a
@@ -4808,8 +4834,8 @@ changes:
48084834
represents the number of retries. This option is ignored if the `recursive`
48094835
option is not `true`. **Default:** `0`.
48104836
* `recursive`{boolean} If `true`, perform a recursive directory removal. In
4811-
recursive mode, errors are not reported if `path` does not exist, and
4812-
operations are retried on failure. **Default:** `false`. **Deprecated**.
4837+
recursive mode, operations are retried on failure. **Default:** `false`.
4838+
**Deprecated**.
48134839
* `retryDelay`{integer} The amount of time in milliseconds to wait between
48144840
retries. This option is ignored if the `recursive` option is not `true`.
48154841
**Default:** `100`.
@@ -4819,10 +4845,8 @@ Synchronous rmdir(2). Returns `undefined`.
48194845
Using `fs.rmdirSync()` on a file (not a directory) results in an `ENOENT` error
48204846
on Windows and an `ENOTDIR` error on POSIX.
48214847
4822-
Setting `recursive` to `true` results in behavior similar to the Unix command
4823-
`rm -rf`: an error will not be raised for paths that do not exist, and paths
4824-
that represent files will be deleted. The `recursive` option is deprecated,
4825-
`ENOTDIR` and `ENOENT` will be thrown in the future.
4848+
To get a behavior similar to the `rm -rf` Unix command, use [`fs.rmSync()`][]
4849+
with options `{recursive:true, force:true }`.
48264850
48274851
### `fs.rmSync(path[, options])`
48284852
<!-- YAML
@@ -6670,6 +6694,8 @@ the file contents.
66706694
[`fs.readdirSync()`]: #fs_fs_readdirsync_path_options
66716695
[`fs.readv()`]: #fs_fs_readv_fd_buffers_position_callback
66726696
[`fs.realpath()`]: #fs_fs_realpath_path_options_callback
6697+
[`fs.rm()`]: #fs_fs_rm_path_options_callback
6698+
[`fs.rmSync()`]: #fs_fs_rmsync_path_options
66736699
[`fs.rmdir()`]: #fs_fs_rmdir_path_options_callback
66746700
[`fs.stat()`]: #fs_fs_stat_path_options_callback
66756701
[`fs.symlink()`]: #fs_fs_symlink_target_path_type_callback
@@ -6681,6 +6707,7 @@ the file contents.
66816707
[`fs.writev()`]: #fs_fs_writev_fd_buffers_position_callback
66826708
[`fsPromises.open()`]: #fs_fspromises_open_path_flags_mode
66836709
[`fsPromises.opendir()`]: #fs_fspromises_opendir_path_options
6710+
[`fsPromises.rm()`]: #fs_fspromises_rm_path_options
66846711
[`fsPromises.utimes()`]: #fs_fspromises_utimes_path_atime_mtime
66856712
[`inotify(7)`]: https://man7.org/linux/man-pages/man7/inotify.7.html
66866713
[`kqueue(2)`]: https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2

‎lib/fs.js‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -898,15 +898,20 @@ function rmdir(path, options, callback){
898898
emitRecursiveRmdirWarning();
899899
validateRmOptions(
900900
path,
901-
{ ...options,force: true},
901+
{ ...options,force: false},
902902
true,
903903
(err,options)=>{
904+
if(err===false){
905+
constreq=newFSReqCallback();
906+
req.oncomplete=callback;
907+
returnbinding.rmdir(path,req);
908+
}
904909
if(err){
905910
returncallback(err);
906911
}
907912

908913
lazyLoadRimraf();
909-
returnrimraf(path,options,callback);
914+
rimraf(path,options,callback);
910915
});
911916
}else{
912917
validateRmdirOptions(options);
@@ -921,12 +926,15 @@ function rmdirSync(path, options){
921926

922927
if(options?.recursive){
923928
emitRecursiveRmdirWarning();
924-
options=validateRmOptionsSync(path,{ ...options,force: true},true);
925-
lazyLoadRimraf();
926-
returnrimrafSync(pathModule.toNamespacedPath(path),options);
929+
options=validateRmOptionsSync(path,{ ...options,force: false},true);
930+
if(options!==false){
931+
lazyLoadRimraf();
932+
returnrimrafSync(pathModule.toNamespacedPath(path),options);
933+
}
934+
}else{
935+
validateRmdirOptions(options);
927936
}
928937

929-
validateRmdirOptions(options);
930938
constctx={ path };
931939
binding.rmdir(pathModule.toNamespacedPath(path),undefined,ctx);
932940
returnhandleErrorFromBinding(ctx);

‎lib/internal/fs/promises.js‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,10 @@ async function rmdir(path, options){
505505

506506
if(options.recursive){
507507
emitRecursiveRmdirWarning();
508-
returnrimrafPromises(path,options);
508+
conststats=awaitstat(path);
509+
if(stats.isDirectory()){
510+
returnrimrafPromises(path,options);
511+
}
509512
}
510513

511514
returnbinding.rmdir(path,kUsePromises);

‎lib/internal/fs/utils.js‎

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -698,39 +698,47 @@ const defaultRmdirOptions ={
698698
recursive: false,
699699
};
700700

701-
constvalidateRmOptions=hideStackFrames((path,options,warn,callback)=>{
701+
constvalidateRmOptions=hideStackFrames((path,options,expectDir,cb)=>{
702702
options=validateRmdirOptions(options,defaultRmOptions);
703703
validateBoolean(options.force,'options.force');
704704

705705
lazyLoadFs().stat(path,(err,stats)=>{
706706
if(err){
707707
if(options.force&&err.code==='ENOENT'){
708-
returncallback(null,options);
708+
returncb(null,options);
709709
}
710-
returncallback(err,options);
710+
returncb(err,options);
711+
}
712+
713+
if(expectDir&&!stats.isDirectory()){
714+
returncb(false);
711715
}
712716

713717
if(stats.isDirectory()&&!options.recursive){
714-
returncallback(newERR_FS_EISDIR({
718+
returncb(newERR_FS_EISDIR({
715719
code: 'EISDIR',
716720
message: 'is a directory',
717721
path,
718722
syscall: 'rm',
719723
errno: EISDIR
720724
}));
721725
}
722-
returncallback(null,options);
726+
returncb(null,options);
723727
});
724728
});
725729

726-
constvalidateRmOptionsSync=hideStackFrames((path,options,warn)=>{
730+
constvalidateRmOptionsSync=hideStackFrames((path,options,expectDir)=>{
727731
options=validateRmdirOptions(options,defaultRmOptions);
728732
validateBoolean(options.force,'options.force');
729733

730-
if(!options.force||warn||!options.recursive){
734+
if(!options.force||expectDir||!options.recursive){
731735
constisDirectory=lazyLoadFs()
732736
.statSync(path,{throwIfNoEntry: !options.force})?.isDirectory();
733737

738+
if(expectDir&&!isDirectory){
739+
returnfalse;
740+
}
741+
734742
if(isDirectory&&!options.recursive){
735743
thrownewERR_FS_EISDIR({
736744
code: 'EISDIR',

‎test/parallel/test-fs-rmdir-recursive-sync-warns-not-found.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22
constcommon=require('../common');
33
consttmpdir=require('../common/tmpdir');
4+
constassert=require('assert');
45
constfs=require('fs');
56
constpath=require('path');
67

@@ -14,5 +15,9 @@ tmpdir.refresh();
1415
'will be removed. Use fs.rm(path,{recursive: true }) instead',
1516
'DEP0147'
1617
);
17-
fs.rmdirSync(path.join(tmpdir.path,'noexist.txt'),{recursive: true});
18+
assert.throws(
19+
()=>fs.rmdirSync(path.join(tmpdir.path,'noexist.txt'),
20+
{recursive: true}),
21+
{code: 'ENOENT'}
22+
);
1823
}
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
'use strict';
22
constcommon=require('../common');
33
consttmpdir=require('../common/tmpdir');
4+
constassert=require('assert');
45
constfs=require('fs');
56
constpath=require('path');
67

78
tmpdir.refresh();
89

910
{
10-
// Should warn when trying to delete a file
1111
common.expectWarning(
1212
'DeprecationWarning',
1313
'In future versions of Node.js, fs.rmdir(path,{recursive: true }) '+
@@ -16,5 +16,8 @@ tmpdir.refresh();
1616
);
1717
constfilePath=path.join(tmpdir.path,'rmdir-recursive.txt');
1818
fs.writeFileSync(filePath,'');
19-
fs.rmdirSync(filePath,{recursive: true});
19+
assert.throws(
20+
()=>fs.rmdirSync(filePath,{recursive: true}),
21+
{code: common.isWindows ? 'ENOENT' : 'ENOTDIR'}
22+
);
2023
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
'use strict';
2+
constcommon=require('../common');
3+
consttmpdir=require('../common/tmpdir');
4+
constassert=require('assert');
5+
constfs=require('fs');
6+
constpath=require('path');
7+
8+
tmpdir.refresh();
9+
10+
{
11+
assert.throws(
12+
()=>
13+
fs.rmdirSync(path.join(tmpdir.path,'noexist.txt'),{recursive: true}),
14+
{
15+
code: 'ENOENT',
16+
}
17+
);
18+
}
19+
{
20+
fs.rmdir(
21+
path.join(tmpdir.path,'noexist.txt'),
22+
{recursive: true},
23+
common.mustCall((err)=>{
24+
assert.strictEqual(err.code,'ENOENT');
25+
})
26+
);
27+
}
28+
{
29+
assert.rejects(
30+
()=>fs.promises.rmdir(path.join(tmpdir.path,'noexist.txt'),
31+
{recursive: true}),
32+
{
33+
code: 'ENOENT',
34+
}
35+
).then(common.mustCall());
36+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict';
2+
constcommon=require('../common');
3+
consttmpdir=require('../common/tmpdir');
4+
constassert=require('assert');
5+
constfs=require('fs');
6+
constpath=require('path');
7+
8+
tmpdir.refresh();
9+
10+
constcode=common.isWindows ? 'ENOENT' : 'ENOTDIR';
11+
12+
{
13+
constfilePath=path.join(tmpdir.path,'rmdir-recursive.txt');
14+
fs.writeFileSync(filePath,'');
15+
assert.throws(()=>fs.rmdirSync(filePath,{recursive: true}),{ code });
16+
}
17+
{
18+
constfilePath=path.join(tmpdir.path,'rmdir-recursive.txt');
19+
fs.writeFileSync(filePath,'');
20+
fs.rmdir(filePath,{recursive: true},common.mustCall((err)=>{
21+
assert.strictEqual(err.code,code);
22+
}));
23+
}
24+
{
25+
constfilePath=path.join(tmpdir.path,'rmdir-recursive.txt');
26+
fs.writeFileSync(filePath,'');
27+
assert.rejects(()=>fs.promises.rmdir(filePath,{recursive: true}),
28+
{ code }).then(common.mustCall());
29+
}
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
'use strict';
22
constcommon=require('../common');
33
consttmpdir=require('../common/tmpdir');
4+
constassert=require('assert');
45
constfs=require('fs');
56
constpath=require('path');
67

78
tmpdir.refresh();
89

910
{
10-
// Should warn when trying to delete a file
1111
common.expectWarning(
1212
'DeprecationWarning',
1313
'In future versions of Node.js, fs.rmdir(path,{recursive: true }) '+
@@ -16,5 +16,7 @@ tmpdir.refresh();
1616
);
1717
constfilePath=path.join(tmpdir.path,'rmdir-recursive.txt');
1818
fs.writeFileSync(filePath,'');
19-
fs.rmdir(filePath,{recursive: true},common.mustSucceed());
19+
fs.rmdir(filePath,{recursive: true},common.mustCall((err)=>{
20+
assert.strictEqual(err.code,common.isWindows ? 'ENOENT' : 'ENOTDIR');
21+
}));
2022
}

0 commit comments

Comments
(0)