use random tmpnames

This commit is contained in:
AJ ONeal 2015-12-14 22:07:54 -08:00
parent 5db9d90c90
commit b74d5f2644
3 changed files with 53 additions and 24 deletions

View File

@ -3,6 +3,9 @@ safe-replace
A micro-module for safely replacing a file. 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 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 ```bash
# create the new version # create the new version
touch keep.txt.new touch keep.txt.RANDOM.tmp
# remove the previous backup # remove the previous backup
rm -f keep.txt.bak rm -f keep.txt.bak
@ -19,7 +22,7 @@ rm -f keep.txt.bak
mv keep.txt keep.txt.bak mv keep.txt keep.txt.bak
# move the new version to the current # 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, 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 // let's say I wrote keep.txt.x7t7sq926.tmp with my own mechanism
safeReplace.commit('keep.txt').then(function () { safeReplace.commit('keep.txt.x7t7sq926.tmp', 'keep.txt').then(function () {
fs.readdir('.', function (nodes) { fs.readdir('.', function (nodes) {
console.log('file system nodes', nodes); console.log('file system nodes', nodes);
// keep.txt // keep.txt
@ -65,4 +68,14 @@ safeReplace.revert('keep.txt').then(function () {
// keep.txt.bak // 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
});
});
``` ```

View File

@ -2,54 +2,65 @@
var PromiseA = require('bluebird').Promise; var PromiseA = require('bluebird').Promise;
var fs = PromiseA.promisifyAll(require('fs')); var fs = PromiseA.promisifyAll(require('fs'));
var crypto = require('crypto');
function noop() { function noop() {
} }
function create(options) { function create(options) {
if (!options) { if (!options) {
options = {}; options = {};
} }
if (!options.new) { if (!options.tmp) {
options.new = 'new'; options.tmp = 'tmp';
} }
if (!options.bak) { if (!options.bak) {
options.bak = 'bak'; options.bak = 'bak';
} }
if (options.new === options.bak) { if (options.tmp === options.bak) {
throw new Error("'new' and 'bak' suffixes cannot be the same... duh"); throw new Error("'tmp' and 'bak' suffixes cannot be the same... duh");
} }
var newnamefn = options.newnamefn || function (pathname) { var tmpnamefn = options.tmpnamefn || function (pathname) {
return pathname + '.' + options.new; return pathname + '.' + crypto.randomBytes(8).toString('hex') + '.' + options.tmp;
}; };
var baknamefn = options.baknamefn || function (pathname) { var baknamefn = options.baknamefn || function (pathname) {
return pathname + '.' + options.bak; return pathname + '.' + options.bak;
}; };
/*
var namefn = options.namefn || function (pathname) { var namefn = options.namefn || function (pathname) {
return pathname; return pathname;
}; };
*/
var sfs = { var sfs = {
writeFile: function (filename, data, options) { writeFile: function (filename, data, options) {
//console.log(newnamefn(filename)); return sfs.stage(filename, data, options).then(function (tmpname) {
return fs.writeFileAsync(newnamefn(filename), data, options).then(function () { //console.log(filename);
//console.log(namefn(filename)); return sfs.commit(tmpname, filename);
return sfs.commit(namefn(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 // 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 // this may not exist
//console.log(namefn(filename), '->', baknamefn(filename)); //console.log(namefn(filename), '->', bakname);
return fs.renameAsync(namefn(filename), baknamefn(filename)).then(function () { return fs.renameAsync(filename, bakname).then(function () {
//console.log('created bak'); //console.log('created bak');
}, noop); }, noop);
}).then(function () { }).then(function () {
// this must be successful // this must be successful
//console.log(newnamefn(filename), '->', namefn(filename)); //console.log(filename, '->', filename);
return fs.renameAsync(newnamefn(filename), namefn(filename)).then(noop, function (err) { return fs.renameAsync(tmpname, filename).then(noop, function (err) {
//console.error(err); //console.error(err);
return sfs.revert(filename).then(function () { return sfs.revert(filename).then(function () {
return PromiseA.reject(err); return PromiseA.reject(err);
@ -59,14 +70,19 @@ function create(options) {
} }
, revert: function (filename) { , revert: function (filename) {
return new PromiseA(function (resolve, reject) { return new PromiseA(function (resolve, reject) {
var reader = fs.createReadStream(baknamefn(filename)); var bakname = baknamefn(filename);
var writer = fs.createWriteStream(namefn(filename)); var tmpname = tmpnamefn(filename);
var reader = fs.createReadStream(bakname);
var writer = fs.createWriteStream(tmpname);
reader.on('error', reject); reader.on('error', reject);
writer.on('error', reject); writer.on('error', reject);
reader.pipe(writer); reader.pipe(writer);
writer.on('close', resolve); writer.on('close', function () {
sfs.commit(tmpname, filename).then(resolve, reject);
});
}); });
} }
}; };

View File

@ -5,7 +5,7 @@ var safeReplace = require('./').create();
var fs = require('fs'); var fs = require('fs');
safeReplace.writeFile('keep.txt', 'my precious').then(function () { safeReplace.writeFile('keep.txt', 'my precious').then(function () {
fs.readdir('.', function (nodes) { fs.readdir('.', function (err, nodes) {
console.log('file system nodes', nodes); console.log('file system nodes', nodes);
}); });
}); });