1const assert = require("assert") 2const path = require("path") 3const fs = require("fs") 4let glob = undefined 5try { 6 glob = require("glob") 7} catch (_err) { 8 // treat glob as optional. 9} 10 11const defaultGlobOpts = { 12 nosort: true, 13 silent: true 14} 15 16// for EMFILE handling 17let timeout = 0 18 19const isWindows = (process.platform === "win32") 20 21const defaults = options => { 22 const methods = [ 23 'unlink', 24 'chmod', 25 'stat', 26 'lstat', 27 'rmdir', 28 'readdir' 29 ] 30 methods.forEach(m => { 31 options[m] = options[m] || fs[m] 32 m = m + 'Sync' 33 options[m] = options[m] || fs[m] 34 }) 35 36 options.maxBusyTries = options.maxBusyTries || 3 37 options.emfileWait = options.emfileWait || 1000 38 if (options.glob === false) { 39 options.disableGlob = true 40 } 41 if (options.disableGlob !== true && glob === undefined) { 42 throw Error('glob dependency not found, set `options.disableGlob = true` if intentional') 43 } 44 options.disableGlob = options.disableGlob || false 45 options.glob = options.glob || defaultGlobOpts 46} 47 48const rimraf = (p, options, cb) => { 49 if (typeof options === 'function') { 50 cb = options 51 options = {} 52 } 53 54 assert(p, 'rimraf: missing path') 55 assert.equal(typeof p, 'string', 'rimraf: path should be a string') 56 assert.equal(typeof cb, 'function', 'rimraf: callback function required') 57 assert(options, 'rimraf: invalid options argument provided') 58 assert.equal(typeof options, 'object', 'rimraf: options should be object') 59 60 defaults(options) 61 62 let busyTries = 0 63 let errState = null 64 let n = 0 65 66 const next = (er) => { 67 errState = errState || er 68 if (--n === 0) 69 cb(errState) 70 } 71 72 const afterGlob = (er, results) => { 73 if (er) 74 return cb(er) 75 76 n = results.length 77 if (n === 0) 78 return cb() 79 80 results.forEach(p => { 81 const CB = (er) => { 82 if (er) { 83 if ((er.code === "EBUSY" || er.code === "ENOTEMPTY" || er.code === "EPERM") && 84 busyTries < options.maxBusyTries) { 85 busyTries ++ 86 // try again, with the same exact callback as this one. 87 return setTimeout(() => rimraf_(p, options, CB), busyTries * 100) 88 } 89 90 // this one won't happen if graceful-fs is used. 91 if (er.code === "EMFILE" && timeout < options.emfileWait) { 92 return setTimeout(() => rimraf_(p, options, CB), timeout ++) 93 } 94 95 // already gone 96 if (er.code === "ENOENT") er = null 97 } 98 99 timeout = 0 100 next(er) 101 } 102 rimraf_(p, options, CB) 103 }) 104 } 105 106 if (options.disableGlob || !glob.hasMagic(p)) 107 return afterGlob(null, [p]) 108 109 options.lstat(p, (er, stat) => { 110 if (!er) 111 return afterGlob(null, [p]) 112 113 glob(p, options.glob, afterGlob) 114 }) 115 116} 117 118// Two possible strategies. 119// 1. Assume it's a file. unlink it, then do the dir stuff on EPERM or EISDIR 120// 2. Assume it's a directory. readdir, then do the file stuff on ENOTDIR 121// 122// Both result in an extra syscall when you guess wrong. However, there 123// are likely far more normal files in the world than directories. This 124// is based on the assumption that a the average number of files per 125// directory is >= 1. 126// 127// If anyone ever complains about this, then I guess the strategy could 128// be made configurable somehow. But until then, YAGNI. 129const rimraf_ = (p, options, cb) => { 130 assert(p) 131 assert(options) 132 assert(typeof cb === 'function') 133 134 // sunos lets the root user unlink directories, which is... weird. 135 // so we have to lstat here and make sure it's not a dir. 136 options.lstat(p, (er, st) => { 137 if (er && er.code === "ENOENT") 138 return cb(null) 139 140 // Windows can EPERM on stat. Life is suffering. 141 if (er && er.code === "EPERM" && isWindows) 142 fixWinEPERM(p, options, er, cb) 143 144 if (st && st.isDirectory()) 145 return rmdir(p, options, er, cb) 146 147 options.unlink(p, er => { 148 if (er) { 149 if (er.code === "ENOENT") 150 return cb(null) 151 if (er.code === "EPERM") 152 return (isWindows) 153 ? fixWinEPERM(p, options, er, cb) 154 : rmdir(p, options, er, cb) 155 if (er.code === "EISDIR") 156 return rmdir(p, options, er, cb) 157 } 158 return cb(er) 159 }) 160 }) 161} 162 163const fixWinEPERM = (p, options, er, cb) => { 164 assert(p) 165 assert(options) 166 assert(typeof cb === 'function') 167 168 options.chmod(p, 0o666, er2 => { 169 if (er2) 170 cb(er2.code === "ENOENT" ? null : er) 171 else 172 options.stat(p, (er3, stats) => { 173 if (er3) 174 cb(er3.code === "ENOENT" ? null : er) 175 else if (stats.isDirectory()) 176 rmdir(p, options, er, cb) 177 else 178 options.unlink(p, cb) 179 }) 180 }) 181} 182 183const fixWinEPERMSync = (p, options, er) => { 184 assert(p) 185 assert(options) 186 187 try { 188 options.chmodSync(p, 0o666) 189 } catch (er2) { 190 if (er2.code === "ENOENT") 191 return 192 else 193 throw er 194 } 195 196 let stats 197 try { 198 stats = options.statSync(p) 199 } catch (er3) { 200 if (er3.code === "ENOENT") 201 return 202 else 203 throw er 204 } 205 206 if (stats.isDirectory()) 207 rmdirSync(p, options, er) 208 else 209 options.unlinkSync(p) 210} 211 212const rmdir = (p, options, originalEr, cb) => { 213 assert(p) 214 assert(options) 215 assert(typeof cb === 'function') 216 217 // try to rmdir first, and only readdir on ENOTEMPTY or EEXIST (SunOS) 218 // if we guessed wrong, and it's not a directory, then 219 // raise the original error. 220 options.rmdir(p, er => { 221 if (er && (er.code === "ENOTEMPTY" || er.code === "EEXIST" || er.code === "EPERM")) 222 rmkids(p, options, cb) 223 else if (er && er.code === "ENOTDIR") 224 cb(originalEr) 225 else 226 cb(er) 227 }) 228} 229 230const rmkids = (p, options, cb) => { 231 assert(p) 232 assert(options) 233 assert(typeof cb === 'function') 234 235 options.readdir(p, (er, files) => { 236 if (er) 237 return cb(er) 238 let n = files.length 239 if (n === 0) 240 return options.rmdir(p, cb) 241 let errState 242 files.forEach(f => { 243 rimraf(path.join(p, f), options, er => { 244 if (errState) 245 return 246 if (er) 247 return cb(errState = er) 248 if (--n === 0) 249 options.rmdir(p, cb) 250 }) 251 }) 252 }) 253} 254 255// this looks simpler, and is strictly *faster*, but will 256// tie up the JavaScript thread and fail on excessively 257// deep directory trees. 258const rimrafSync = (p, options) => { 259 options = options || {} 260 defaults(options) 261 262 assert(p, 'rimraf: missing path') 263 assert.equal(typeof p, 'string', 'rimraf: path should be a string') 264 assert(options, 'rimraf: missing options') 265 assert.equal(typeof options, 'object', 'rimraf: options should be object') 266 267 let results 268 269 if (options.disableGlob || !glob.hasMagic(p)) { 270 results = [p] 271 } else { 272 try { 273 options.lstatSync(p) 274 results = [p] 275 } catch (er) { 276 results = glob.sync(p, options.glob) 277 } 278 } 279 280 if (!results.length) 281 return 282 283 for (let i = 0; i < results.length; i++) { 284 const p = results[i] 285 286 let st 287 try { 288 st = options.lstatSync(p) 289 } catch (er) { 290 if (er.code === "ENOENT") 291 return 292 293 // Windows can EPERM on stat. Life is suffering. 294 if (er.code === "EPERM" && isWindows) 295 fixWinEPERMSync(p, options, er) 296 } 297 298 try { 299 // sunos lets the root user unlink directories, which is... weird. 300 if (st && st.isDirectory()) 301 rmdirSync(p, options, null) 302 else 303 options.unlinkSync(p) 304 } catch (er) { 305 if (er.code === "ENOENT") 306 return 307 if (er.code === "EPERM") 308 return isWindows ? fixWinEPERMSync(p, options, er) : rmdirSync(p, options, er) 309 if (er.code !== "EISDIR") 310 throw er 311 312 rmdirSync(p, options, er) 313 } 314 } 315} 316 317const rmdirSync = (p, options, originalEr) => { 318 assert(p) 319 assert(options) 320 321 try { 322 options.rmdirSync(p) 323 } catch (er) { 324 if (er.code === "ENOENT") 325 return 326 if (er.code === "ENOTDIR") 327 throw originalEr 328 if (er.code === "ENOTEMPTY" || er.code === "EEXIST" || er.code === "EPERM") 329 rmkidsSync(p, options) 330 } 331} 332 333const rmkidsSync = (p, options) => { 334 assert(p) 335 assert(options) 336 options.readdirSync(p).forEach(f => rimrafSync(path.join(p, f), options)) 337 338 // We only end up here once we got ENOTEMPTY at least once, and 339 // at this point, we are guaranteed to have removed all the kids. 340 // So, we know that it won't be ENOENT or ENOTDIR or anything else. 341 // try really hard to delete stuff on windows, because it has a 342 // PROFOUNDLY annoying habit of not closing handles promptly when 343 // files are deleted, resulting in spurious ENOTEMPTY errors. 344 const retries = isWindows ? 100 : 1 345 let i = 0 346 do { 347 let threw = true 348 try { 349 const ret = options.rmdirSync(p, options) 350 threw = false 351 return ret 352 } finally { 353 if (++i < retries && threw) 354 continue 355 } 356 } while (true) 357} 358 359module.exports = rimraf 360rimraf.sync = rimrafSync 361