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