aboutsummaryrefslogtreecommitdiff
path: root/node_modules/write-file-atomic/index.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/write-file-atomic/index.js')
-rw-r--r--node_modules/write-file-atomic/index.js177
1 files changed, 121 insertions, 56 deletions
diff --git a/node_modules/write-file-atomic/index.js b/node_modules/write-file-atomic/index.js
index c677ee962..3b5607d15 100644
--- a/node_modules/write-file-atomic/index.js
+++ b/node_modules/write-file-atomic/index.js
@@ -2,10 +2,13 @@
module.exports = writeFile
module.exports.sync = writeFileSync
module.exports._getTmpname = getTmpname // for testing
+module.exports._cleanupOnExit = cleanupOnExit
var fs = require('graceful-fs')
-var chain = require('slide').chain
var MurmurHash3 = require('imurmurhash')
+var onExit = require('signal-exit')
+var path = require('path')
+var activeFiles = {}
var invocations = 0
function getTmpname (filename) {
@@ -16,75 +19,134 @@ function getTmpname (filename) {
.result()
}
+function cleanupOnExit (tmpfile) {
+ return function () {
+ try {
+ fs.unlinkSync(typeof tmpfile === 'function' ? tmpfile() : tmpfile)
+ } catch (_) {}
+ }
+}
+
function writeFile (filename, data, options, callback) {
if (options instanceof Function) {
callback = options
options = null
}
if (!options) options = {}
- fs.realpath(filename, function (_, realname) {
- _writeFile(realname || filename, data, options, callback)
- })
-}
-function _writeFile (filename, data, options, callback) {
- var tmpfile = getTmpname(filename)
- if (options.mode && options.chown) {
- return thenWriteFile()
- } else {
- // Either mode or chown is not explicitly set
- // Default behavior is to copy it from original file
- return fs.stat(filename, function (err, stats) {
- if (err || !stats) return thenWriteFile()
+ var Promise = options.Promise || global.Promise
+ var truename
+ var fd
+ var tmpfile
+ var removeOnExit = cleanupOnExit(() => tmpfile)
+ var absoluteName = path.resolve(filename)
- options = Object.assign({}, options)
- if (!options.mode) {
- options.mode = stats.mode
- }
- if (!options.chown && process.getuid) {
- options.chown = { uid: stats.uid, gid: stats.gid }
- }
- return thenWriteFile()
- })
- }
+ new Promise(function serializeSameFile (resolve) {
+ // make a queue if it doesn't already exist
+ if (!activeFiles[absoluteName]) activeFiles[absoluteName] = []
- function thenWriteFile () {
- chain([
- [writeFileAsync, tmpfile, data, options.mode, options.encoding || 'utf8'],
- options.chown && [fs, fs.chown, tmpfile, options.chown.uid, options.chown.gid],
- options.mode && [fs, fs.chmod, tmpfile, options.mode],
- [fs, fs.rename, tmpfile, filename]
- ], function (err) {
- err ? fs.unlink(tmpfile, function () { callback(err) })
- : callback()
+ activeFiles[absoluteName].push(resolve) // add this job to the queue
+ if (activeFiles[absoluteName].length === 1) resolve() // kick off the first one
+ }).then(function getRealPath () {
+ return new Promise(function (resolve) {
+ fs.realpath(filename, function (_, realname) {
+ truename = realname || filename
+ tmpfile = getTmpname(truename)
+ resolve()
+ })
})
- }
+ }).then(function stat () {
+ return new Promise(function stat (resolve) {
+ if (options.mode && options.chown) resolve()
+ else {
+ // Either mode or chown is not explicitly set
+ // Default behavior is to copy it from original file
+ fs.stat(truename, function (err, stats) {
+ if (err || !stats) resolve()
+ else {
+ options = Object.assign({}, options)
- // doing this instead of `fs.writeFile` in order to get the ability to
- // call `fsync`.
- function writeFileAsync (file, data, mode, encoding, cb) {
- fs.open(file, 'w', options.mode, function (err, fd) {
- if (err) return cb(err)
+ if (!options.mode) {
+ options.mode = stats.mode
+ }
+ if (!options.chown && process.getuid) {
+ options.chown = { uid: stats.uid, gid: stats.gid }
+ }
+ resolve()
+ }
+ })
+ }
+ })
+ }).then(function thenWriteFile () {
+ return new Promise(function (resolve, reject) {
+ fs.open(tmpfile, 'w', options.mode, function (err, _fd) {
+ fd = _fd
+ if (err) reject(err)
+ else resolve()
+ })
+ })
+ }).then(function write () {
+ return new Promise(function (resolve, reject) {
if (Buffer.isBuffer(data)) {
- return fs.write(fd, data, 0, data.length, 0, syncAndClose)
+ fs.write(fd, data, 0, data.length, 0, function (err) {
+ if (err) reject(err)
+ else resolve()
+ })
} else if (data != null) {
- return fs.write(fd, String(data), 0, String(encoding), syncAndClose)
- } else {
- return syncAndClose()
- }
- function syncAndClose (err) {
- if (err) return cb(err)
- if (options.fsync !== false) {
- fs.fsync(fd, function (err) {
- if (err) return cb(err)
- fs.close(fd, cb)
- })
- } else {
- fs.close(fd, cb)
- }
- }
+ fs.write(fd, String(data), 0, String(options.encoding || 'utf8'), function (err) {
+ if (err) reject(err)
+ else resolve()
+ })
+ } else resolve()
})
- }
+ }).then(function syncAndClose () {
+ if (options.fsync !== false) {
+ return new Promise(function (resolve, reject) {
+ fs.fsync(fd, function (err) {
+ if (err) reject(err)
+ else fs.close(fd, resolve)
+ })
+ })
+ }
+ }).then(function chown () {
+ if (options.chown) {
+ return new Promise(function (resolve, reject) {
+ fs.chown(tmpfile, options.chown.uid, options.chown.gid, function (err) {
+ if (err) reject(err)
+ else resolve()
+ })
+ })
+ }
+ }).then(function chmod () {
+ if (options.mode) {
+ return new Promise(function (resolve, reject) {
+ fs.chmod(tmpfile, options.mode, function (err) {
+ if (err) reject(err)
+ else resolve()
+ })
+ })
+ }
+ }).then(function rename () {
+ return new Promise(function (resolve, reject) {
+ fs.rename(tmpfile, truename, function (err) {
+ if (err) reject(err)
+ else resolve()
+ })
+ })
+ }).then(function success () {
+ removeOnExit()
+ callback()
+ }).catch(function fail (err) {
+ removeOnExit()
+ fs.unlink(tmpfile, function () {
+ callback(err)
+ })
+ }).then(function checkQueue () {
+ activeFiles[absoluteName].shift() // remove the element added by serializeSameFile
+ if (activeFiles[absoluteName].length > 0) {
+ activeFiles[absoluteName][0]() // start next job if one is pending
+ } else delete activeFiles[absoluteName]
+ })
}
function writeFileSync (filename, data, options) {
@@ -114,6 +176,7 @@ function writeFileSync (filename, data, options) {
}
}
+ var removeOnExit = onExit(cleanupOnExit(tmpfile))
var fd = fs.openSync(tmpfile, 'w', options.mode)
if (Buffer.isBuffer(data)) {
fs.writeSync(fd, data, 0, data.length, 0)
@@ -127,7 +190,9 @@ function writeFileSync (filename, data, options) {
if (options.chown) fs.chownSync(tmpfile, options.chown.uid, options.chown.gid)
if (options.mode) fs.chmodSync(tmpfile, options.mode)
fs.renameSync(tmpfile, filename)
+ removeOnExit()
} catch (err) {
+ removeOnExit()
try { fs.unlinkSync(tmpfile) } catch (e) {}
throw err
}