You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

364 lines
8.8 KiB

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