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.

346 lines
8.6 KiB

4 years ago
  1. var fs = require('fs')
  2. var polyfills = require('./polyfills.js')
  3. var legacy = require('./legacy-streams.js')
  4. var clone = require('./clone.js')
  5. var util = require('util')
  6. /* istanbul ignore next - node 0.x polyfill */
  7. var gracefulQueue
  8. var previousSymbol
  9. /* istanbul ignore else - node 0.x polyfill */
  10. if (typeof Symbol === 'function' && typeof Symbol.for === 'function') {
  11. gracefulQueue = Symbol.for('graceful-fs.queue')
  12. // This is used in testing by future versions
  13. previousSymbol = Symbol.for('graceful-fs.previous')
  14. } else {
  15. gracefulQueue = '___graceful-fs.queue'
  16. previousSymbol = '___graceful-fs.previous'
  17. }
  18. function noop () {}
  19. var debug = noop
  20. if (util.debuglog)
  21. debug = util.debuglog('gfs4')
  22. else if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || ''))
  23. debug = function() {
  24. var m = util.format.apply(util, arguments)
  25. m = 'GFS4: ' + m.split(/\n/).join('\nGFS4: ')
  26. console.error(m)
  27. }
  28. // Once time initialization
  29. if (!global[gracefulQueue]) {
  30. // This queue can be shared by multiple loaded instances
  31. var queue = []
  32. Object.defineProperty(global, gracefulQueue, {
  33. get: function() {
  34. return queue
  35. }
  36. })
  37. // Patch fs.close/closeSync to shared queue version, because we need
  38. // to retry() whenever a close happens *anywhere* in the program.
  39. // This is essential when multiple graceful-fs instances are
  40. // in play at the same time.
  41. fs.close = (function (fs$close) {
  42. function close (fd, cb) {
  43. return fs$close.call(fs, fd, function (err) {
  44. // This function uses the graceful-fs shared queue
  45. if (!err) {
  46. retry()
  47. }
  48. if (typeof cb === 'function')
  49. cb.apply(this, arguments)
  50. })
  51. }
  52. Object.defineProperty(close, previousSymbol, {
  53. value: fs$close
  54. })
  55. return close
  56. })(fs.close)
  57. fs.closeSync = (function (fs$closeSync) {
  58. function closeSync (fd) {
  59. // This function uses the graceful-fs shared queue
  60. fs$closeSync.apply(fs, arguments)
  61. retry()
  62. }
  63. Object.defineProperty(closeSync, previousSymbol, {
  64. value: fs$closeSync
  65. })
  66. return closeSync
  67. })(fs.closeSync)
  68. if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) {
  69. process.on('exit', function() {
  70. debug(global[gracefulQueue])
  71. require('assert').equal(global[gracefulQueue].length, 0)
  72. })
  73. }
  74. }
  75. module.exports = patch(clone(fs))
  76. if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs.__patched) {
  77. module.exports = patch(fs)
  78. fs.__patched = true;
  79. }
  80. function patch (fs) {
  81. // Everything that references the open() function needs to be in here
  82. polyfills(fs)
  83. fs.gracefulify = patch
  84. fs.createReadStream = createReadStream
  85. fs.createWriteStream = createWriteStream
  86. var fs$readFile = fs.readFile
  87. fs.readFile = readFile
  88. function readFile (path, options, cb) {
  89. if (typeof options === 'function')
  90. cb = options, options = null
  91. return go$readFile(path, options, cb)
  92. function go$readFile (path, options, cb) {
  93. return fs$readFile(path, options, function (err) {
  94. if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
  95. enqueue([go$readFile, [path, options, cb]])
  96. else {
  97. if (typeof cb === 'function')
  98. cb.apply(this, arguments)
  99. retry()
  100. }
  101. })
  102. }
  103. }
  104. var fs$writeFile = fs.writeFile
  105. fs.writeFile = writeFile
  106. function writeFile (path, data, options, cb) {
  107. if (typeof options === 'function')
  108. cb = options, options = null
  109. return go$writeFile(path, data, options, cb)
  110. function go$writeFile (path, data, options, cb) {
  111. return fs$writeFile(path, data, options, function (err) {
  112. if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
  113. enqueue([go$writeFile, [path, data, options, cb]])
  114. else {
  115. if (typeof cb === 'function')
  116. cb.apply(this, arguments)
  117. retry()
  118. }
  119. })
  120. }
  121. }
  122. var fs$appendFile = fs.appendFile
  123. if (fs$appendFile)
  124. fs.appendFile = appendFile
  125. function appendFile (path, data, options, cb) {
  126. if (typeof options === 'function')
  127. cb = options, options = null
  128. return go$appendFile(path, data, options, cb)
  129. function go$appendFile (path, data, options, cb) {
  130. return fs$appendFile(path, data, options, function (err) {
  131. if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
  132. enqueue([go$appendFile, [path, data, options, cb]])
  133. else {
  134. if (typeof cb === 'function')
  135. cb.apply(this, arguments)
  136. retry()
  137. }
  138. })
  139. }
  140. }
  141. var fs$readdir = fs.readdir
  142. fs.readdir = readdir
  143. function readdir (path, options, cb) {
  144. var args = [path]
  145. if (typeof options !== 'function') {
  146. args.push(options)
  147. } else {
  148. cb = options
  149. }
  150. args.push(go$readdir$cb)
  151. return go$readdir(args)
  152. function go$readdir$cb (err, files) {
  153. if (files && files.sort)
  154. files.sort()
  155. if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
  156. enqueue([go$readdir, [args]])
  157. else {
  158. if (typeof cb === 'function')
  159. cb.apply(this, arguments)
  160. retry()
  161. }
  162. }
  163. }
  164. function go$readdir (args) {
  165. return fs$readdir.apply(fs, args)
  166. }
  167. if (process.version.substr(0, 4) === 'v0.8') {
  168. var legStreams = legacy(fs)
  169. ReadStream = legStreams.ReadStream
  170. WriteStream = legStreams.WriteStream
  171. }
  172. var fs$ReadStream = fs.ReadStream
  173. if (fs$ReadStream) {
  174. ReadStream.prototype = Object.create(fs$ReadStream.prototype)
  175. ReadStream.prototype.open = ReadStream$open
  176. }
  177. var fs$WriteStream = fs.WriteStream
  178. if (fs$WriteStream) {
  179. WriteStream.prototype = Object.create(fs$WriteStream.prototype)
  180. WriteStream.prototype.open = WriteStream$open
  181. }
  182. Object.defineProperty(fs, 'ReadStream', {
  183. get: function () {
  184. return ReadStream
  185. },
  186. set: function (val) {
  187. ReadStream = val
  188. },
  189. enumerable: true,
  190. configurable: true
  191. })
  192. Object.defineProperty(fs, 'WriteStream', {
  193. get: function () {
  194. return WriteStream
  195. },
  196. set: function (val) {
  197. WriteStream = val
  198. },
  199. enumerable: true,
  200. configurable: true
  201. })
  202. // legacy names
  203. var FileReadStream = ReadStream
  204. Object.defineProperty(fs, 'FileReadStream', {
  205. get: function () {
  206. return FileReadStream
  207. },
  208. set: function (val) {
  209. FileReadStream = val
  210. },
  211. enumerable: true,
  212. configurable: true
  213. })
  214. var FileWriteStream = WriteStream
  215. Object.defineProperty(fs, 'FileWriteStream', {
  216. get: function () {
  217. return FileWriteStream
  218. },
  219. set: function (val) {
  220. FileWriteStream = val
  221. },
  222. enumerable: true,
  223. configurable: true
  224. })
  225. function ReadStream (path, options) {
  226. if (this instanceof ReadStream)
  227. return fs$ReadStream.apply(this, arguments), this
  228. else
  229. return ReadStream.apply(Object.create(ReadStream.prototype), arguments)
  230. }
  231. function ReadStream$open () {
  232. var that = this
  233. open(that.path, that.flags, that.mode, function (err, fd) {
  234. if (err) {
  235. if (that.autoClose)
  236. that.destroy()
  237. that.emit('error', err)
  238. } else {
  239. that.fd = fd
  240. that.emit('open', fd)
  241. that.read()
  242. }
  243. })
  244. }
  245. function WriteStream (path, options) {
  246. if (this instanceof WriteStream)
  247. return fs$WriteStream.apply(this, arguments), this
  248. else
  249. return WriteStream.apply(Object.create(WriteStream.prototype), arguments)
  250. }
  251. function WriteStream$open () {
  252. var that = this
  253. open(that.path, that.flags, that.mode, function (err, fd) {
  254. if (err) {
  255. that.destroy()
  256. that.emit('error', err)
  257. } else {
  258. that.fd = fd
  259. that.emit('open', fd)
  260. }
  261. })
  262. }
  263. function createReadStream (path, options) {
  264. return new fs.ReadStream(path, options)
  265. }
  266. function createWriteStream (path, options) {
  267. return new fs.WriteStream(path, options)
  268. }
  269. var fs$open = fs.open
  270. fs.open = open
  271. function open (path, flags, mode, cb) {
  272. if (typeof mode === 'function')
  273. cb = mode, mode = null
  274. return go$open(path, flags, mode, cb)
  275. function go$open (path, flags, mode, cb) {
  276. return fs$open(path, flags, mode, function (err, fd) {
  277. if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
  278. enqueue([go$open, [path, flags, mode, cb]])
  279. else {
  280. if (typeof cb === 'function')
  281. cb.apply(this, arguments)
  282. retry()
  283. }
  284. })
  285. }
  286. }
  287. return fs
  288. }
  289. function enqueue (elem) {
  290. debug('ENQUEUE', elem[0].name, elem[1])
  291. global[gracefulQueue].push(elem)
  292. }
  293. function retry () {
  294. var elem = global[gracefulQueue].shift()
  295. if (elem) {
  296. debug('RETRY', elem[0].name, elem[1])
  297. elem[0].apply(null, elem[1])
  298. }
  299. }