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