1// This file is a modified version of the rimraf module on npm. It has been 2// modified in the following ways: 3// - Use of the assert module has been replaced with core's error system. 4// - All code related to the glob dependency has been removed. 5// - Bring your own custom fs module is not currently supported. 6// - Some basic code cleanup. 7'use strict'; 8 9const { 10 ArrayPrototypeForEach, 11 Promise, 12 SafeSet, 13} = primordials; 14 15const { Buffer } = require('buffer'); 16const fs = require('fs'); 17const { 18 chmod, 19 chmodSync, 20 lstat, 21 lstatSync, 22 readdir, 23 readdirSync, 24 rmdir, 25 rmdirSync, 26 stat, 27 statSync, 28 unlink, 29 unlinkSync, 30} = fs; 31const { sep } = require('path'); 32const { setTimeout } = require('timers'); 33const { sleep } = require('internal/util'); 34const notEmptyErrorCodes = new SafeSet(['ENOTEMPTY', 'EEXIST', 'EPERM']); 35const retryErrorCodes = new SafeSet( 36 ['EBUSY', 'EMFILE', 'ENFILE', 'ENOTEMPTY', 'EPERM']); 37const isWindows = process.platform === 'win32'; 38const epermHandler = isWindows ? fixWinEPERM : _rmdir; 39const epermHandlerSync = isWindows ? fixWinEPERMSync : _rmdirSync; 40const readdirEncoding = 'buffer'; 41const separator = Buffer.from(sep); 42 43 44function rimraf(path, options, callback) { 45 let retries = 0; 46 47 _rimraf(path, options, function CB(err) { 48 if (err) { 49 if (retryErrorCodes.has(err.code) && retries < options.maxRetries) { 50 retries++; 51 const delay = retries * options.retryDelay; 52 return setTimeout(_rimraf, delay, path, options, CB); 53 } 54 55 // The file is already gone. 56 if (err.code === 'ENOENT') 57 err = null; 58 } 59 60 callback(err); 61 }); 62} 63 64 65function _rimraf(path, options, callback) { 66 // SunOS lets the root user unlink directories. Use lstat here to make sure 67 // it's not a directory. 68 lstat(path, (err, stats) => { 69 if (err) { 70 if (err.code === 'ENOENT') 71 return callback(null); 72 73 // Windows can EPERM on stat. 74 if (isWindows && err.code === 'EPERM') 75 return fixWinEPERM(path, options, err, callback); 76 } else if (stats.isDirectory()) { 77 return _rmdir(path, options, err, callback); 78 } 79 80 unlink(path, (err) => { 81 if (err) { 82 if (err.code === 'ENOENT') 83 return callback(null); 84 if (err.code === 'EISDIR') 85 return _rmdir(path, options, err, callback); 86 if (err.code === 'EPERM') { 87 return epermHandler(path, options, err, callback); 88 } 89 } 90 91 return callback(err); 92 }); 93 }); 94} 95 96 97function fixWinEPERM(path, options, originalErr, callback) { 98 chmod(path, 0o666, (err) => { 99 if (err) 100 return callback(err.code === 'ENOENT' ? null : originalErr); 101 102 stat(path, (err, stats) => { 103 if (err) 104 return callback(err.code === 'ENOENT' ? null : originalErr); 105 106 if (stats.isDirectory()) 107 _rmdir(path, options, originalErr, callback); 108 else 109 unlink(path, callback); 110 }); 111 }); 112} 113 114 115function _rmdir(path, options, originalErr, callback) { 116 rmdir(path, (err) => { 117 if (err) { 118 if (notEmptyErrorCodes.has(err.code)) 119 return _rmchildren(path, options, callback); 120 if (err.code === 'ENOTDIR') 121 return callback(originalErr); 122 } 123 124 callback(err); 125 }); 126} 127 128 129function _rmchildren(path, options, callback) { 130 const pathBuf = Buffer.from(path); 131 132 readdir(pathBuf, readdirEncoding, (err, files) => { 133 if (err) 134 return callback(err); 135 136 let numFiles = files.length; 137 138 if (numFiles === 0) 139 return rmdir(path, callback); 140 141 let done = false; 142 143 ArrayPrototypeForEach(files, (child) => { 144 const childPath = Buffer.concat([pathBuf, separator, child]); 145 146 rimraf(childPath, options, (err) => { 147 if (done) 148 return; 149 150 if (err) { 151 done = true; 152 return callback(err); 153 } 154 155 numFiles--; 156 if (numFiles === 0) 157 rmdir(path, callback); 158 }); 159 }); 160 }); 161} 162 163 164function rimrafPromises(path, options) { 165 return new Promise((resolve, reject) => { 166 rimraf(path, options, (err) => { 167 if (err) 168 return reject(err); 169 170 resolve(); 171 }); 172 }); 173} 174 175 176function rimrafSync(path, options) { 177 let stats; 178 179 try { 180 stats = lstatSync(path); 181 } catch (err) { 182 if (err.code === 'ENOENT') 183 return; 184 185 // Windows can EPERM on stat. 186 if (isWindows && err.code === 'EPERM') 187 fixWinEPERMSync(path, options, err); 188 } 189 190 try { 191 // SunOS lets the root user unlink directories. 192 if (stats?.isDirectory()) 193 _rmdirSync(path, options, null); 194 else 195 _unlinkSync(path, options); 196 } catch (err) { 197 if (err.code === 'ENOENT') 198 return; 199 if (err.code === 'EPERM') 200 return epermHandlerSync(path, options, err); 201 if (err.code !== 'EISDIR') 202 throw err; 203 204 _rmdirSync(path, options, err); 205 } 206} 207 208 209function _unlinkSync(path, options) { 210 const tries = options.maxRetries + 1; 211 212 for (let i = 1; i <= tries; i++) { 213 try { 214 return unlinkSync(path); 215 } catch (err) { 216 // Only sleep if this is not the last try, and the delay is greater 217 // than zero, and an error was encountered that warrants a retry. 218 if (retryErrorCodes.has(err.code) && 219 i < tries && 220 options.retryDelay > 0) { 221 sleep(i * options.retryDelay); 222 } else if (err.code === 'ENOENT') { 223 // The file is already gone. 224 return; 225 } else if (i === tries) { 226 throw err; 227 } 228 } 229 } 230} 231 232 233function _rmdirSync(path, options, originalErr) { 234 try { 235 rmdirSync(path); 236 } catch (err) { 237 if (err.code === 'ENOENT') 238 return; 239 if (err.code === 'ENOTDIR') { 240 throw originalErr || err; 241 } 242 243 if (notEmptyErrorCodes.has(err.code)) { 244 // Removing failed. Try removing all children and then retrying the 245 // original removal. Windows has a habit of not closing handles promptly 246 // when files are deleted, resulting in spurious ENOTEMPTY failures. Work 247 // around that issue by retrying on Windows. 248 const pathBuf = Buffer.from(path); 249 250 ArrayPrototypeForEach(readdirSync(pathBuf, readdirEncoding), (child) => { 251 const childPath = Buffer.concat([pathBuf, separator, child]); 252 253 rimrafSync(childPath, options); 254 }); 255 256 const tries = options.maxRetries + 1; 257 258 for (let i = 1; i <= tries; i++) { 259 try { 260 return fs.rmdirSync(path); 261 } catch (err) { 262 // Only sleep if this is not the last try, and the delay is greater 263 // than zero, and an error was encountered that warrants a retry. 264 if (retryErrorCodes.has(err.code) && 265 i < tries && 266 options.retryDelay > 0) { 267 sleep(i * options.retryDelay); 268 } else if (err.code === 'ENOENT') { 269 // The file is already gone. 270 return; 271 } else if (i === tries) { 272 throw err; 273 } 274 } 275 } 276 } 277 278 throw originalErr || err; 279 } 280} 281 282 283function fixWinEPERMSync(path, options, originalErr) { 284 try { 285 chmodSync(path, 0o666); 286 } catch (err) { 287 if (err.code === 'ENOENT') 288 return; 289 290 throw originalErr; 291 } 292 293 let stats; 294 295 try { 296 stats = statSync(path, { throwIfNoEntry: false }); 297 } catch { 298 throw originalErr; 299 } 300 301 if (stats === undefined) return; 302 303 if (stats.isDirectory()) 304 _rmdirSync(path, options, originalErr); 305 else 306 _unlinkSync(path, options); 307} 308 309 310module.exports = { rimraf, rimrafPromises, rimrafSync }; 311