tmp.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842
  1. /*!
  2. * Tmp
  3. *
  4. * Copyright (c) 2011-2017 KARASZI Istvan <github@spam.raszi.hu>
  5. *
  6. * MIT Licensed
  7. */
  8. /*
  9. * Module dependencies.
  10. */
  11. const fs = require('fs');
  12. const os = require('os');
  13. const path = require('path');
  14. const crypto = require('crypto');
  15. const _c = { fs: fs.constants, os: os.constants };
  16. /*
  17. * The working inner variables.
  18. */
  19. const // the random characters to choose from
  20. RANDOM_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
  21. TEMPLATE_PATTERN = /XXXXXX/,
  22. DEFAULT_TRIES = 3,
  23. CREATE_FLAGS = (_c.O_CREAT || _c.fs.O_CREAT) | (_c.O_EXCL || _c.fs.O_EXCL) | (_c.O_RDWR || _c.fs.O_RDWR),
  24. // constants are off on the windows platform and will not match the actual errno codes
  25. IS_WIN32 = os.platform() === 'win32',
  26. EBADF = _c.EBADF || _c.os.errno.EBADF,
  27. ENOENT = _c.ENOENT || _c.os.errno.ENOENT,
  28. DIR_MODE = 0o700 /* 448 */,
  29. FILE_MODE = 0o600 /* 384 */,
  30. EXIT = 'exit',
  31. // this will hold the objects need to be removed on exit
  32. _removeObjects = [],
  33. // API change in fs.rmdirSync leads to error when passing in a second parameter, e.g. the callback
  34. FN_RMDIR_SYNC = fs.rmdirSync.bind(fs);
  35. let _gracefulCleanup = false;
  36. /**
  37. * Recursively remove a directory and its contents.
  38. *
  39. * @param {string} dirPath path of directory to remove
  40. * @param {Function} callback
  41. * @private
  42. */
  43. function rimraf(dirPath, callback) {
  44. return fs.rm(dirPath, { recursive: true }, callback);
  45. }
  46. /**
  47. * Recursively remove a directory and its contents, synchronously.
  48. *
  49. * @param {string} dirPath path of directory to remove
  50. * @private
  51. */
  52. function FN_RIMRAF_SYNC(dirPath) {
  53. return fs.rmSync(dirPath, { recursive: true });
  54. }
  55. /**
  56. * Gets a temporary file name.
  57. *
  58. * @param {(Options|tmpNameCallback)} options options or callback
  59. * @param {?tmpNameCallback} callback the callback function
  60. */
  61. function tmpName(options, callback) {
  62. const args = _parseArguments(options, callback),
  63. opts = args[0],
  64. cb = args[1];
  65. _assertAndSanitizeOptions(opts, function (err, sanitizedOptions) {
  66. if (err) return cb(err);
  67. let tries = sanitizedOptions.tries;
  68. (function _getUniqueName() {
  69. try {
  70. const name = _generateTmpName(sanitizedOptions);
  71. // check whether the path exists then retry if needed
  72. fs.stat(name, function (err) {
  73. /* istanbul ignore else */
  74. if (!err) {
  75. /* istanbul ignore else */
  76. if (tries-- > 0) return _getUniqueName();
  77. return cb(new Error('Could not get a unique tmp filename, max tries reached ' + name));
  78. }
  79. cb(null, name);
  80. });
  81. } catch (err) {
  82. cb(err);
  83. }
  84. })();
  85. });
  86. }
  87. /**
  88. * Synchronous version of tmpName.
  89. *
  90. * @param {Object} options
  91. * @returns {string} the generated random name
  92. * @throws {Error} if the options are invalid or could not generate a filename
  93. */
  94. function tmpNameSync(options) {
  95. const args = _parseArguments(options),
  96. opts = args[0];
  97. const sanitizedOptions = _assertAndSanitizeOptionsSync(opts);
  98. let tries = sanitizedOptions.tries;
  99. do {
  100. const name = _generateTmpName(sanitizedOptions);
  101. try {
  102. fs.statSync(name);
  103. } catch (e) {
  104. return name;
  105. }
  106. } while (tries-- > 0);
  107. throw new Error('Could not get a unique tmp filename, max tries reached');
  108. }
  109. /**
  110. * Creates and opens a temporary file.
  111. *
  112. * @param {(Options|null|undefined|fileCallback)} options the config options or the callback function or null or undefined
  113. * @param {?fileCallback} callback
  114. */
  115. function file(options, callback) {
  116. const args = _parseArguments(options, callback),
  117. opts = args[0],
  118. cb = args[1];
  119. // gets a temporary filename
  120. tmpName(opts, function _tmpNameCreated(err, name) {
  121. /* istanbul ignore else */
  122. if (err) return cb(err);
  123. // create and open the file
  124. fs.open(name, CREATE_FLAGS, opts.mode || FILE_MODE, function _fileCreated(err, fd) {
  125. /* istanbu ignore else */
  126. if (err) return cb(err);
  127. if (opts.discardDescriptor) {
  128. return fs.close(fd, function _discardCallback(possibleErr) {
  129. // the chance of getting an error on close here is rather low and might occur in the most edgiest cases only
  130. return cb(possibleErr, name, undefined, _prepareTmpFileRemoveCallback(name, -1, opts, false));
  131. });
  132. } else {
  133. // detachDescriptor passes the descriptor whereas discardDescriptor closes it, either way, we no longer care
  134. // about the descriptor
  135. const discardOrDetachDescriptor = opts.discardDescriptor || opts.detachDescriptor;
  136. cb(null, name, fd, _prepareTmpFileRemoveCallback(name, discardOrDetachDescriptor ? -1 : fd, opts, false));
  137. }
  138. });
  139. });
  140. }
  141. /**
  142. * Synchronous version of file.
  143. *
  144. * @param {Options} options
  145. * @returns {FileSyncObject} object consists of name, fd and removeCallback
  146. * @throws {Error} if cannot create a file
  147. */
  148. function fileSync(options) {
  149. const args = _parseArguments(options),
  150. opts = args[0];
  151. const discardOrDetachDescriptor = opts.discardDescriptor || opts.detachDescriptor;
  152. const name = tmpNameSync(opts);
  153. let fd = fs.openSync(name, CREATE_FLAGS, opts.mode || FILE_MODE);
  154. /* istanbul ignore else */
  155. if (opts.discardDescriptor) {
  156. fs.closeSync(fd);
  157. fd = undefined;
  158. }
  159. return {
  160. name: name,
  161. fd: fd,
  162. removeCallback: _prepareTmpFileRemoveCallback(name, discardOrDetachDescriptor ? -1 : fd, opts, true)
  163. };
  164. }
  165. /**
  166. * Creates a temporary directory.
  167. *
  168. * @param {(Options|dirCallback)} options the options or the callback function
  169. * @param {?dirCallback} callback
  170. */
  171. function dir(options, callback) {
  172. const args = _parseArguments(options, callback),
  173. opts = args[0],
  174. cb = args[1];
  175. // gets a temporary filename
  176. tmpName(opts, function _tmpNameCreated(err, name) {
  177. /* istanbul ignore else */
  178. if (err) return cb(err);
  179. // create the directory
  180. fs.mkdir(name, opts.mode || DIR_MODE, function _dirCreated(err) {
  181. /* istanbul ignore else */
  182. if (err) return cb(err);
  183. cb(null, name, _prepareTmpDirRemoveCallback(name, opts, false));
  184. });
  185. });
  186. }
  187. /**
  188. * Synchronous version of dir.
  189. *
  190. * @param {Options} options
  191. * @returns {DirSyncObject} object consists of name and removeCallback
  192. * @throws {Error} if it cannot create a directory
  193. */
  194. function dirSync(options) {
  195. const args = _parseArguments(options),
  196. opts = args[0];
  197. const name = tmpNameSync(opts);
  198. fs.mkdirSync(name, opts.mode || DIR_MODE);
  199. return {
  200. name: name,
  201. removeCallback: _prepareTmpDirRemoveCallback(name, opts, true)
  202. };
  203. }
  204. /**
  205. * Removes files asynchronously.
  206. *
  207. * @param {Object} fdPath
  208. * @param {Function} next
  209. * @private
  210. */
  211. function _removeFileAsync(fdPath, next) {
  212. const _handler = function (err) {
  213. if (err && !_isENOENT(err)) {
  214. // reraise any unanticipated error
  215. return next(err);
  216. }
  217. next();
  218. };
  219. if (0 <= fdPath[0])
  220. fs.close(fdPath[0], function () {
  221. fs.unlink(fdPath[1], _handler);
  222. });
  223. else fs.unlink(fdPath[1], _handler);
  224. }
  225. /**
  226. * Removes files synchronously.
  227. *
  228. * @param {Object} fdPath
  229. * @private
  230. */
  231. function _removeFileSync(fdPath) {
  232. let rethrownException = null;
  233. try {
  234. if (0 <= fdPath[0]) fs.closeSync(fdPath[0]);
  235. } catch (e) {
  236. // reraise any unanticipated error
  237. if (!_isEBADF(e) && !_isENOENT(e)) throw e;
  238. } finally {
  239. try {
  240. fs.unlinkSync(fdPath[1]);
  241. } catch (e) {
  242. // reraise any unanticipated error
  243. if (!_isENOENT(e)) rethrownException = e;
  244. }
  245. }
  246. if (rethrownException !== null) {
  247. throw rethrownException;
  248. }
  249. }
  250. /**
  251. * Prepares the callback for removal of the temporary file.
  252. *
  253. * Returns either a sync callback or a async callback depending on whether
  254. * fileSync or file was called, which is expressed by the sync parameter.
  255. *
  256. * @param {string} name the path of the file
  257. * @param {number} fd file descriptor
  258. * @param {Object} opts
  259. * @param {boolean} sync
  260. * @returns {fileCallback | fileCallbackSync}
  261. * @private
  262. */
  263. function _prepareTmpFileRemoveCallback(name, fd, opts, sync) {
  264. const removeCallbackSync = _prepareRemoveCallback(_removeFileSync, [fd, name], sync);
  265. const removeCallback = _prepareRemoveCallback(_removeFileAsync, [fd, name], sync, removeCallbackSync);
  266. if (!opts.keep) _removeObjects.unshift(removeCallbackSync);
  267. return sync ? removeCallbackSync : removeCallback;
  268. }
  269. /**
  270. * Prepares the callback for removal of the temporary directory.
  271. *
  272. * Returns either a sync callback or a async callback depending on whether
  273. * tmpFileSync or tmpFile was called, which is expressed by the sync parameter.
  274. *
  275. * @param {string} name
  276. * @param {Object} opts
  277. * @param {boolean} sync
  278. * @returns {Function} the callback
  279. * @private
  280. */
  281. function _prepareTmpDirRemoveCallback(name, opts, sync) {
  282. const removeFunction = opts.unsafeCleanup ? rimraf : fs.rmdir.bind(fs);
  283. const removeFunctionSync = opts.unsafeCleanup ? FN_RIMRAF_SYNC : FN_RMDIR_SYNC;
  284. const removeCallbackSync = _prepareRemoveCallback(removeFunctionSync, name, sync);
  285. const removeCallback = _prepareRemoveCallback(removeFunction, name, sync, removeCallbackSync);
  286. if (!opts.keep) _removeObjects.unshift(removeCallbackSync);
  287. return sync ? removeCallbackSync : removeCallback;
  288. }
  289. /**
  290. * Creates a guarded function wrapping the removeFunction call.
  291. *
  292. * The cleanup callback is save to be called multiple times.
  293. * Subsequent invocations will be ignored.
  294. *
  295. * @param {Function} removeFunction
  296. * @param {string} fileOrDirName
  297. * @param {boolean} sync
  298. * @param {cleanupCallbackSync?} cleanupCallbackSync
  299. * @returns {cleanupCallback | cleanupCallbackSync}
  300. * @private
  301. */
  302. function _prepareRemoveCallback(removeFunction, fileOrDirName, sync, cleanupCallbackSync) {
  303. let called = false;
  304. // if sync is true, the next parameter will be ignored
  305. return function _cleanupCallback(next) {
  306. /* istanbul ignore else */
  307. if (!called) {
  308. // remove cleanupCallback from cache
  309. const toRemove = cleanupCallbackSync || _cleanupCallback;
  310. const index = _removeObjects.indexOf(toRemove);
  311. /* istanbul ignore else */
  312. if (index >= 0) _removeObjects.splice(index, 1);
  313. called = true;
  314. if (sync || removeFunction === FN_RMDIR_SYNC || removeFunction === FN_RIMRAF_SYNC) {
  315. return removeFunction(fileOrDirName);
  316. } else {
  317. return removeFunction(fileOrDirName, next || function () {});
  318. }
  319. }
  320. };
  321. }
  322. /**
  323. * The garbage collector.
  324. *
  325. * @private
  326. */
  327. function _garbageCollector() {
  328. /* istanbul ignore else */
  329. if (!_gracefulCleanup) return;
  330. // the function being called removes itself from _removeObjects,
  331. // loop until _removeObjects is empty
  332. while (_removeObjects.length) {
  333. try {
  334. _removeObjects[0]();
  335. } catch (e) {
  336. // already removed?
  337. }
  338. }
  339. }
  340. /**
  341. * Random name generator based on crypto.
  342. * Adapted from http://blog.tompawlak.org/how-to-generate-random-values-nodejs-javascript
  343. *
  344. * @param {number} howMany
  345. * @returns {string} the generated random name
  346. * @private
  347. */
  348. function _randomChars(howMany) {
  349. let value = [],
  350. rnd = null;
  351. // make sure that we do not fail because we ran out of entropy
  352. try {
  353. rnd = crypto.randomBytes(howMany);
  354. } catch (e) {
  355. rnd = crypto.pseudoRandomBytes(howMany);
  356. }
  357. for (let i = 0; i < howMany; i++) {
  358. value.push(RANDOM_CHARS[rnd[i] % RANDOM_CHARS.length]);
  359. }
  360. return value.join('');
  361. }
  362. /**
  363. * Checks whether the `obj` parameter is defined or not.
  364. *
  365. * @param {Object} obj
  366. * @returns {boolean} true if the object is undefined
  367. * @private
  368. */
  369. function _isUndefined(obj) {
  370. return typeof obj === 'undefined';
  371. }
  372. /**
  373. * Parses the function arguments.
  374. *
  375. * This function helps to have optional arguments.
  376. *
  377. * @param {(Options|null|undefined|Function)} options
  378. * @param {?Function} callback
  379. * @returns {Array} parsed arguments
  380. * @private
  381. */
  382. function _parseArguments(options, callback) {
  383. /* istanbul ignore else */
  384. if (typeof options === 'function') {
  385. return [{}, options];
  386. }
  387. /* istanbul ignore else */
  388. if (_isUndefined(options)) {
  389. return [{}, callback];
  390. }
  391. // copy options so we do not leak the changes we make internally
  392. const actualOptions = {};
  393. for (const key of Object.getOwnPropertyNames(options)) {
  394. actualOptions[key] = options[key];
  395. }
  396. return [actualOptions, callback];
  397. }
  398. /**
  399. * Resolve the specified path name in respect to tmpDir.
  400. *
  401. * The specified name might include relative path components, e.g. ../
  402. * so we need to resolve in order to be sure that is is located inside tmpDir
  403. *
  404. * @private
  405. */
  406. function _resolvePath(name, tmpDir, cb) {
  407. const pathToResolve = path.isAbsolute(name) ? name : path.join(tmpDir, name);
  408. fs.stat(pathToResolve, function (err) {
  409. if (err) {
  410. fs.realpath(path.dirname(pathToResolve), function (err, parentDir) {
  411. if (err) return cb(err);
  412. cb(null, path.join(parentDir, path.basename(pathToResolve)));
  413. });
  414. } else {
  415. fs.realpath(pathToResolve, cb);
  416. }
  417. });
  418. }
  419. /**
  420. * Resolve the specified path name in respect to tmpDir.
  421. *
  422. * The specified name might include relative path components, e.g. ../
  423. * so we need to resolve in order to be sure that is is located inside tmpDir
  424. *
  425. * @private
  426. */
  427. function _resolvePathSync(name, tmpDir) {
  428. const pathToResolve = path.isAbsolute(name) ? name : path.join(tmpDir, name);
  429. try {
  430. fs.statSync(pathToResolve);
  431. return fs.realpathSync(pathToResolve);
  432. } catch (_err) {
  433. const parentDir = fs.realpathSync(path.dirname(pathToResolve));
  434. return path.join(parentDir, path.basename(pathToResolve));
  435. }
  436. }
  437. /**
  438. * Generates a new temporary name.
  439. *
  440. * @param {Object} opts
  441. * @returns {string} the new random name according to opts
  442. * @private
  443. */
  444. function _generateTmpName(opts) {
  445. const tmpDir = opts.tmpdir;
  446. /* istanbul ignore else */
  447. if (!_isUndefined(opts.name)) {
  448. return path.join(tmpDir, opts.dir, opts.name);
  449. }
  450. /* istanbul ignore else */
  451. if (!_isUndefined(opts.template)) {
  452. return path.join(tmpDir, opts.dir, opts.template).replace(TEMPLATE_PATTERN, _randomChars(6));
  453. }
  454. // prefix and postfix
  455. const name = [
  456. opts.prefix ? opts.prefix : 'tmp',
  457. '-',
  458. process.pid,
  459. '-',
  460. _randomChars(12),
  461. opts.postfix ? '-' + opts.postfix : ''
  462. ].join('');
  463. return path.join(tmpDir, opts.dir, name);
  464. }
  465. /**
  466. * Asserts and sanitizes the basic options.
  467. *
  468. * @private
  469. */
  470. function _assertOptionsBase(options) {
  471. if (!_isUndefined(options.name)) {
  472. const name = options.name;
  473. // assert that name is not absolute and does not contain a path
  474. if (path.isAbsolute(name)) throw new Error(`name option must not contain an absolute path, found "${name}".`);
  475. // must not fail on valid .<name> or ..<name> or similar such constructs
  476. const basename = path.basename(name);
  477. if (basename === '..' || basename === '.' || basename !== name)
  478. throw new Error(`name option must not contain a path, found "${name}".`);
  479. }
  480. /* istanbul ignore else */
  481. if (!_isUndefined(options.template) && !options.template.match(TEMPLATE_PATTERN)) {
  482. throw new Error(`Invalid template, found "${options.template}".`);
  483. }
  484. /* istanbul ignore else */
  485. if ((!_isUndefined(options.tries) && isNaN(options.tries)) || options.tries < 0) {
  486. throw new Error(`Invalid tries, found "${options.tries}".`);
  487. }
  488. // if a name was specified we will try once
  489. options.tries = _isUndefined(options.name) ? options.tries || DEFAULT_TRIES : 1;
  490. options.keep = !!options.keep;
  491. options.detachDescriptor = !!options.detachDescriptor;
  492. options.discardDescriptor = !!options.discardDescriptor;
  493. options.unsafeCleanup = !!options.unsafeCleanup;
  494. // for completeness' sake only, also keep (multiple) blanks if the user, purportedly sane, requests us to
  495. options.prefix = _isUndefined(options.prefix) ? '' : options.prefix;
  496. options.postfix = _isUndefined(options.postfix) ? '' : options.postfix;
  497. }
  498. /**
  499. * Gets the relative directory to tmpDir.
  500. *
  501. * @private
  502. */
  503. function _getRelativePath(option, name, tmpDir, cb) {
  504. if (_isUndefined(name)) return cb(null);
  505. _resolvePath(name, tmpDir, function (err, resolvedPath) {
  506. if (err) return cb(err);
  507. const relativePath = path.relative(tmpDir, resolvedPath);
  508. if (!resolvedPath.startsWith(tmpDir)) {
  509. return cb(new Error(`${option} option must be relative to "${tmpDir}", found "${relativePath}".`));
  510. }
  511. cb(null, relativePath);
  512. });
  513. }
  514. /**
  515. * Gets the relative path to tmpDir.
  516. *
  517. * @private
  518. */
  519. function _getRelativePathSync(option, name, tmpDir) {
  520. if (_isUndefined(name)) return;
  521. const resolvedPath = _resolvePathSync(name, tmpDir);
  522. const relativePath = path.relative(tmpDir, resolvedPath);
  523. if (!resolvedPath.startsWith(tmpDir)) {
  524. throw new Error(`${option} option must be relative to "${tmpDir}", found "${relativePath}".`);
  525. }
  526. return relativePath;
  527. }
  528. /**
  529. * Asserts whether the specified options are valid, also sanitizes options and provides sane defaults for missing
  530. * options.
  531. *
  532. * @private
  533. */
  534. function _assertAndSanitizeOptions(options, cb) {
  535. _getTmpDir(options, function (err, tmpDir) {
  536. if (err) return cb(err);
  537. options.tmpdir = tmpDir;
  538. try {
  539. _assertOptionsBase(options, tmpDir);
  540. } catch (err) {
  541. return cb(err);
  542. }
  543. // sanitize dir, also keep (multiple) blanks if the user, purportedly sane, requests us to
  544. _getRelativePath('dir', options.dir, tmpDir, function (err, dir) {
  545. if (err) return cb(err);
  546. options.dir = _isUndefined(dir) ? '' : dir;
  547. // sanitize further if template is relative to options.dir
  548. _getRelativePath('template', options.template, tmpDir, function (err, template) {
  549. if (err) return cb(err);
  550. options.template = template;
  551. cb(null, options);
  552. });
  553. });
  554. });
  555. }
  556. /**
  557. * Asserts whether the specified options are valid, also sanitizes options and provides sane defaults for missing
  558. * options.
  559. *
  560. * @private
  561. */
  562. function _assertAndSanitizeOptionsSync(options) {
  563. const tmpDir = (options.tmpdir = _getTmpDirSync(options));
  564. _assertOptionsBase(options, tmpDir);
  565. const dir = _getRelativePathSync('dir', options.dir, tmpDir);
  566. options.dir = _isUndefined(dir) ? '' : dir;
  567. options.template = _getRelativePathSync('template', options.template, tmpDir);
  568. return options;
  569. }
  570. /**
  571. * Helper for testing against EBADF to compensate changes made to Node 7.x under Windows.
  572. *
  573. * @private
  574. */
  575. function _isEBADF(error) {
  576. return _isExpectedError(error, -EBADF, 'EBADF');
  577. }
  578. /**
  579. * Helper for testing against ENOENT to compensate changes made to Node 7.x under Windows.
  580. *
  581. * @private
  582. */
  583. function _isENOENT(error) {
  584. return _isExpectedError(error, -ENOENT, 'ENOENT');
  585. }
  586. /**
  587. * Helper to determine whether the expected error code matches the actual code and errno,
  588. * which will differ between the supported node versions.
  589. *
  590. * - Node >= 7.0:
  591. * error.code {string}
  592. * error.errno {number} any numerical value will be negated
  593. *
  594. * CAVEAT
  595. *
  596. * On windows, the errno for EBADF is -4083 but os.constants.errno.EBADF is different and we must assume that ENOENT
  597. * is no different here.
  598. *
  599. * @param {SystemError} error
  600. * @param {number} errno
  601. * @param {string} code
  602. * @private
  603. */
  604. function _isExpectedError(error, errno, code) {
  605. return IS_WIN32 ? error.code === code : error.code === code && error.errno === errno;
  606. }
  607. /**
  608. * Sets the graceful cleanup.
  609. *
  610. * If graceful cleanup is set, tmp will remove all controlled temporary objects on process exit, otherwise the
  611. * temporary objects will remain in place, waiting to be cleaned up on system restart or otherwise scheduled temporary
  612. * object removals.
  613. */
  614. function setGracefulCleanup() {
  615. _gracefulCleanup = true;
  616. }
  617. /**
  618. * Returns the currently configured tmp dir from os.tmpdir().
  619. *
  620. * @private
  621. */
  622. function _getTmpDir(options, cb) {
  623. return fs.realpath((options && options.tmpdir) || os.tmpdir(), cb);
  624. }
  625. /**
  626. * Returns the currently configured tmp dir from os.tmpdir().
  627. *
  628. * @private
  629. */
  630. function _getTmpDirSync(options) {
  631. return fs.realpathSync((options && options.tmpdir) || os.tmpdir());
  632. }
  633. // Install process exit listener
  634. process.addListener(EXIT, _garbageCollector);
  635. /**
  636. * Configuration options.
  637. *
  638. * @typedef {Object} Options
  639. * @property {?boolean} keep the temporary object (file or dir) will not be garbage collected
  640. * @property {?number} tries the number of tries before give up the name generation
  641. * @property (?int) mode the access mode, defaults are 0o700 for directories and 0o600 for files
  642. * @property {?string} template the "mkstemp" like filename template
  643. * @property {?string} name fixed name relative to tmpdir or the specified dir option
  644. * @property {?string} dir tmp directory relative to the root tmp directory in use
  645. * @property {?string} prefix prefix for the generated name
  646. * @property {?string} postfix postfix for the generated name
  647. * @property {?string} tmpdir the root tmp directory which overrides the os tmpdir
  648. * @property {?boolean} unsafeCleanup recursively removes the created temporary directory, even when it's not empty
  649. * @property {?boolean} detachDescriptor detaches the file descriptor, caller is responsible for closing the file, tmp will no longer try closing the file during garbage collection
  650. * @property {?boolean} discardDescriptor discards the file descriptor (closes file, fd is -1), tmp will no longer try closing the file during garbage collection
  651. */
  652. /**
  653. * @typedef {Object} FileSyncObject
  654. * @property {string} name the name of the file
  655. * @property {string} fd the file descriptor or -1 if the fd has been discarded
  656. * @property {fileCallback} removeCallback the callback function to remove the file
  657. */
  658. /**
  659. * @typedef {Object} DirSyncObject
  660. * @property {string} name the name of the directory
  661. * @property {fileCallback} removeCallback the callback function to remove the directory
  662. */
  663. /**
  664. * @callback tmpNameCallback
  665. * @param {?Error} err the error object if anything goes wrong
  666. * @param {string} name the temporary file name
  667. */
  668. /**
  669. * @callback fileCallback
  670. * @param {?Error} err the error object if anything goes wrong
  671. * @param {string} name the temporary file name
  672. * @param {number} fd the file descriptor or -1 if the fd had been discarded
  673. * @param {cleanupCallback} fn the cleanup callback function
  674. */
  675. /**
  676. * @callback fileCallbackSync
  677. * @param {?Error} err the error object if anything goes wrong
  678. * @param {string} name the temporary file name
  679. * @param {number} fd the file descriptor or -1 if the fd had been discarded
  680. * @param {cleanupCallbackSync} fn the cleanup callback function
  681. */
  682. /**
  683. * @callback dirCallback
  684. * @param {?Error} err the error object if anything goes wrong
  685. * @param {string} name the temporary file name
  686. * @param {cleanupCallback} fn the cleanup callback function
  687. */
  688. /**
  689. * @callback dirCallbackSync
  690. * @param {?Error} err the error object if anything goes wrong
  691. * @param {string} name the temporary file name
  692. * @param {cleanupCallbackSync} fn the cleanup callback function
  693. */
  694. /**
  695. * Removes the temporary created file or directory.
  696. *
  697. * @callback cleanupCallback
  698. * @param {simpleCallback} [next] function to call whenever the tmp object needs to be removed
  699. */
  700. /**
  701. * Removes the temporary created file or directory.
  702. *
  703. * @callback cleanupCallbackSync
  704. */
  705. /**
  706. * Callback function for function composition.
  707. * @see {@link https://github.com/raszi/node-tmp/issues/57|raszi/node-tmp#57}
  708. *
  709. * @callback simpleCallback
  710. */
  711. // exporting all the needed methods
  712. // evaluate _getTmpDir() lazily, mainly for simplifying testing but it also will
  713. // allow users to reconfigure the temporary directory
  714. Object.defineProperty(module.exports, 'tmpdir', {
  715. enumerable: true,
  716. configurable: false,
  717. get: function () {
  718. return _getTmpDirSync();
  719. }
  720. });
  721. module.exports.dir = dir;
  722. module.exports.dirSync = dirSync;
  723. module.exports.file = file;
  724. module.exports.fileSync = fileSync;
  725. module.exports.tmpName = tmpName;
  726. module.exports.tmpNameSync = tmpNameSync;
  727. module.exports.setGracefulCleanup = setGracefulCleanup;