From b74d5f2644191d37dbb16d110c0d4253e3b883a9 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 14 Dec 2015 22:07:54 -0800 Subject: [PATCH] use random tmpnames --- README.md | 21 +++++++++++++++++---- index.js | 54 +++++++++++++++++++++++++++++++++++------------------- test.js | 2 +- 3 files changed, 53 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index fd03ff4..04883c5 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ safe-replace A micro-module for safely replacing a file. +This is intended to be generally safe even when a function that writes a file +is accidentally called twice (as may happen with node cluster). + Commandline Reference --------------------- @@ -10,7 +13,7 @@ If I want to safely replace a file with a new version, I would do so like this: ```bash # create the new version -touch keep.txt.new +touch keep.txt.RANDOM.tmp # remove the previous backup rm -f keep.txt.bak @@ -19,7 +22,7 @@ rm -f keep.txt.bak mv keep.txt keep.txt.bak # move the new version to the current -mv keep.txt.new keep.txt +mv keep.txt.RANDOM.tmp keep.txt ``` If `keep.txt` became corrupt and I wanted to use the backup, @@ -48,8 +51,8 @@ safeReplace.writeFile('keep.txt', data, 'ascii').then(function () { }); }); -// let's say I wrote keep.txt.new with my own mechanism -safeReplace.commit('keep.txt').then(function () { +// let's say I wrote keep.txt.x7t7sq926.tmp with my own mechanism +safeReplace.commit('keep.txt.x7t7sq926.tmp', 'keep.txt').then(function () { fs.readdir('.', function (nodes) { console.log('file system nodes', nodes); // keep.txt @@ -65,4 +68,14 @@ safeReplace.revert('keep.txt').then(function () { // keep.txt.bak }); }); + +// let's say I want to write a tmp file and not commit it... weird +safeReplace.stage('keep.txt', data, 'ascii').then(function (tmpname) { + fs.readdir('.', function (nodes) { + console.log('file system nodes', nodes); + // keep.txt + // keep.txt.bak + // keep.txt.ac71teh8mja.tmp + }); +}); ``` diff --git a/index.js b/index.js index 41b6e31..f592058 100644 --- a/index.js +++ b/index.js @@ -2,54 +2,65 @@ var PromiseA = require('bluebird').Promise; var fs = PromiseA.promisifyAll(require('fs')); +var crypto = require('crypto'); function noop() { } function create(options) { + if (!options) { options = {}; } - if (!options.new) { - options.new = 'new'; + if (!options.tmp) { + options.tmp = 'tmp'; } if (!options.bak) { options.bak = 'bak'; } - if (options.new === options.bak) { - throw new Error("'new' and 'bak' suffixes cannot be the same... duh"); + if (options.tmp === options.bak) { + throw new Error("'tmp' and 'bak' suffixes cannot be the same... duh"); } - var newnamefn = options.newnamefn || function (pathname) { - return pathname + '.' + options.new; + var tmpnamefn = options.tmpnamefn || function (pathname) { + return pathname + '.' + crypto.randomBytes(8).toString('hex') + '.' + options.tmp; }; var baknamefn = options.baknamefn || function (pathname) { return pathname + '.' + options.bak; }; + /* var namefn = options.namefn || function (pathname) { return pathname; }; + */ var sfs = { writeFile: function (filename, data, options) { - //console.log(newnamefn(filename)); - return fs.writeFileAsync(newnamefn(filename), data, options).then(function () { - //console.log(namefn(filename)); - return sfs.commit(namefn(filename)); + return sfs.stage(filename, data, options).then(function (tmpname) { + //console.log(filename); + return sfs.commit(tmpname, filename); }); } - , commit: function (filename) { + , stage: function (filename, data, options) { + var tmpname = tmpnamefn(filename); + //console.log(tmpname); + return fs.writeFileAsync(tmpname, data, options).then(function () { + return tmpname; + }); + } + , commit: function (tmpname, filename) { + var bakname = baknamefn(filename); // this may not exist - return fs.unlinkAsync(baknamefn(filename)).then(noop, noop).then(function () { + return fs.unlinkAsync(bakname).then(noop, noop).then(function () { // this may not exist - //console.log(namefn(filename), '->', baknamefn(filename)); - return fs.renameAsync(namefn(filename), baknamefn(filename)).then(function () { + //console.log(namefn(filename), '->', bakname); + return fs.renameAsync(filename, bakname).then(function () { //console.log('created bak'); }, noop); }).then(function () { // this must be successful - //console.log(newnamefn(filename), '->', namefn(filename)); - return fs.renameAsync(newnamefn(filename), namefn(filename)).then(noop, function (err) { + //console.log(filename, '->', filename); + return fs.renameAsync(tmpname, filename).then(noop, function (err) { //console.error(err); return sfs.revert(filename).then(function () { return PromiseA.reject(err); @@ -59,14 +70,19 @@ function create(options) { } , revert: function (filename) { return new PromiseA(function (resolve, reject) { - var reader = fs.createReadStream(baknamefn(filename)); - var writer = fs.createWriteStream(namefn(filename)); + var bakname = baknamefn(filename); + var tmpname = tmpnamefn(filename); + + var reader = fs.createReadStream(bakname); + var writer = fs.createWriteStream(tmpname); reader.on('error', reject); writer.on('error', reject); reader.pipe(writer); - writer.on('close', resolve); + writer.on('close', function () { + sfs.commit(tmpname, filename).then(resolve, reject); + }); }); } }; diff --git a/test.js b/test.js index ee89f25..2641a3a 100644 --- a/test.js +++ b/test.js @@ -5,7 +5,7 @@ var safeReplace = require('./').create(); var fs = require('fs'); safeReplace.writeFile('keep.txt', 'my precious').then(function () { - fs.readdir('.', function (nodes) { + fs.readdir('.', function (err, nodes) { console.log('file system nodes', nodes); }); });