| 'use strict' | |
| const fs = require('graceful-fs') | |
| const path = require('path') | |
| const assert = require('assert') | |
| const isWindows = (process.platform === 'win32') | |
| function defaults (options) { | |
| const methods = [ | |
| 'unlink', | |
| 'chmod', | |
| 'stat', | |
| 'lstat', | |
| 'rmdir', | |
| 'readdir' | |
| ] | |
| methods.forEach(m => { | |
| options[m] = options[m] || fs[m] | |
| m = m + 'Sync' | |
| options[m] = options[m] || fs[m] | |
| }) | |
| options.maxBusyTries = options.maxBusyTries || 3 | |
| } | |
| function rimraf (p, options, cb) { | |
| let busyTries = 0 | |
| if (typeof options === 'function') { | |
| cb = options | |
| options = {} | |
| } | |
| assert(p, 'rimraf: missing path') | |
| assert.strictEqual(typeof p, 'string', 'rimraf: path should be a string') | |
| assert.strictEqual(typeof cb, 'function', 'rimraf: callback function required') | |
| assert(options, 'rimraf: invalid options argument provided') | |
| assert.strictEqual(typeof options, 'object', 'rimraf: options should be object') | |
| defaults(options) | |
| rimraf_(p, options, function CB (er) { | |
| if (er) { | |
| if ((er.code === 'EBUSY' || er.code === 'ENOTEMPTY' || er.code === 'EPERM') && | |
| busyTries < options.maxBusyTries) { | |
| busyTries++ | |
| const time = busyTries * 100 | |
| // try again, with the same exact callback as this one. | |
| return setTimeout(() => rimraf_(p, options, CB), time) | |
| } | |
| // already gone | |
| if (er.code === 'ENOENT') er = null | |
| } | |
| cb(er) | |
| }) | |
| } | |
| // Two possible strategies. | |
| // 1. Assume it's a file. unlink it, then do the dir stuff on EPERM or EISDIR | |
| // 2. Assume it's a directory. readdir, then do the file stuff on ENOTDIR | |
| // | |
| // Both result in an extra syscall when you guess wrong. However, there | |
| // are likely far more normal files in the world than directories. This | |
| // is based on the assumption that a the average number of files per | |
| // directory is >= 1. | |
| // | |
| // If anyone ever complains about this, then I guess the strategy could | |
| // be made configurable somehow. But until then, YAGNI. | |
| function rimraf_ (p, options, cb) { | |
| assert(p) | |
| assert(options) | |
| assert(typeof cb === 'function') | |
| // sunos lets the root user unlink directories, which is... weird. | |
| // so we have to lstat here and make sure it's not a dir. | |
| options.lstat(p, (er, st) => { | |
| if (er && er.code === 'ENOENT') { | |
| return cb(null) | |
| } | |
| // Windows can EPERM on stat. Life is suffering. | |
| if (er && er.code === 'EPERM' && isWindows) { | |
| return fixWinEPERM(p, options, er, cb) | |
| } | |
| if (st && st.isDirectory()) { | |
| return rmdir(p, options, er, cb) | |
| } | |
| options.unlink(p, er => { | |
| if (er) { | |
| if (er.code === 'ENOENT') { | |
| return cb(null) | |
| } | |
| if (er.code === 'EPERM') { | |
| return (isWindows) | |
| ? fixWinEPERM(p, options, er, cb) | |
| : rmdir(p, options, er, cb) | |
| } | |
| if (er.code === 'EISDIR') { | |
| return rmdir(p, options, er, cb) | |
| } | |
| } | |
| return cb(er) | |
| }) | |
| }) | |
| } | |
| function fixWinEPERM (p, options, er, cb) { | |
| assert(p) | |
| assert(options) | |
| assert(typeof cb === 'function') | |
| if (er) { | |
| assert(er instanceof Error) | |
| } | |
| options.chmod(p, 0o666, er2 => { | |
| if (er2) { | |
| cb(er2.code === 'ENOENT' ? null : er) | |
| } else { | |
| options.stat(p, (er3, stats) => { | |
| if (er3) { | |
| cb(er3.code === 'ENOENT' ? null : er) | |
| } else if (stats.isDirectory()) { | |
| rmdir(p, options, er, cb) | |
| } else { | |
| options.unlink(p, cb) | |
| } | |
| }) | |
| } | |
| }) | |
| } | |
| function fixWinEPERMSync (p, options, er) { | |
| let stats | |
| assert(p) | |
| assert(options) | |
| if (er) { | |
| assert(er instanceof Error) | |
| } | |
| try { | |
| options.chmodSync(p, 0o666) | |
| } catch (er2) { | |
| if (er2.code === 'ENOENT') { | |
| return | |
| } else { | |
| throw er | |
| } | |
| } | |
| try { | |
| stats = options.statSync(p) | |
| } catch (er3) { | |
| if (er3.code === 'ENOENT') { | |
| return | |
| } else { | |
| throw er | |
| } | |
| } | |
| if (stats.isDirectory()) { | |
| rmdirSync(p, options, er) | |
| } else { | |
| options.unlinkSync(p) | |
| } | |
| } | |
| function rmdir (p, options, originalEr, cb) { | |
| assert(p) | |
| assert(options) | |
| if (originalEr) { | |
| assert(originalEr instanceof Error) | |
| } | |
| assert(typeof cb === 'function') | |
| // try to rmdir first, and only readdir on ENOTEMPTY or EEXIST (SunOS) | |
| // if we guessed wrong, and it's not a directory, then | |
| // raise the original error. | |
| options.rmdir(p, er => { | |
| if (er && (er.code === 'ENOTEMPTY' || er.code === 'EEXIST' || er.code === 'EPERM')) { | |
| rmkids(p, options, cb) | |
| } else if (er && er.code === 'ENOTDIR') { | |
| cb(originalEr) | |
| } else { | |
| cb(er) | |
| } | |
| }) | |
| } | |
| function rmkids (p, options, cb) { | |
| assert(p) | |
| assert(options) | |
| assert(typeof cb === 'function') | |
| options.readdir(p, (er, files) => { | |
| if (er) return cb(er) | |
| let n = files.length | |
| let errState | |
| if (n === 0) return options.rmdir(p, cb) | |
| files.forEach(f => { | |
| rimraf(path.join(p, f), options, er => { | |
| if (errState) { | |
| return | |
| } | |
| if (er) return cb(errState = er) | |
| if (--n === 0) { | |
| options.rmdir(p, cb) | |
| } | |
| }) | |
| }) | |
| }) | |
| } | |
| // this looks simpler, and is strictly *faster*, but will | |
| // tie up the JavaScript thread and fail on excessively | |
| // deep directory trees. | |
| function rimrafSync (p, options) { | |
| let st | |
| options = options || {} | |
| defaults(options) | |
| assert(p, 'rimraf: missing path') | |
| assert.strictEqual(typeof p, 'string', 'rimraf: path should be a string') | |
| assert(options, 'rimraf: missing options') | |
| assert.strictEqual(typeof options, 'object', 'rimraf: options should be object') | |
| try { | |
| st = options.lstatSync(p) | |
| } catch (er) { | |
| if (er.code === 'ENOENT') { | |
| return | |
| } | |
| // Windows can EPERM on stat. Life is suffering. | |
| if (er.code === 'EPERM' && isWindows) { | |
| fixWinEPERMSync(p, options, er) | |
| } | |
| } | |
| try { | |
| // sunos lets the root user unlink directories, which is... weird. | |
| if (st && st.isDirectory()) { | |
| rmdirSync(p, options, null) | |
| } else { | |
| options.unlinkSync(p) | |
| } | |
| } catch (er) { | |
| if (er.code === 'ENOENT') { | |
| return | |
| } else if (er.code === 'EPERM') { | |
| return isWindows ? fixWinEPERMSync(p, options, er) : rmdirSync(p, options, er) | |
| } else if (er.code !== 'EISDIR') { | |
| throw er | |
| } | |
| rmdirSync(p, options, er) | |
| } | |
| } | |
| function rmdirSync (p, options, originalEr) { | |
| assert(p) | |
| assert(options) | |
| if (originalEr) { | |
| assert(originalEr instanceof Error) | |
| } | |
| try { | |
| options.rmdirSync(p) | |
| } catch (er) { | |
| if (er.code === 'ENOTDIR') { | |
| throw originalEr | |
| } else if (er.code === 'ENOTEMPTY' || er.code === 'EEXIST' || er.code === 'EPERM') { | |
| rmkidsSync(p, options) | |
| } else if (er.code !== 'ENOENT') { | |
| throw er | |
| } | |
| } | |
| } | |
| function rmkidsSync (p, options) { | |
| assert(p) | |
| assert(options) | |
| options.readdirSync(p).forEach(f => rimrafSync(path.join(p, f), options)) | |
| if (isWindows) { | |
| // We only end up here once we got ENOTEMPTY at least once, and | |
| // at this point, we are guaranteed to have removed all the kids. | |
| // So, we know that it won't be ENOENT or ENOTDIR or anything else. | |
| // try really hard to delete stuff on windows, because it has a | |
| // PROFOUNDLY annoying habit of not closing handles promptly when | |
| // files are deleted, resulting in spurious ENOTEMPTY errors. | |
| const startTime = Date.now() | |
| do { | |
| try { | |
| const ret = options.rmdirSync(p, options) | |
| return ret | |
| } catch (er) { } | |
| } while (Date.now() - startTime < 500) // give up after 500ms | |
| } else { | |
| const ret = options.rmdirSync(p, options) | |
| return ret | |
| } | |
| } | |
| module.exports = rimraf | |
| rimraf.sync = rimrafSync |