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.

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