Skip to content

Commit

Permalink
fix gh-176: fail early if there is no tmp dir specified
Browse files Browse the repository at this point in the history
  • Loading branch information
silkentrance committed Mar 13, 2019
1 parent 0343c9e commit 71b00bf
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 37 deletions.
99 changes: 68 additions & 31 deletions lib/tmp.js
Expand Up @@ -22,12 +22,6 @@ const rimraf = require('rimraf');
* The working inner variables.
*/
const
/**
* The temporary directory.
* @type {string}
*/
tmpDir = os.tmpdir(),

// the random characters to choose from
RANDOM_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',

Expand Down Expand Up @@ -121,12 +115,21 @@ function _parseArguments(options, callback) {
* @private
*/
function _generateTmpName(opts) {

const tmpDir = _getTmpDir();

// fail early on missing tmp dir
if (isBlank(opts.dir) && isBlank(tmpDir)) {
throw new Error('No tmp dir specified');
}

/* istanbul ignore else */
if (opts.name) {
if (!isBlank(opts.name)) {
return path.join(opts.dir || tmpDir, opts.name);
}

// mkstemps like template
// opts.template has already been guarded in tmpName() below
/* istanbul ignore else */
if (opts.template) {
var template = opts.template;
Expand All @@ -139,10 +142,10 @@ function _generateTmpName(opts) {

// prefix and postfix
const name = [
opts.prefix || 'tmp-',
(isBlank(opts.prefix) ? 'tmp-' : opts.prefix),
process.pid,
_randomChars(12),
opts.postfix || ''
(isBlank(opts.postfix) ? '' : opts.postfix)
].join('');

return path.join(opts.dir || tmpDir, name);
Expand All @@ -159,7 +162,7 @@ function tmpName(options, callback) {
args = _parseArguments(options, callback),
opts = args[0],
cb = args[1],
tries = opts.name ? 1 : opts.tries || DEFAULT_TRIES;
tries = !isBlank(opts.name) ? 1 : opts.tries || DEFAULT_TRIES;

/* istanbul ignore else */
if (isNaN(tries) || tries < 0)
Expand All @@ -170,20 +173,24 @@ function tmpName(options, callback) {
return cb(new Error('Invalid template provided'));

(function _getUniqueName() {
const name = _generateTmpName(opts);
try {
const name = _generateTmpName(opts);

// check whether the path exists then retry if needed
fs.stat(name, function (err) {
/* istanbul ignore else */
if (!err) {
// check whether the path exists then retry if needed
fs.stat(name, function (err) {
/* istanbul ignore else */
if (tries-- > 0) return _getUniqueName();
if (!err) {
/* istanbul ignore else */
if (tries-- > 0) return _getUniqueName();

return cb(new Error('Could not get a unique tmp filename, max tries reached ' + name));
}
return cb(new Error('Could not get a unique tmp filename, max tries reached ' + name));
}

cb(null, name);
});
cb(null, name);
});
} catch (err) {
cb(err);
}
}());
}

Expand All @@ -198,7 +205,7 @@ function tmpNameSync(options) {
var
args = _parseArguments(options),
opts = args[0],
tries = opts.name ? 1 : opts.tries || DEFAULT_TRIES;
tries = !isBlank(opts.name) ? 1 : opts.tries || DEFAULT_TRIES;

/* istanbul ignore else */
if (isNaN(tries) || tries < 0)
Expand Down Expand Up @@ -232,7 +239,7 @@ function file(options, callback) {
opts = args[0],
cb = args[1];

opts.postfix = (_isUndefined(opts.postfix)) ? '.tmp' : opts.postfix;
opts.postfix = isBlank(opts.postfix) ? '.tmp' : opts.postfix;

// gets a temporary filename
tmpName(opts, function _tmpNameCreated(err, name) {
Expand Down Expand Up @@ -285,7 +292,7 @@ function fileSync(options) {
args = _parseArguments(options),
opts = args[0];

opts.postfix = (_isUndefined(opts.postfix)) ? '.tmp' : opts.postfix;
opts.postfix = isBlank(opts.postfix) ? '.tmp' : opts.postfix;

const discardOrDetachDescriptor = opts.discardDescriptor || opts.detachDescriptor;
const name = tmpNameSync(opts);
Expand Down Expand Up @@ -534,32 +541,53 @@ function isENOENT(error) {
* which will differ between the supported node versions.
*
* - Node >= 7.0:
* error.code {String}
* error.errno {String|Number} any numerical value will be negated
* error.code {string}
* error.errno {string|number} any numerical value will be negated
*
* - Node >= 6.0 < 7.0:
* error.code {String}
* error.errno {Number} negated
* error.code {string}
* error.errno {number} negated
*
* - Node >= 4.0 < 6.0: introduces SystemError
* error.code {String}
* error.errno {Number} negated
* error.code {string}
* error.errno {number} negated
*
* - Node >= 0.10 < 4.0:
* error.code {Number} negated
* error.code {number} negated
* error.errno n/a
*/
function isExpectedError(error, code, errno) {
return error.code == code || error.code == errno;
}

/**
* Helper which determines whether a string s is blank, that is undefined, or empty or null.
*
* @private
* @param {string} s
* @returns {Boolean} true whether the string s is blank, false otherwise
*/
function isBlank(s) {
return s === null || s === undefined || !s.trim();
}

/**
* Sets the graceful cleanup.
*/
function setGracefulCleanup() {
_gracefulCleanup = true;
}

/**
* Returns the currently configured tmp dir from os.tmpdir().
*
* @private
* @returns {string} the currently configured tmp dir
*/
function _getTmpDir() {
return os.tmpdir();
}

/**
* If there are multiple different versions of tmp in place, make sure that
* we recognize the old listeners.
Expand Down Expand Up @@ -669,7 +697,16 @@ _safely_install_listener();
*/

// exporting all the needed methods
module.exports.tmpdir = tmpDir;

// evaluate os.tmpdir() lazily, mainly for simplifying testing but it also will
// allow users to reconfigure the temporary directory
Object.defineProperty(module.exports, 'tmpdir', {
enumerable: true,
configurable: false,
get: function () {
return _getTmpDir();
}
});

module.exports.dir = dir;
module.exports.dirSync = dirSync;
Expand Down
3 changes: 0 additions & 3 deletions package.json
Expand Up @@ -21,9 +21,6 @@
"engines": {
"node": ">=6"
},
"dependencies": {
"rimraf": "^2.6.2"
},
"dependencies": {},
"devDependencies": {
"eslint": "^4.19.1",
Expand Down
1 change: 0 additions & 1 deletion test/dir-test.js
Expand Up @@ -54,7 +54,6 @@ describe('tmp', function () {
});

describe('when running issue specific inband tests', function () {
// add your issue specific tests here
});

describe('when running standard outband tests', function () {
Expand Down
68 changes: 67 additions & 1 deletion test/name-sync-test.js
@@ -1,8 +1,9 @@
/* eslint-disable no-octal */
// vim: expandtab:ts=2:sw=2

var
const
assert = require('assert'),
os = require('os'),
inbandStandardTests = require('./name-inband-standard'),
tmp = require('../lib/tmp');

Expand Down Expand Up @@ -35,6 +36,71 @@ describe('tmp', function () {
});

describe('when running issue specific inband tests', function () {
describe('on issue #176', function () {
const origfn = os.tmpdir;

function _generateSpecName(optsDir, osTmpDir) {
return 'opts.dir = "$1", os.tmpdir() = "$2"'.replace('$1', optsDir).replace('$2', osTmpDir);
}

const failing = ['', ' ', undefined, null];
const nonFailing = ['tmp']; // the origfn cannot be trusted as the os may or may not have a valid tmp dir

describe('must fail on invalid os.tmpdir() and invalid opts.dir', function () {
// test all failing permutations
for (let oidx = 0; oidx < failing.length; oidx++) {
for (let iidx = 0; iidx < failing.length; iidx++) {
it(_generateSpecName(failing[iidx], failing[oidx]), function () {
os.tmpdir = function () { return failing[oidx]; };
try {
tmp.tmpNameSync({ dir: failing[iidx] });
assert.fail('expected this to fail');
} catch (err) {
assert.ok(err instanceof Error, 'error expected');
} finally {
os.tmpdir = origfn;
}
});
}
}
});

describe('must not fail on invalid os.tmpdir() and valid opts.dir', function () {
// test all non failing permutations for non failing opts.dir and failing osTmpDir
for (let oidx = 0; oidx < failing.length; oidx++) {
for (let iidx = 0; iidx < nonFailing.length; iidx++) {
it(_generateSpecName(nonFailing[iidx], failing[oidx]), function () {
os.tmpdir = function () { return failing[oidx]; };
try {
tmp.tmpNameSync({ dir: nonFailing[iidx] });
} catch (err) {
assert.fail(err);
} finally {
os.tmpdir = origfn;
}
});
}
}
});

describe('must not fail on valid os.tmpdir() and invalid opts.dir', function () {
// test all non failing permutations for failing opts.dir and non failing osTmpDir
for (let oidx = 0; oidx < nonFailing.length; oidx++) {
for (let iidx = 0; iidx < failing.length; iidx++) {
it(_generateSpecName(failing[iidx], nonFailing[oidx]), function () {
os.tmpdir = function () { return nonFailing[oidx]; };
try {
tmp.tmpNameSync({ dir: failing[iidx] });
} catch (err) {
assert.fail(err);
} finally {
os.tmpdir = origfn;
}
});
}
}
});
});
});

describe('when running standard outband tests', function () {
Expand Down
76 changes: 75 additions & 1 deletion test/name-test.js
@@ -1,8 +1,9 @@
/* eslint-disable no-octal */
// vim: expandtab:ts=2:sw=2

var
const
assert = require('assert'),
os = require('os'),
inbandStandardTests = require('./name-inband-standard'),
tmp = require('../lib/tmp');

Expand Down Expand Up @@ -45,6 +46,79 @@ describe('tmp', function () {
});

describe('when running issue specific inband tests', function () {
describe('on issue #176', function () {
const origfn = os.tmpdir;

function _generateSpecName(optsDir, osTmpDir) {
return 'opts.dir = "$1", os.tmpdir() = "$2"'.replace('$1', optsDir).replace('$2', osTmpDir);
}

const failing = ['', ' ', undefined, null];
const nonFailing = ['tmp']; // the origfn cannot be trusted as the os may or may not have a valid tmp dir

describe('must fail on invalid os.tmpdir() and invalid opts.dir', function () {
// test all failing permutations
for (let oidx = 0; oidx < failing.length; oidx++) {
for (let iidx = 0; iidx < failing.length; iidx++) {
it(_generateSpecName(failing[iidx], failing[oidx]), function (done) {
os.tmpdir = function () { return failing[oidx]; };
tmp.tmpName({ dir: failing[iidx] }, function (err) {
try {
assert.ok(err instanceof Error, 'should have failed');
} catch (err) {
return done(err);
} finally {
os.tmpdir = origfn;
}
done();
});
});
}
}
});

describe('must not fail on invalid os.tmpdir() and valid opts.dir', function () {
// test all non failing permutations for non failing opts.dir and failing osTmpDir
for (let oidx = 0; oidx < failing.length; oidx++) {
for (let iidx = 0; iidx < nonFailing.length; iidx++) {
it(_generateSpecName(nonFailing[iidx], failing[oidx]), function (done) {
os.tmpdir = function () { return failing[oidx]; };
tmp.tmpName({ dir: nonFailing[iidx] }, function (err) {
try {
assert.ok(err === null || err === undefined, 'should not have failed');
} catch (err) {
return done(err);
} finally {
os.tmpdir = origfn;
}
done();
});
});
}
}
});

describe('must not fail on valid os.tmpdir() and invalid opts.dir', function () {
// test all non failing permutations for failing opts.dir and non failing osTmpDir
for (let oidx = 0; oidx < nonFailing.length; oidx++) {
for (let iidx = 0; iidx < failing.length; iidx++) {
it(_generateSpecName(failing[iidx], nonFailing[oidx]), function (done) {
os.tmpdir = function () { return nonFailing[oidx]; };
tmp.tmpName({ dir: failing[iidx] }, function (err) {
try {
assert.ok(err === null || err === undefined, 'should not have failed');
} catch (err) {
return done(err);
} finally {
os.tmpdir = origfn;
}
done();
});
});
}
}
});
});
});

describe('when running standard outband tests', function () {
Expand Down

0 comments on commit 71b00bf

Please sign in to comment.