Projekt

Obecné

Profil

Stáhnout (9.01 KB) Statistiky
| Větev: | Revize:
1 3a515b92 cagy
module.exports = rimraf
2
rimraf.sync = rimrafSync
3
4
var assert = require("assert")
5
var path = require("path")
6
var fs = require("fs")
7
var glob = undefined
8
try {
9
  glob = require("glob")
10
} catch (_err) {
11
  // treat glob as optional.
12
}
13
var _0666 = parseInt('666', 8)
14
15
var defaultGlobOpts = {
16
  nosort: true,
17
  silent: true
18
}
19
20
// for EMFILE handling
21
var timeout = 0
22
23
var isWindows = (process.platform === "win32")
24
25
function 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
52
function 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.
136
function 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
170
function 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
192
function 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
222
function 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
242
function 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.
270
function 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
328
function 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
346
function 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
}