Initial commit
[yaffs-website] / node_modules / tmp / lib / tmp.js
1 /*!
2  * Tmp
3  *
4  * Copyright (c) 2011-2015 KARASZI Istvan <github@spam.raszi.hu>
5  *
6  * MIT Licensed
7  */
8
9 /**
10  * Module dependencies.
11  */
12 var
13   fs     = require('fs'),
14   path   = require('path'),
15   os     = require('os'),
16   crypto = require('crypto'),
17   exists = fs.exists || path.exists,
18   existsSync = fs.existsSync || path.existsSync,
19   tmpDir = require('os-tmpdir'),
20   _c     = require('constants');
21
22
23 /**
24  * The working inner variables.
25  */
26 var
27   // store the actual TMP directory
28   _TMP = tmpDir(),
29
30   // the random characters to choose from
31   RANDOM_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
32
33   TEMPLATE_PATTERN = /XXXXXX/,
34
35   DEFAULT_TRIES = 3,
36
37   CREATE_FLAGS = _c.O_CREAT | _c.O_EXCL | _c.O_RDWR,
38
39   DIR_MODE = 448 /* 0700 */,
40   FILE_MODE = 384 /* 0600 */,
41
42   // this will hold the objects need to be removed on exit
43   _removeObjects = [],
44
45   _gracefulCleanup = false,
46   _uncaughtException = false;
47
48 /**
49  * Random name generator based on crypto.
50  * Adapted from http://blog.tompawlak.org/how-to-generate-random-values-nodejs-javascript
51  *
52  * @param {Number} howMany
53  * @return {String}
54  * @api private
55  */
56 function _randomChars(howMany) {
57   var
58     value = [],
59     rnd = null;
60
61   // make sure that we do not fail because we ran out of entropy
62   try {
63     rnd = crypto.randomBytes(howMany);
64   } catch (e) {
65     rnd = crypto.pseudoRandomBytes(howMany);
66   }
67
68   for (var i = 0; i < howMany; i++) {
69     value.push(RANDOM_CHARS[rnd[i] % RANDOM_CHARS.length]);
70   }
71
72   return value.join('');
73 }
74
75 /**
76  * Checks whether the `obj` parameter is defined or not.
77  *
78  * @param {Object} obj
79  * @return {Boolean}
80  * @api private
81  */
82 function _isUndefined(obj) {
83   return typeof obj === 'undefined';
84 }
85
86 /**
87  * Parses the function arguments.
88  *
89  * This function helps to have optional arguments.
90  *
91  * @param {Object} options
92  * @param {Function} callback
93  * @api private
94  */
95 function _parseArguments(options, callback) {
96   if (typeof options == 'function') {
97     var
98       tmp = options;
99       options = callback || {};
100       callback = tmp;
101   } else if (typeof options == 'undefined') {
102     options = {};
103   }
104
105   return [options, callback];
106 }
107
108 /**
109  * Generates a new temporary name.
110  *
111  * @param {Object} opts
112  * @returns {String}
113  * @api private
114  */
115 function _generateTmpName(opts) {
116   if (opts.name) {
117     return path.join(opts.dir || _TMP, opts.name);
118   }
119
120   // mkstemps like template
121   if (opts.template) {
122     return opts.template.replace(TEMPLATE_PATTERN, _randomChars(6));
123   }
124
125   // prefix and postfix
126   var name = [
127     opts.prefix || 'tmp-',
128     process.pid,
129     _randomChars(12),
130     opts.postfix || ''
131   ].join('');
132
133   return path.join(opts.dir || _TMP, name);
134 }
135
136 /**
137  * Gets a temporary file name.
138  *
139  * @param {Object} options
140  * @param {Function} callback
141  * @api private
142  */
143 function _getTmpName(options, callback) {
144   var
145     args = _parseArguments(options, callback),
146     opts = args[0],
147     cb = args[1],
148     tries = opts.tries || DEFAULT_TRIES;
149
150   if (isNaN(tries) || tries < 0)
151     return cb(new Error('Invalid tries'));
152
153   if (opts.template && !opts.template.match(TEMPLATE_PATTERN))
154     return cb(new Error('Invalid template provided'));
155
156   (function _getUniqueName() {
157     var name = _generateTmpName(opts);
158
159     // check whether the path exists then retry if needed
160     exists(name, function _pathExists(pathExists) {
161       if (pathExists) {
162         if (tries-- > 0) return _getUniqueName();
163
164         return cb(new Error('Could not get a unique tmp filename, max tries reached ' + name));
165       }
166
167       cb(null, name);
168     });
169   }());
170 }
171
172 /**
173  * Synchronous version of _getTmpName.
174  *
175  * @param {Object} options
176  * @returns {String}
177  * @api private
178  */
179 function _getTmpNameSync(options) {
180   var
181     args = _parseArguments(options),
182     opts = args[0],
183     tries = opts.tries || DEFAULT_TRIES;
184
185   if (isNaN(tries) || tries < 0)
186     throw new Error('Invalid tries');
187
188   if (opts.template && !opts.template.match(TEMPLATE_PATTERN))
189     throw new Error('Invalid template provided');
190
191   do {
192     var name = _generateTmpName(opts);
193     if (!existsSync(name)) {
194       return name;
195     }
196   } while (tries-- > 0);
197
198   throw new Error('Could not get a unique tmp filename, max tries reached');
199 }
200
201 /**
202  * Creates and opens a temporary file.
203  *
204  * @param {Object} options
205  * @param {Function} callback
206  * @api public
207  */
208 function _createTmpFile(options, callback) {
209   var
210     args = _parseArguments(options, callback),
211     opts = args[0],
212     cb = args[1];
213
214     opts.postfix = (_isUndefined(opts.postfix)) ? '.tmp' : opts.postfix;
215
216   // gets a temporary filename
217   _getTmpName(opts, function _tmpNameCreated(err, name) {
218     if (err) return cb(err);
219
220     // create and open the file
221     fs.open(name, CREATE_FLAGS, opts.mode || FILE_MODE, function _fileCreated(err, fd) {
222       if (err) return cb(err);
223
224       cb(null, name, fd, _prepareTmpFileRemoveCallback(name, fd, opts));
225     });
226   });
227 }
228
229 /**
230  * Synchronous version of _createTmpFile.
231  *
232  * @param {Object} options
233  * @returns {Object} object consists of name, fd and removeCallback
234  * @api private
235  */
236 function _createTmpFileSync(options) {
237   var
238     args = _parseArguments(options),
239     opts = args[0];
240
241     opts.postfix = opts.postfix || '.tmp';
242
243   var name = _getTmpNameSync(opts);
244   var fd = fs.openSync(name, CREATE_FLAGS, opts.mode || FILE_MODE);
245
246   return {
247     name : name,
248     fd : fd,
249     removeCallback : _prepareTmpFileRemoveCallback(name, fd, opts)
250   };
251 }
252
253 /**
254  * Removes files and folders in a directory recursively.
255  *
256  * @param {String} root
257  * @api private
258  */
259 function _rmdirRecursiveSync(root) {
260   var dirs = [root];
261
262   do {
263     var
264       dir = dirs.pop(),
265       deferred = false,
266       files = fs.readdirSync(dir);
267
268     for (var i = 0, length = files.length; i < length; i++) {
269       var
270         file = path.join(dir, files[i]),
271         stat = fs.lstatSync(file); // lstat so we don't recurse into symlinked directories
272
273       if (stat.isDirectory()) {
274         if (!deferred) {
275           deferred = true;
276           dirs.push(dir);
277         }  
278         dirs.push(file);
279       } else {
280         fs.unlinkSync(file);
281       }
282     }
283
284     if (!deferred) {
285       fs.rmdirSync(dir);
286     }
287   } while (dirs.length !== 0);
288 }
289
290 /**
291  * Creates a temporary directory.
292  *
293  * @param {Object} options
294  * @param {Function} callback
295  * @api public
296  */
297 function _createTmpDir(options, callback) {
298   var
299     args = _parseArguments(options, callback),
300     opts = args[0],
301     cb = args[1];
302
303   // gets a temporary filename
304   _getTmpName(opts, function _tmpNameCreated(err, name) {
305     if (err) return cb(err);
306
307     // create the directory
308     fs.mkdir(name, opts.mode || DIR_MODE, function _dirCreated(err) {
309       if (err) return cb(err);
310
311       cb(null, name, _prepareTmpDirRemoveCallback(name, opts));
312     });
313   });
314 }
315
316 /**
317  * Synchronous version of _createTmpDir.
318  *
319  * @param {Object} options
320  * @returns {Object} object consists of name and removeCallback
321  * @api private
322  */
323 function _createTmpDirSync(options) {
324   var
325     args = _parseArguments(options),
326     opts = args[0];
327
328   var name = _getTmpNameSync(opts);
329   fs.mkdirSync(name, opts.mode || DIR_MODE);
330
331   return {
332     name : name,
333     removeCallback : _prepareTmpDirRemoveCallback(name, opts)
334   };
335 }
336
337 /**
338  * Prepares the callback for removal of the temporary file.
339  *
340  * @param {String} name
341  * @param {int} fd
342  * @param {Object} opts
343  * @api private
344  * @returns {Function} the callback
345  */
346 function _prepareTmpFileRemoveCallback(name, fd, opts) {
347   var removeCallback = _prepareRemoveCallback(function _removeCallback(fdPath) {
348     try {
349       fs.closeSync(fdPath[0]);
350     }
351     catch (e) {
352       // under some node/windows related circumstances, a temporary file 
353       // may have not be created as expected or the file was already closed
354       // by the user, in which case we will simply ignore the error
355       if (e.errno != -_c.EBADF && e.errno != -c.ENOENT) {
356         // reraise any unanticipated error
357         throw e;
358       }
359     }
360     fs.unlinkSync(fdPath[1]);
361   }, [fd, name]);
362
363   if (!opts.keep) {
364     _removeObjects.unshift(removeCallback);
365   }
366
367   return removeCallback;
368 }
369
370 /**
371  * Prepares the callback for removal of the temporary directory.
372  *
373  * @param {String} name
374  * @param {Object} opts
375  * @returns {Function} the callback
376  * @api private
377  */
378 function _prepareTmpDirRemoveCallback(name, opts) {
379   var removeFunction = opts.unsafeCleanup ? _rmdirRecursiveSync : fs.rmdirSync.bind(fs);
380   var removeCallback = _prepareRemoveCallback(removeFunction, name);
381
382   if (!opts.keep) {
383     _removeObjects.unshift(removeCallback);
384   }
385
386   return removeCallback;
387 }
388
389 /**
390  * Creates a guarded function wrapping the removeFunction call.
391  *
392  * @param {Function} removeFunction
393  * @param {Object} arg
394  * @returns {Function}
395  * @api private
396  */
397 function _prepareRemoveCallback(removeFunction, arg) {
398   var called = false;
399
400   return function _cleanupCallback() {
401     if (called) return;
402
403     var index = _removeObjects.indexOf(removeFunction);
404     if (index >= 0) {
405       _removeObjects.splice(index, 1);
406     }
407
408     called = true;
409     removeFunction(arg);
410   };
411 }
412
413 /**
414  * The garbage collector.
415  *
416  * @api private
417  */
418 function _garbageCollector() {
419   if (_uncaughtException && !_gracefulCleanup) {
420     return;
421   }
422
423   for (var i = 0, length = _removeObjects.length; i < length; i++) {
424     try {
425       _removeObjects[i].call(null);
426     } catch (e) {
427       // already removed?
428     }
429   }
430 }
431
432 function _setGracefulCleanup() {
433   _gracefulCleanup = true;
434 }
435
436 var version = process.versions.node.split('.').map(function (value) {
437   return parseInt(value, 10);
438 });
439
440 if (version[0] === 0 && (version[1] < 9 || version[1] === 9 && version[2] < 5)) {
441   process.addListener('uncaughtException', function _uncaughtExceptionThrown(err) {
442     _uncaughtException = true;
443     _garbageCollector();
444
445     throw err;
446   });
447 }
448
449 process.addListener('exit', function _exit(code) {
450   if (code) _uncaughtException = true;
451   _garbageCollector();
452 });
453
454 // exporting all the needed methods
455 module.exports.tmpdir = _TMP;
456 module.exports.dir = _createTmpDir;
457 module.exports.dirSync = _createTmpDirSync;
458 module.exports.file = _createTmpFile;
459 module.exports.fileSync = _createTmpFileSync;
460 module.exports.tmpName = _getTmpName;
461 module.exports.tmpNameSync = _getTmpNameSync;
462 module.exports.setGracefulCleanup = _setGracefulCleanup;