4 * Copyright (c) 2011-2015 KARASZI Istvan <github@spam.raszi.hu>
10 * Module dependencies.
14 path = require('path'),
15 crypto = require('crypto'),
16 tmpDir = require('os-tmpdir'),
17 _c = process.binding('constants');
21 * The working inner variables.
24 // store the actual TMP directory
27 // the random characters to choose from
28 RANDOM_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
30 TEMPLATE_PATTERN = /XXXXXX/,
34 CREATE_FLAGS = (_c.O_CREAT || _c.fs.O_CREAT) | (_c.O_EXCL || _c.fs.O_EXCL) | (_c.O_RDWR || _c.fs.O_RDWR),
36 EBADF = _c.EBADF || _c.os.errno.EBADF,
37 ENOENT = _c.ENOENT || _c.os.errno.ENOENT,
39 DIR_MODE = 448 /* 0700 */,
40 FILE_MODE = 384 /* 0600 */,
42 // this will hold the objects need to be removed on exit
45 _gracefulCleanup = false,
46 _uncaughtException = false;
49 * Random name generator based on crypto.
50 * Adapted from http://blog.tompawlak.org/how-to-generate-random-values-nodejs-javascript
52 * @param {Number} howMany
56 function _randomChars(howMany) {
61 // make sure that we do not fail because we ran out of entropy
63 rnd = crypto.randomBytes(howMany);
65 rnd = crypto.pseudoRandomBytes(howMany);
68 for (var i = 0; i < howMany; i++) {
69 value.push(RANDOM_CHARS[rnd[i] % RANDOM_CHARS.length]);
72 return value.join('');
76 * Checks whether the `obj` parameter is defined or not.
82 function _isUndefined(obj) {
83 return typeof obj === 'undefined';
87 * Parses the function arguments.
89 * This function helps to have optional arguments.
91 * @param {Object} options
92 * @param {Function} callback
95 function _parseArguments(options, callback) {
96 if (typeof options == 'function') {
99 options = callback || {},
101 } else if (typeof options == 'undefined') {
105 return [options, callback];
109 * Generates a new temporary name.
111 * @param {Object} opts
115 function _generateTmpName(opts) {
117 return path.join(opts.dir || _TMP, opts.name);
120 // mkstemps like template
122 return opts.template.replace(TEMPLATE_PATTERN, _randomChars(6));
125 // prefix and postfix
127 opts.prefix || 'tmp-',
133 return path.join(opts.dir || _TMP, name);
137 * Gets a temporary file name.
139 * @param {Object} options
140 * @param {Function} callback
143 function _getTmpName(options, callback) {
145 args = _parseArguments(options, callback),
148 tries = opts.tries || DEFAULT_TRIES;
150 if (isNaN(tries) || tries < 0)
151 return cb(new Error('Invalid tries'));
153 if (opts.template && !opts.template.match(TEMPLATE_PATTERN))
154 return cb(new Error('Invalid template provided'));
156 (function _getUniqueName() {
157 var name = _generateTmpName(opts);
159 // check whether the path exists then retry if needed
160 fs.stat(name, function (err) {
162 if (tries-- > 0) return _getUniqueName();
164 return cb(new Error('Could not get a unique tmp filename, max tries reached ' + name));
173 * Synchronous version of _getTmpName.
175 * @param {Object} options
179 function _getTmpNameSync(options) {
181 args = _parseArguments(options),
183 tries = opts.tries || DEFAULT_TRIES;
185 if (isNaN(tries) || tries < 0)
186 throw new Error('Invalid tries');
188 if (opts.template && !opts.template.match(TEMPLATE_PATTERN))
189 throw new Error('Invalid template provided');
192 var name = _generateTmpName(opts);
198 } while (tries-- > 0);
200 throw new Error('Could not get a unique tmp filename, max tries reached');
204 * Creates and opens a temporary file.
206 * @param {Object} options
207 * @param {Function} callback
210 function _createTmpFile(options, callback) {
212 args = _parseArguments(options, callback),
216 opts.postfix = (_isUndefined(opts.postfix)) ? '.tmp' : opts.postfix;
218 // gets a temporary filename
219 _getTmpName(opts, function _tmpNameCreated(err, name) {
220 if (err) return cb(err);
222 // create and open the file
223 fs.open(name, CREATE_FLAGS, opts.mode || FILE_MODE, function _fileCreated(err, fd) {
224 if (err) return cb(err);
226 if (opts.discardDescriptor) {
227 return fs.close(fd, function _discardCallback(err) {
229 // Low probability, and the file exists, so this could be
230 // ignored. If it isn't we certainly need to unlink the
231 // file, and if that fails too its error is more
240 cb(null, name, undefined, _prepareTmpFileRemoveCallback(name, -1, opts));
243 if (opts.detachDescriptor) {
244 return cb(null, name, fd, _prepareTmpFileRemoveCallback(name, -1, opts));
246 cb(null, name, fd, _prepareTmpFileRemoveCallback(name, fd, opts));
252 * Synchronous version of _createTmpFile.
254 * @param {Object} options
255 * @returns {Object} object consists of name, fd and removeCallback
258 function _createTmpFileSync(options) {
260 args = _parseArguments(options),
263 opts.postfix = opts.postfix || '.tmp';
265 var name = _getTmpNameSync(opts);
266 var fd = fs.openSync(name, CREATE_FLAGS, opts.mode || FILE_MODE);
271 removeCallback : _prepareTmpFileRemoveCallback(name, fd, opts)
276 * Removes files and folders in a directory recursively.
278 * @param {String} root
281 function _rmdirRecursiveSync(root) {
288 files = fs.readdirSync(dir);
290 for (var i = 0, length = files.length; i < length; i++) {
292 file = path.join(dir, files[i]),
293 stat = fs.lstatSync(file); // lstat so we don't recurse into symlinked directories
295 if (stat.isDirectory()) {
309 } while (dirs.length !== 0);
313 * Creates a temporary directory.
315 * @param {Object} options
316 * @param {Function} callback
319 function _createTmpDir(options, callback) {
321 args = _parseArguments(options, callback),
325 // gets a temporary filename
326 _getTmpName(opts, function _tmpNameCreated(err, name) {
327 if (err) return cb(err);
329 // create the directory
330 fs.mkdir(name, opts.mode || DIR_MODE, function _dirCreated(err) {
331 if (err) return cb(err);
333 cb(null, name, _prepareTmpDirRemoveCallback(name, opts));
339 * Synchronous version of _createTmpDir.
341 * @param {Object} options
342 * @returns {Object} object consists of name and removeCallback
345 function _createTmpDirSync(options) {
347 args = _parseArguments(options),
350 var name = _getTmpNameSync(opts);
351 fs.mkdirSync(name, opts.mode || DIR_MODE);
355 removeCallback : _prepareTmpDirRemoveCallback(name, opts)
360 * Prepares the callback for removal of the temporary file.
362 * @param {String} name
364 * @param {Object} opts
366 * @returns {Function} the callback
368 function _prepareTmpFileRemoveCallback(name, fd, opts) {
369 var removeCallback = _prepareRemoveCallback(function _removeCallback(fdPath) {
371 if (0 <= fdPath[0]) {
372 fs.closeSync(fdPath[0]);
376 // under some node/windows related circumstances, a temporary file
377 // may have not be created as expected or the file was already closed
378 // by the user, in which case we will simply ignore the error
379 if (e.errno != -EBADF && e.errno != -ENOENT) {
380 // reraise any unanticipated error
384 fs.unlinkSync(fdPath[1]);
388 _removeObjects.unshift(removeCallback);
391 return removeCallback;
395 * Prepares the callback for removal of the temporary directory.
397 * @param {String} name
398 * @param {Object} opts
399 * @returns {Function} the callback
402 function _prepareTmpDirRemoveCallback(name, opts) {
403 var removeFunction = opts.unsafeCleanup ? _rmdirRecursiveSync : fs.rmdirSync.bind(fs);
404 var removeCallback = _prepareRemoveCallback(removeFunction, name);
407 _removeObjects.unshift(removeCallback);
410 return removeCallback;
414 * Creates a guarded function wrapping the removeFunction call.
416 * @param {Function} removeFunction
417 * @param {Object} arg
418 * @returns {Function}
421 function _prepareRemoveCallback(removeFunction, arg) {
424 return function _cleanupCallback(next) {
426 var index = _removeObjects.indexOf(_cleanupCallback);
428 _removeObjects.splice(index, 1);
434 if (next) next(null);
439 * The garbage collector.
443 function _garbageCollector() {
444 if (_uncaughtException && !_gracefulCleanup) {
448 // the function being called removes itself from _removeObjects,
449 // loop until _removeObjects is empty
450 while (_removeObjects.length) {
452 _removeObjects[0].call(null);
459 function _setGracefulCleanup() {
460 _gracefulCleanup = true;
463 var version = process.versions.node.split('.').map(function (value) {
464 return parseInt(value, 10);
467 if (version[0] === 0 && (version[1] < 9 || version[1] === 9 && version[2] < 5)) {
468 process.addListener('uncaughtException', function _uncaughtExceptionThrown(err) {
469 _uncaughtException = true;
476 process.addListener('exit', function _exit(code) {
477 if (code) _uncaughtException = true;
481 // exporting all the needed methods
482 module.exports.tmpdir = _TMP;
483 module.exports.dir = _createTmpDir;
484 module.exports.dirSync = _createTmpDirSync;
485 module.exports.file = _createTmpFile;
486 module.exports.fileSync = _createTmpFileSync;
487 module.exports.tmpName = _getTmpName;
488 module.exports.tmpNameSync = _getTmpNameSync;
489 module.exports.setGracefulCleanup = _setGracefulCleanup;