tmp.js 10 KB

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