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