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: stop fixing EPERM on Windows
Originally, when rimraf faced an EPERM on Windows, it would try to
change the file permissions of the entry and attempt to remove it
again. However, rm -rf doesn't change any file permissions, so rimraf
shouldn't either.

This change also updates test/common/tmpdir.js to repeatedly try
changing the file permissions of its contents to 0o777 when an EACCES or
an EPERM is faced while attempting to remove them.

Signed-off-by: Darshan Sen <[email protected]>
  • Loading branch information
RaisinTen committed Jun 2, 2021
commit 8186ba411902defccde0524eec2e38da8dc9ff83
83 changes: 6 additions & 77 deletions lib/internal/fs/rimraf.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
// This file is a modified version of the rimraf module on npm. It has been
// modified in the following ways:
// - Use of the assert module has been replaced with core's error system.
// - All code related to the glob dependency has been removed.
// - Bring your own custom fs module is not currently supported.
// - Some basic code cleanup.
// This file is a modified version of the rimraf module on npm.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment listing the changes was added in #29168 as a separate commit (ee63d32), so I'm unsure if it was a suggestion motivated by a good reason or just something to help reviewers review the original PR. I'm not against removing it, I just thought it'd be interesting to point it out.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it was added to address #29168 (comment). Since we can see the changes made to the file with a git log and the linked PRs have the reasons for the changes, it feels redundant to keep this around.

'use strict';

const {
Expand All @@ -15,16 +10,12 @@ const {
const { Buffer } = require('buffer');
const fs = require('fs');
const {
chmod,
chmodSync,
lstat,
lstatSync,
readdir,
readdirSync,
rmdir,
rmdirSync,
stat,
statSync,
unlink,
unlinkSync
} = fs;
Expand All @@ -34,9 +25,6 @@ const { sleep } = require('internal/util');
const notEmptyErrorCodes = new SafeSet(['ENOTEMPTY', 'EEXIST', 'EPERM']);
const retryErrorCodes = new SafeSet(
['EBUSY', 'EMFILE', 'ENFILE', 'ENOTEMPTY', 'EPERM']);
const isWindows = process.platform === 'win32';
const epermHandler = isWindows ? fixWinEPERM : _rmdir;
const epermHandlerSync = isWindows ? fixWinEPERMSync : _rmdirSync;
const readdirEncoding = 'buffer';
const separator = Buffer.from(sep);

Expand Down Expand Up @@ -69,10 +57,6 @@ function _rimraf(path, options, callback) {
if (err) {
if (err.code === 'ENOENT')
return callback(null);

// Windows can EPERM on stat.
if (isWindows && err.code === 'EPERM')
return fixWinEPERM(path, options, err, callback);
} else if (stats.isDirectory()) {
return _rmdir(path, options, err, callback);
}
Expand All @@ -81,11 +65,8 @@ function _rimraf(path, options, callback) {
if (err) {
if (err.code === 'ENOENT')
return callback(null);
if (err.code === 'EISDIR')
if (err.code === 'EISDIR' || err.code === 'EPERM')
return _rmdir(path, options, err, callback);
if (err.code === 'EPERM') {
return epermHandler(path, options, err, callback);
}
}

return callback(err);
Expand All @@ -94,24 +75,6 @@ function _rimraf(path, options, callback) {
}


function fixWinEPERM(path, options, originalErr, callback) {
chmod(path, 0o666, (err) => {
if (err)
return callback(err.code === 'ENOENT' ? null : originalErr);

stat(path, (err, stats) => {
if (err)
return callback(err.code === 'ENOENT' ? null : originalErr);

if (stats.isDirectory())
_rmdir(path, options, originalErr, callback);
else
unlink(path, callback);
});
});
}


function _rmdir(path, options, originalErr, callback) {
rmdir(path, (err) => {
if (err) {
Expand Down Expand Up @@ -181,10 +144,6 @@ function rimrafSync(path, options) {
} catch (err) {
if (err.code === 'ENOENT')
return;

// Windows can EPERM on stat.
if (isWindows && err.code === 'EPERM')
fixWinEPERMSync(path, options, err);
}

try {
Expand All @@ -194,14 +153,11 @@ function rimrafSync(path, options) {
else
_unlinkSync(path, options);
} catch (err) {
if (err.code === 'ENOENT')
return;
if (err.code === 'EPERM')
return epermHandlerSync(path, options, err);
if (err.code !== 'EISDIR')
throw err;
if (err.code === 'EISDIR' || err.code === 'EPERM')
return _rmdirSync(path, options, err);

_rmdirSync(path, options, err);
if (err.code !== 'ENOENT')
throw err;
}
}

Expand Down Expand Up @@ -280,31 +236,4 @@ function _rmdirSync(path, options, originalErr) {
}


function fixWinEPERMSync(path, options, originalErr) {
try {
chmodSync(path, 0o666);
} catch (err) {
if (err.code === 'ENOENT')
return;

throw originalErr;
}

let stats;

try {
stats = statSync(path, { throwIfNoEntry: false });
} catch {
throw originalErr;
}

if (stats === undefined) return;

if (stats.isDirectory())
_rmdirSync(path, options, originalErr);
else
_unlinkSync(path, options);
}


module.exports = { rimraf, rimrafPromises, rimrafSync };
79 changes: 63 additions & 16 deletions test/common/tmpdir.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,45 @@ const path = require('path');
const { isMainThread } = require('worker_threads');

function rmSync(pathname) {
fs.rmSync(pathname, { maxRetries: 3, recursive: true, force: true });
let err = null;
const maxRetries = 10;

for (let retryNumber = 0; retryNumber < maxRetries; ++retryNumber) {
err = null;

try {
fs.rmSync(pathname, { maxRetries: 3, recursive: true, force: true });
} catch (_err) {
err = _err;
}

if (err === null) {
return;
}

const errPath = err.path;
const errCode = err.code;

if (errCode === 'EACCES' || errCode === 'EPERM') {
const surroundingDir = path.join(errPath, '..');

try {
fs.chmodSync(surroundingDir, 0o777);
} catch {
}

try {
fs.chmodSync(errPath, 0o777);
} catch {
}
}
}

if (err === null) {
return;
}

throw err;
}

const testRoot = process.env.NODE_TEST_DIR ?
Expand All @@ -31,28 +69,37 @@ function refresh() {
}
}

function logErrorInfo(err) {
console.error('Can\'t clean tmpdir:', tmpPath);

const files = fs.readdirSync(tmpPath);
console.error('Files blocking:', files);

if (files.some((f) => f.startsWith('.nfs'))) {
// Warn about NFS "silly rename"
console.error('Note: ".nfs*" might be files that were open and ' +
'unlinked but not closed.');
console.error('See http://nfs.sourceforge.net/#faq_d2 for details.');
}

console.error();

// Additionally logging err, just in case throwing err gets overshadowed by
// an error from a test failure.
console.error(err);

throw err;
}

function onexit() {
// Change directory to avoid possible EBUSY
if (isMainThread)
process.chdir(testRoot);

try {
rmSync(tmpPath);
} catch (e) {
console.error('Can\'t clean tmpdir:', tmpPath);

const files = fs.readdirSync(tmpPath);
console.error('Files blocking:', files);

if (files.some((f) => f.startsWith('.nfs'))) {
// Warn about NFS "silly rename"
console.error('Note: ".nfs*" might be files that were open and ' +
'unlinked but not closed.');
console.error('See http://nfs.sourceforge.net/#faq_d2 for details.');
}

console.error();
throw e;
} catch (err) {
logErrorInfo(err);
}
}

Expand Down
Loading