Skip to content
This repository was archived by the owner on Jul 3, 2019. It is now read-only.

Commit c42da71

Browse files
authored
feat(tmp): safe tmp dir creation/management util (#73)
This might seem odd for a caching library, but it turns out a lot of work that will end up *in* the cache needs to be frobbed before it can be used. In these cases, users may request a unique tmp directory with safe ownership management.
1 parent f8e0f25 commit c42da71

File tree

4 files changed

+119
-1
lines changed

4 files changed

+119
-1
lines changed

README.md

+43
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ can just as easily be used on its own
3333
* [`rm.content`](#rm-content)
3434
* Utilities
3535
* [`clearMemoized`](#clear-memoized)
36+
* [`tmp.mkdir`](#tmp-mkdir)
37+
* [`tmp.withTmp`](#with-tmp)
3638
* [`verify`](#verify)
3739
* [`verify.lastRun`](#verify-last-run)
3840

@@ -419,6 +421,47 @@ cacache.rm.content(cachePath, 'deadbeef').then(() => {
419421

420422
Completely resets the in-memory entry cache.
421423

424+
#### <a name="tmp-mkdir"></a> `> tmp.mkdir(cache, opts) -> Promise<Path>`
425+
426+
Returns a unique temporary directory inside the cache's `tmp` dir. This
427+
directory will use the same safe user assignment that all the other stuff use.
428+
429+
Once the directory is made, it's the user's responsibility that all files within
430+
are made according to the same `opts.gid`/`opts.uid` settings that would be
431+
passed in. If not, you can ask cacache to do it for you by calling
432+
[`tmp.fix()`](#tmp-fix), which will fix all tmp directory permissions.
433+
434+
If you want automatic cleanup of this directory, use
435+
[`tmp.withTmp()`](#with-tpm)
436+
437+
##### Example
438+
439+
```javascript
440+
cacache.tmp.mkdir(cache).then(dir => {
441+
fs.writeFile(path.join(dir, 'blablabla'), Buffer#<1234>, ...)
442+
})
443+
```
444+
445+
#### <a name="with-tmp"></a> `> tmp.withTmp(cache, opts, cb) -> Promise`
446+
447+
Creates a temporary directory with [`tmp.mkdir()`](#tmp-mkdir) and calls `cb`
448+
with it. The created temporary directory will be removed when the return value
449+
of `cb()` resolves -- that is, if you return a Promise from `cb()`, the tmp
450+
directory will be automatically deleted once that promise completes.
451+
452+
The same caveats apply when it comes to managing permissions for the tmp dir's
453+
contents.
454+
455+
##### Example
456+
457+
```javascript
458+
cacache.tmp.withTmp(cache, dir => {
459+
return fs.writeFileAsync(path.join(dir, 'blablabla'), Buffer#<1234>, ...)
460+
}).then(() => {
461+
// `dir` no longer exists
462+
})
463+
```
464+
422465
#### <a name="verify"></a> `> cacache.verify(cache, opts) -> Promise`
423466

424467
Checks out and fixes up your cache:

index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ module.exports = {
66
put: require('./put'),
77
rm: require('./rm'),
88
verify: require('./verify'),
9-
clearMemoized: require('./lib/memoization').clearMemoized
9+
clearMemoized: require('./lib/memoization').clearMemoized,
10+
tmp: require('./lib/util/tmp')
1011
}

lib/util/tmp.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use strict'
2+
3+
const BB = require('bluebird')
4+
5+
const fixOwner = require('./fix-owner')
6+
const path = require('path')
7+
const rimraf = BB.promisify(require('rimraf'))
8+
const uniqueFilename = require('unique-filename')
9+
10+
module.exports.mkdir = mktmpdir
11+
function mktmpdir (cache, opts) {
12+
opts = opts || {}
13+
const tmpTarget = uniqueFilename(path.join(cache, 'tmp'), opts.tmpPrefix)
14+
return fixOwner.mkdirfix(tmpTarget, opts.uid, opts.gid).then(() => {
15+
return tmpTarget
16+
})
17+
}
18+
19+
module.exports.withTmp = withTmp
20+
function withTmp (cache, opts, cb) {
21+
if (!cb) {
22+
cb = opts
23+
opts = null
24+
}
25+
opts = opts || {}
26+
return BB.using(mktmpdir(cache, opts).disposer(rimraf), cb)
27+
}
28+
29+
module.exports.fix = fixtmpdir
30+
function fixtmpdir (cache, opts) {
31+
return fixOwner(path.join(cache, 'tmp'), opts.uid, opts.gid)
32+
}

test/util.tmp.js

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use strict'
2+
3+
const BB = require('bluebird')
4+
5+
const fs = BB.promisifyAll(require('graceful-fs'))
6+
const path = require('path')
7+
const test = require('tap').test
8+
9+
const CACHE = require('./util/test-dir')(__filename)
10+
11+
const tmp = require('../lib/util/tmp')
12+
13+
test('creates a unique tmpdir inside the cache', t => {
14+
return tmp.mkdir(CACHE).then(dir => {
15+
t.match(path.relative(CACHE, dir), /^tmp[\\/].*/, 'returns a path inside tmp')
16+
return fs.statAsync(dir)
17+
}).then(stat => {
18+
t.ok(stat.isDirectory(), 'path points to an existing directory')
19+
})
20+
})
21+
22+
test('provides a utility that does resource disposal on tmp', t => {
23+
return tmp.withTmp(CACHE, dir => {
24+
return fs.statAsync(dir).then(stat => {
25+
t.ok(stat.isDirectory(), 'path points to an existing directory')
26+
}).then(() => dir)
27+
}).then(dir => {
28+
return BB.join(
29+
fs.statAsync(dir).then(() => {
30+
throw new Error('expected fail')
31+
}).catch({code: 'ENOENT'}, () => {}),
32+
fs.statAsync(path.join(CACHE, 'tmp')),
33+
(nope, yes) => {
34+
t.notOk(nope, 'tmp subdir removed')
35+
t.ok(yes.isDirectory(), 'tmp parent dir left intact')
36+
}
37+
)
38+
})
39+
})
40+
41+
test('makes sure ownership is correct')
42+
test('provides a function for fixing ownership in the tmp dir')

0 commit comments

Comments
 (0)