rimraf.js 7.3 KB

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