From f90c7dd1c36ca2f5b52f5902d072b3a5ec3f1419 Mon Sep 17 00:00:00 2001 From: Rebecca Turner Date: Thu, 5 Jan 2017 16:37:28 -0800 Subject: [PATCH] Stop unsymlinking destinations that are symlinks Fixes: #5 --- index.js | 10 ++++++++++ test/basic.js | 6 ++++++ test/integration.js | 26 ++++++++++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/index.js b/index.js index 16f93f7..dbdf975 100644 --- a/index.js +++ b/index.js @@ -15,6 +15,11 @@ module.exports = function writeFile (filename, data, options, callback) { 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) { @@ -51,6 +56,11 @@ module.exports = function writeFile (filename, data, options, callback) { module.exports.sync = function writeFileSync (filename, data, options) { if (!options) options = {} + try { + filename = fs.realpathSync(filename) + } catch (ex) { + // it's ok, it'll happen on a not yet existing file + } var tmpfile = getTmpname(filename) try { diff --git a/test/basic.js b/test/basic.js index 2e63dac..6e2c5d0 100644 --- a/test/basic.js +++ b/test/basic.js @@ -3,6 +3,9 @@ var test = require('tap').test var requireInject = require('require-inject') var writeFileAtomic = requireInject('../index', { 'graceful-fs': { + realpath: function (filename, cb) { + return cb(null, filename) + }, writeFile: function (tmpfile, data, options, cb) { if (/nowrite/.test(tmpfile)) return cb(new Error('ENOWRITE')) cb() @@ -27,6 +30,9 @@ var writeFileAtomic = requireInject('../index', { if (/nostat/.test(tmpfile)) return cb(new Error('ENOSTAT')) cb() }, + realpathSync: function (filename, cb) { + return filename + }, writeFileSync: function (tmpfile, data, options) { if (/nowrite/.test(tmpfile)) throw new Error('ENOWRITE') }, diff --git a/test/integration.js b/test/integration.js index ada5a07..1c1d4db 100644 --- a/test/integration.js +++ b/test/integration.js @@ -84,6 +84,20 @@ test('writes simple file (async)', function (t) { }) }) +test('writes to symlinks without clobbering (async)', function (t) { + t.plan(5) + var file = tmpFile() + var link = tmpFile() + fs.writeFileSync(file, '42') + fs.symlinkSync(file, link) + didWriteFileAtomic(t, currentUser(), link, '43', function (err) { + t.ifError(err, 'no error') + t.is(readFile(file), '43', 'target content ok') + t.is(readFile(link), '43', 'link content ok') + t.ok(fs.lstatSync(link).isSymbolicLink(), 'link is link') + }) +}) + test('runs chown on given file (async)', function (t) { var file = tmpFile() didWriteFileAtomic(t, {uid: 42, gid: 43}, file, '42', { chown: { uid: 42, gid: 43 } }, function (err) { @@ -156,6 +170,18 @@ test('writes simple file (sync)', function (t) { t.is(readFile(file), '42') }) +test('writes to symlinks without clobbering (sync)', function (t) { + t.plan(4) + var file = tmpFile() + var link = tmpFile() + fs.writeFileSync(file, '42') + fs.symlinkSync(file, link) + didWriteFileAtomicSync(t, currentUser(), link, '43') + t.is(readFile(file), '43', 'target content ok') + t.is(readFile(link), '43', 'link content ok') + t.ok(fs.lstatSync(link).isSymbolicLink(), 'link is link') +}) + test('runs chown on given file (sync)', function (t) { t.plan(1) var file = tmpFile()