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