-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 5ef6f37
Showing
5 changed files
with
217 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
*~ | ||
DEADJOE | ||
.#* | ||
node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
write-file-atomic | ||
----------------- | ||
|
||
This is an extension for node's `fs.writeFile` that makes its operation | ||
atomic allows you to include uid/gid for the final file as well. It does | ||
this by initially writing to a temporary file (your filename, followed by | ||
".writeFile.atomic"), chowning it to the uid and gid you specified (if you | ||
specified any) and finally renames it to your filename. | ||
|
||
### var writeFileAtomic = require('write-file-atomic')<br>writeFileAtomic(filename, data, [options], callback) | ||
|
||
* filename **String** | ||
* data **String** | **Buffer** | ||
* options **Object** | ||
* chown **Object** | ||
* uid **Number** | ||
* gid **Number** | ||
* encoding **String** | **Null** default = 'utf8' | ||
* mode **Number** default = 438 (aka 0666 in Octal) | ||
callback **Function** | ||
|
||
Atomically and asynchronously writes data to a file, replacing the file if it already | ||
exists. data can be a string or a buffer. | ||
|
||
If provided, the **chown** option requires both **uid** and **gid** properties or else | ||
you'll get an error. | ||
|
||
The **encoding** option is ignored if **data** is a buffer. It defaults to 'utf8'. | ||
|
||
Example: | ||
|
||
```javascript | ||
fs.writeFile('message.txt', 'Hello Node', {chown:{uid:100,gid:50}}, function (err) { | ||
if (err) throw err; | ||
console.log('It\'s saved!'); | ||
}); | ||
``` | ||
|
||
### var writeFileAtomicSync = require('write-file-atomic').sync<br>writeFileAtomicSync(filename, data, [options]) | ||
|
||
The synchronous version of **writeFileAtomic**. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
'use strict' | ||
var fs = require('fs'); | ||
var chain = require('slide').chain; | ||
var crypto = require('crypto'); | ||
|
||
var md5hex = function () { | ||
var hash = crypto.createHash('md5'); | ||
for (var ii=0; ii<arguments.length; ++ii) hash.update(''+arguments[ii]) | ||
return hash.digest('hex') | ||
} | ||
var invocations = 0; | ||
var getTmpname = function (filename) { | ||
return filename + "." + md5hex(__filename, process.pid, ++invocations) | ||
} | ||
|
||
module.exports = function writeFile(filename, data, options, callback) { | ||
if (options instanceof Function) { | ||
callback = options; | ||
options = null; | ||
} | ||
if (!options) options = {}; | ||
var tmpfile = getTmpname(filename); | ||
chain([ | ||
[fs, fs.writeFile, tmpfile, data, options], | ||
options.chown && [fs, fs.chown, tmpfile, options.chown.uid, options.chown.gid], | ||
[fs, fs.rename, tmpfile, filename] | ||
], function (err) { | ||
err ? fs.unlink(tmpfile, function () { callback(err) }) | ||
: callback() | ||
}) | ||
} | ||
|
||
module.exports.sync = function writeFileSync(filename, data, options) { | ||
if (!options) options = {}; | ||
var tmpfile = getTmpname(filename); | ||
try { | ||
fs.writeFileSync(tmpfile, data, options); | ||
if (options.chown) fs.chownSync(tmpfile, options.chown.uid, options.chown.gid); | ||
fs.renameSync(tmpfile, filename); | ||
} | ||
catch (err) { | ||
try { fs.unlinkSync(tmpfile) } catch(e) {} | ||
throw err; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
{ | ||
"name": "write-file-atomic", | ||
"version": "1.0.0", | ||
"description": "Write files in an atomic fashion w/configurable ownership", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "tap test/*.js" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "[email protected]:iarna/write-file-atomic.git" | ||
}, | ||
"keywords": [ | ||
"writeFile", | ||
"atomic" | ||
], | ||
"author": "Rebecca Turner <[email protected]> (http://re-becca.org)", | ||
"license": "ISC", | ||
"bugs": { | ||
"url": "https://github.com/iarna/write-file-atomic/issues" | ||
}, | ||
"homepage": "https://github.com/iarna/write-file-atomic", | ||
"dependencies": { | ||
"slide": "^1.1.5" | ||
}, | ||
"devDependencies": { | ||
"require-inject": "^1.1.0", | ||
"tap": "^0.4.12" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
"use strict"; | ||
var test = require('tap').test; | ||
var requireInject = require('require-inject'); | ||
var writeFileAtomic = requireInject('../index', { | ||
fs: { | ||
writeFile: function (tmpfile, data, options, cb) { | ||
if (/nowrite/.test(tmpfile)) return cb('ENOWRITE'); | ||
cb(); | ||
}, | ||
chown: function (tmpfile, uid, gid, cb) { | ||
if (/nochown/.test(tmpfile)) return cb('ENOCHOWN'); | ||
cb(); | ||
}, | ||
rename: function (tmpfile, filename, cb) { | ||
if (/norename/.test(tmpfile)) return cb('ENORENAME'); | ||
cb(); | ||
}, | ||
unlink: function (tmpfile, cb) { | ||
if (/nounlink/.test(tmpfile)) return cb('ENOUNLINK'); | ||
cb(); | ||
}, | ||
writeFileSync: function (tmpfile, data, options) { | ||
if (/nowrite/.test(tmpfile)) throw 'ENOWRITE'; | ||
}, | ||
chownSync: function (tmpfile, uid, gid) { | ||
if (/nochown/.test(tmpfile)) throw 'ENOCHOWN'; | ||
}, | ||
renameSync: function (tmpfile, filename) { | ||
if (/norename/.test(tmpfile)) throw 'ENORENAME'; | ||
}, | ||
unlinkSync: function (tmpfile) { | ||
if (/nounlink/.test(tmpfile)) throw 'ENOUNLINK'; | ||
}, | ||
} | ||
}); | ||
var writeFileAtomicSync = writeFileAtomic.sync; | ||
|
||
test('async tests', function (t) { | ||
t.plan(7); | ||
writeFileAtomic('good', 'test', {mode: '0777'}, function (err) { | ||
t.notOk(err, 'No errors occur when passing in options'); | ||
}); | ||
writeFileAtomic('good', 'test', function (err) { | ||
t.notOk(err, 'No errors occur when NOT passing in options'); | ||
}); | ||
writeFileAtomic('nowrite', 'test', function (err) { | ||
t.is(err, 'ENOWRITE', 'writeFile failures propagate'); | ||
}); | ||
writeFileAtomic('nochown', 'test', {chown: {uid:100,gid:100}}, function (err) { | ||
t.is(err, 'ENOCHOWN', 'Chown failures propagate'); | ||
}); | ||
writeFileAtomic('nochown', 'test', function (err) { | ||
t.notOk(err, 'No attempt to chown when no uid/gid passed in'); | ||
}); | ||
writeFileAtomic('norename', 'test', function (err) { | ||
t.is(err, 'ENORENAME', 'Rename errors propagate'); | ||
}); | ||
writeFileAtomic('norename nounlink', 'test', function (err) { | ||
t.is(err, 'ENORENAME', 'Failure to unlink the temp file does not clobber the original error'); | ||
}); | ||
}); | ||
|
||
test('sync tests', function (t) { | ||
t.plan(7); | ||
var throws = function (shouldthrow, msg, todo) { | ||
var err; | ||
try { todo() } catch (e) { err = e } | ||
t.is(shouldthrow,err,msg); | ||
} | ||
var noexception = function (msg, todo) { | ||
var err; | ||
try { todo() } catch (e) { err = e } | ||
t.notOk(err,msg); | ||
} | ||
|
||
noexception('No errors occur when passing in options',function (){ | ||
writeFileAtomicSync('good', 'test', {mode: '0777'}); | ||
}) | ||
noexception('No errors occur when NOT passing in options',function (){ | ||
writeFileAtomicSync('good', 'test'); | ||
}); | ||
throws('ENOWRITE', 'writeFile failures propagate', function () { | ||
writeFileAtomicSync('nowrite', 'test'); | ||
}); | ||
throws('ENOCHOWN', 'Chown failures propagate', function () { | ||
writeFileAtomicSync('nochown', 'test', {chown: {uid:100,gid:100}}); | ||
}); | ||
noexception('No attempt to chown when no uid/gid passed in', function (){ | ||
writeFileAtomicSync('nochown', 'test'); | ||
}); | ||
throws('ENORENAME', 'Rename errors propagate', function (){ | ||
writeFileAtomicSync('norename', 'test'); | ||
}); | ||
throws('ENORENAME', 'Failure to unlink the temp file does not clobber the original error', function (){ | ||
writeFileAtomicSync('norename nounlink', 'test'); | ||
}); | ||
}); |