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.

314 lines
7.5 KiB

4 years ago
  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. if (er) {
  100. assert(er instanceof Error)
  101. }
  102. options.chmod(p, 0o666, er2 => {
  103. if (er2) {
  104. cb(er2.code === 'ENOENT' ? null : er)
  105. } else {
  106. options.stat(p, (er3, stats) => {
  107. if (er3) {
  108. cb(er3.code === 'ENOENT' ? null : er)
  109. } else if (stats.isDirectory()) {
  110. rmdir(p, options, er, cb)
  111. } else {
  112. options.unlink(p, cb)
  113. }
  114. })
  115. }
  116. })
  117. }
  118. function fixWinEPERMSync (p, options, er) {
  119. let stats
  120. assert(p)
  121. assert(options)
  122. if (er) {
  123. assert(er instanceof Error)
  124. }
  125. try {
  126. options.chmodSync(p, 0o666)
  127. } catch (er2) {
  128. if (er2.code === 'ENOENT') {
  129. return
  130. } else {
  131. throw er
  132. }
  133. }
  134. try {
  135. stats = options.statSync(p)
  136. } catch (er3) {
  137. if (er3.code === 'ENOENT') {
  138. return
  139. } else {
  140. throw er
  141. }
  142. }
  143. if (stats.isDirectory()) {
  144. rmdirSync(p, options, er)
  145. } else {
  146. options.unlinkSync(p)
  147. }
  148. }
  149. function rmdir (p, options, originalEr, cb) {
  150. assert(p)
  151. assert(options)
  152. if (originalEr) {
  153. assert(originalEr instanceof Error)
  154. }
  155. assert(typeof cb === 'function')
  156. // try to rmdir first, and only readdir on ENOTEMPTY or EEXIST (SunOS)
  157. // if we guessed wrong, and it's not a directory, then
  158. // raise the original error.
  159. options.rmdir(p, er => {
  160. if (er && (er.code === 'ENOTEMPTY' || er.code === 'EEXIST' || er.code === 'EPERM')) {
  161. rmkids(p, options, cb)
  162. } else if (er && er.code === 'ENOTDIR') {
  163. cb(originalEr)
  164. } else {
  165. cb(er)
  166. }
  167. })
  168. }
  169. function rmkids (p, options, cb) {
  170. assert(p)
  171. assert(options)
  172. assert(typeof cb === 'function')
  173. options.readdir(p, (er, files) => {
  174. if (er) return cb(er)
  175. let n = files.length
  176. let errState
  177. if (n === 0) return options.rmdir(p, cb)
  178. files.forEach(f => {
  179. rimraf(path.join(p, f), options, er => {
  180. if (errState) {
  181. return
  182. }
  183. if (er) return cb(errState = er)
  184. if (--n === 0) {
  185. options.rmdir(p, cb)
  186. }
  187. })
  188. })
  189. })
  190. }
  191. // this looks simpler, and is strictly *faster*, but will
  192. // tie up the JavaScript thread and fail on excessively
  193. // deep directory trees.
  194. function rimrafSync (p, options) {
  195. let st
  196. options = options || {}
  197. defaults(options)
  198. assert(p, 'rimraf: missing path')
  199. assert.strictEqual(typeof p, 'string', 'rimraf: path should be a string')
  200. assert(options, 'rimraf: missing options')
  201. assert.strictEqual(typeof options, 'object', 'rimraf: options should be object')
  202. try {
  203. st = options.lstatSync(p)
  204. } catch (er) {
  205. if (er.code === 'ENOENT') {
  206. return
  207. }
  208. // Windows can EPERM on stat. Life is suffering.
  209. if (er.code === 'EPERM' && isWindows) {
  210. fixWinEPERMSync(p, options, er)
  211. }
  212. }
  213. try {
  214. // sunos lets the root user unlink directories, which is... weird.
  215. if (st && st.isDirectory()) {
  216. rmdirSync(p, options, null)
  217. } else {
  218. options.unlinkSync(p)
  219. }
  220. } catch (er) {
  221. if (er.code === 'ENOENT') {
  222. return
  223. } else if (er.code === 'EPERM') {
  224. return isWindows ? fixWinEPERMSync(p, options, er) : rmdirSync(p, options, er)
  225. } else if (er.code !== 'EISDIR') {
  226. throw er
  227. }
  228. rmdirSync(p, options, er)
  229. }
  230. }
  231. function rmdirSync (p, options, originalEr) {
  232. assert(p)
  233. assert(options)
  234. if (originalEr) {
  235. assert(originalEr instanceof Error)
  236. }
  237. try {
  238. options.rmdirSync(p)
  239. } catch (er) {
  240. if (er.code === 'ENOTDIR') {
  241. throw originalEr
  242. } else if (er.code === 'ENOTEMPTY' || er.code === 'EEXIST' || er.code === 'EPERM') {
  243. rmkidsSync(p, options)
  244. } else if (er.code !== 'ENOENT') {
  245. throw er
  246. }
  247. }
  248. }
  249. function rmkidsSync (p, options) {
  250. assert(p)
  251. assert(options)
  252. options.readdirSync(p).forEach(f => rimrafSync(path.join(p, f), options))
  253. if (isWindows) {
  254. // We only end up here once we got ENOTEMPTY at least once, and
  255. // at this point, we are guaranteed to have removed all the kids.
  256. // So, we know that it won't be ENOENT or ENOTDIR or anything else.
  257. // try really hard to delete stuff on windows, because it has a
  258. // PROFOUNDLY annoying habit of not closing handles promptly when
  259. // files are deleted, resulting in spurious ENOTEMPTY errors.
  260. const startTime = Date.now()
  261. do {
  262. try {
  263. const ret = options.rmdirSync(p, options)
  264. return ret
  265. } catch (er) { }
  266. } while (Date.now() - startTime < 500) // give up after 500ms
  267. } else {
  268. const ret = options.rmdirSync(p, options)
  269. return ret
  270. }
  271. }
  272. module.exports = rimraf
  273. rimraf.sync = rimrafSync