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.

360 lines
8.7 KiB

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