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