Skip to content

Commit a124429

Browse files
PickBasaduh95
authored andcommitted
fs: do not treat EPERM as ENOTEMPTY on Windows
Fixes: #56433 Signed-off-by: PickBas <sayed.kirill@gmail.com> PR-URL: #63709Fixes: #56433 Reviewed-By: Jason Zhang <xzha4350@gmail.com> Reviewed-By: Stefan Stojanovic <stefan.stojanovic@janeasystems.com>
1 parent cc0c586 commit a124429

2 files changed

Lines changed: 65 additions & 1 deletion

File tree

‎lib/internal/fs/rimraf.js‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ const {
2424
const{ sep }=require('path');
2525
const{ setTimeout }=require('timers');
2626
const{ isWindows }=require('internal/util');
27-
constnotEmptyErrorCodes=newSafeSet(['ENOTEMPTY','EEXIST','EPERM']);
27+
constnotEmptyErrorCodes=isWindows ?
28+
newSafeSet(['ENOTEMPTY','EEXIST']) :
29+
newSafeSet(['ENOTEMPTY','EEXIST','EPERM']);
2830
constretryErrorCodes=newSafeSet(
2931
['EBUSY','EMFILE','ENFILE','ENOTEMPTY','EPERM']);
3032
constepermHandler=isWindows ? fixWinEPERM : _rmdir;

‎test/parallel/test-fs-rm.js‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,5 +567,67 @@ if (isGitPresent) {
567567
makeDirectoryWritable(middle);
568568
}
569569
}
570+
571+
if(common.isWindows){
572+
// On Windows, EPERM from rmdir on a directory that cannot be deleted
573+
// due to permissions must not be treated as ENOTEMPTY (which would
574+
// cause rimraf to recurse into and delete the directory's children).
575+
constdirname=nextDirPath();
576+
constparent=path.join(dirname,'parent');
577+
constchild=path.join(parent,'child');
578+
constchildFile=path.join(child,'childFile.txt');
579+
fs.mkdirSync(child,common.mustNotMutateObjectDeep({recursive: true}));
580+
fs.writeFileSync(childFile,'hello');
581+
582+
// (DC) denies deleting children; (DE) denies deleting the directory
583+
// itself. Combined denies on each layer guarantee rmdir returns EPERM.
584+
execSync(`icacls "${dirname}" /deny "everyone:(DC)"`);
585+
execSync(`icacls "${parent}" /deny "everyone:(DE,DC)"`);
586+
execSync(`icacls "${child}" /deny "everyone:(DE)"`);
587+
588+
constcleanup=()=>{
589+
try{
590+
execSync(`icacls "${child}" /remove:d "everyone"`);
591+
}catch{
592+
// Best-effort cleanup; ignore failures (e.g. already cleared).
593+
}
594+
try{
595+
execSync(`icacls "${parent}" /remove:d "everyone"`);
596+
}catch{
597+
// Best-effort cleanup; ignore failures.
598+
}
599+
try{
600+
execSync(`icacls "${dirname}" /remove:d "everyone"`);
601+
}catch{
602+
// Best-effort cleanup; ignore failures.
603+
}
604+
try{
605+
fs.rmSync(dirname,common.mustNotMutateObjectDeep({
606+
recursive: true,
607+
force: true,
608+
}));
609+
}catch{
610+
// Best-effort cleanup; ignore failures.
611+
}
612+
};
613+
process.on('exit',cleanup);
614+
615+
fs.rm(dirname,common.mustNotMutateObjectDeep({recursive: true}),
616+
common.mustCall((err)=>{
617+
try{
618+
assert.ok(err,'expected EPERM error');
619+
assert.strictEqual(err.code,'EPERM');
620+
assert.strictEqual(err.syscall,'rmdir');
621+
assert.ok(err.path.endsWith('\\parent'));
622+
assert.ok(
623+
fs.existsSync(child),
624+
'EPERM from rmdir must propagate without recursing into children',
625+
);
626+
}finally{
627+
process.removeListener('exit',cleanup);
628+
cleanup();
629+
}
630+
}));
631+
}
570632
}
571633
}

0 commit comments

Comments
 (0)