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