Skip to content

Commit

Permalink
feat(tmp): safe tmp dir creation/management util (#73)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
zkat authored Mar 15, 2017
1 parent f8e0f25 commit c42da71
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 1 deletion.
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ can just as easily be used on its own
* [`rm.content`](#rm-content)
* Utilities
* [`clearMemoized`](#clear-memoized)
* [`tmp.mkdir`](#tmp-mkdir)
* [`tmp.withTmp`](#with-tmp)
* [`verify`](#verify)
* [`verify.lastRun`](#verify-last-run)

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

Completely resets the in-memory entry cache.

#### <a name="tmp-mkdir"></a> `> tmp.mkdir(cache, opts) -> Promise<Path>`

Returns a unique temporary directory inside the cache's `tmp` dir. This
directory will use the same safe user assignment that all the other stuff use.

Once the directory is made, it's the user's responsibility that all files within
are made according to the same `opts.gid`/`opts.uid` settings that would be
passed in. If not, you can ask cacache to do it for you by calling
[`tmp.fix()`](#tmp-fix), which will fix all tmp directory permissions.

If you want automatic cleanup of this directory, use
[`tmp.withTmp()`](#with-tpm)

##### Example

```javascript
cacache.tmp.mkdir(cache).then(dir => {
fs.writeFile(path.join(dir, 'blablabla'), Buffer#<1234>, ...)
})
```

#### <a name="with-tmp"></a> `> tmp.withTmp(cache, opts, cb) -> Promise`

Creates a temporary directory with [`tmp.mkdir()`](#tmp-mkdir) and calls `cb`
with it. The created temporary directory will be removed when the return value
of `cb()` resolves -- that is, if you return a Promise from `cb()`, the tmp
directory will be automatically deleted once that promise completes.

The same caveats apply when it comes to managing permissions for the tmp dir's
contents.

##### Example

```javascript
cacache.tmp.withTmp(cache, dir => {
return fs.writeFileAsync(path.join(dir, 'blablabla'), Buffer#<1234>, ...)
}).then(() => {
// `dir` no longer exists
})
```

#### <a name="verify"></a> `> cacache.verify(cache, opts) -> Promise`

Checks out and fixes up your cache:
Expand Down
3 changes: 2 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ module.exports = {
put: require('./put'),
rm: require('./rm'),
verify: require('./verify'),
clearMemoized: require('./lib/memoization').clearMemoized
clearMemoized: require('./lib/memoization').clearMemoized,
tmp: require('./lib/util/tmp')
}
32 changes: 32 additions & 0 deletions lib/util/tmp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use strict'

const BB = require('bluebird')

const fixOwner = require('./fix-owner')
const path = require('path')
const rimraf = BB.promisify(require('rimraf'))
const uniqueFilename = require('unique-filename')

module.exports.mkdir = mktmpdir
function mktmpdir (cache, opts) {
opts = opts || {}
const tmpTarget = uniqueFilename(path.join(cache, 'tmp'), opts.tmpPrefix)
return fixOwner.mkdirfix(tmpTarget, opts.uid, opts.gid).then(() => {
return tmpTarget
})
}

module.exports.withTmp = withTmp
function withTmp (cache, opts, cb) {
if (!cb) {
cb = opts
opts = null
}
opts = opts || {}
return BB.using(mktmpdir(cache, opts).disposer(rimraf), cb)
}

module.exports.fix = fixtmpdir
function fixtmpdir (cache, opts) {
return fixOwner(path.join(cache, 'tmp'), opts.uid, opts.gid)
}
42 changes: 42 additions & 0 deletions test/util.tmp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use strict'

const BB = require('bluebird')

const fs = BB.promisifyAll(require('graceful-fs'))
const path = require('path')
const test = require('tap').test

const CACHE = require('./util/test-dir')(__filename)

const tmp = require('../lib/util/tmp')

test('creates a unique tmpdir inside the cache', t => {
return tmp.mkdir(CACHE).then(dir => {
t.match(path.relative(CACHE, dir), /^tmp[\\/].*/, 'returns a path inside tmp')
return fs.statAsync(dir)
}).then(stat => {
t.ok(stat.isDirectory(), 'path points to an existing directory')
})
})

test('provides a utility that does resource disposal on tmp', t => {
return tmp.withTmp(CACHE, dir => {
return fs.statAsync(dir).then(stat => {
t.ok(stat.isDirectory(), 'path points to an existing directory')
}).then(() => dir)
}).then(dir => {
return BB.join(
fs.statAsync(dir).then(() => {
throw new Error('expected fail')
}).catch({code: 'ENOENT'}, () => {}),
fs.statAsync(path.join(CACHE, 'tmp')),
(nope, yes) => {
t.notOk(nope, 'tmp subdir removed')
t.ok(yes.isDirectory(), 'tmp parent dir left intact')
}
)
})
})

test('makes sure ownership is correct')
test('provides a function for fixing ownership in the tmp dir')

0 comments on commit c42da71

Please sign in to comment.