Skip to content

Commit 25f4f73

Browse files
committed
add a util for writing arbitrary files to cache
This prevents metrics timing and debug logs from becoming root-owned.
1 parent 554b641 commit 25f4f73

File tree

3 files changed

+77
-9
lines changed

3 files changed

+77
-9
lines changed

lib/utils/cache-file.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
const npm = require('../npm.js')
2+
const path = require('path')
3+
const chownr = require('chownr')
4+
const writeFileAtomic = require('write-file-atomic')
5+
const mkdirp = require('mkdirp')
6+
const fs = require('graceful-fs')
7+
8+
let cache = null
9+
let cacheUid = null
10+
let cacheGid = null
11+
let needChown = typeof process.getuid === 'function'
12+
13+
const getCacheOwner = () => {
14+
let st
15+
try {
16+
st = fs.lstatSync(cache)
17+
} catch (er) {
18+
if (er.code !== 'ENOENT') {
19+
throw er
20+
}
21+
st = fs.lstatSync(path.dirname(cache))
22+
}
23+
24+
cacheUid = st.uid
25+
cacheGid = st.gid
26+
27+
needChown = st.uid !== process.getuid() ||
28+
st.gid !== process.getgid()
29+
}
30+
31+
const writeOrAppend = (method, file, data) => {
32+
if (!cache) {
33+
cache = npm.config.get('cache')
34+
}
35+
36+
// redundant if already absolute, but prevents non-absolute files
37+
// from being written as if they're part of the cache.
38+
file = path.resolve(cache, file)
39+
40+
if (cacheUid === null && needChown) {
41+
getCacheOwner()
42+
}
43+
44+
const dir = path.dirname(file)
45+
const firstMade = mkdirp.sync(dir)
46+
47+
if (!needChown) {
48+
return method(file, data)
49+
}
50+
51+
let methodThrew = true
52+
try {
53+
method(file, data)
54+
methodThrew = false
55+
} finally {
56+
// always try to leave it in the right ownership state, even on failure
57+
// let the method error fail it instead of the chownr error, though
58+
if (!methodThrew) {
59+
chownr.sync(firstMade || file, cacheUid, cacheGid)
60+
} else {
61+
try {
62+
chownr.sync(firstMade || file, cacheUid, cacheGid)
63+
} catch (_) {}
64+
}
65+
}
66+
}
67+
68+
exports.append = (file, data) => writeOrAppend(fs.appendFileSync, file, data)
69+
exports.write = (file, data) => writeOrAppend(writeFileAtomic.sync, file, data)

lib/utils/error-handler.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@ var wroteLogFile = false
1111
var exitCode = 0
1212
var rollbacks = npm.rollbacks
1313
var chain = require('slide').chain
14-
var writeFileAtomic = require('write-file-atomic')
1514
var errorMessage = require('./error-message.js')
1615
var stopMetrics = require('./metrics.js').stop
17-
var mkdirp = require('mkdirp')
18-
var fs = require('graceful-fs')
16+
17+
const cacheFile = require('./cache-file.js')
1918

2019
var logFileName
2120
function getLogFile () {
@@ -40,7 +39,7 @@ process.on('exit', function (code) {
4039
if (npm.config.loaded && npm.config.get('timing')) {
4140
try {
4241
timings.logfile = getLogFile()
43-
fs.appendFileSync(path.join(npm.config.get('cache'), '_timing.json'), JSON.stringify(timings) + '\n')
42+
cacheFile.append('_timing.json', JSON.stringify(timings) + '\n')
4443
} catch (_) {
4544
// ignore
4645
}
@@ -228,7 +227,6 @@ function writeLogFile () {
228227
var os = require('os')
229228

230229
try {
231-
mkdirp.sync(path.resolve(npm.config.get('cache'), '_logs'))
232230
var logOutput = ''
233231
log.record.forEach(function (m) {
234232
var pref = [m.id, m.level]
@@ -241,7 +239,7 @@ function writeLogFile () {
241239
logOutput += line + os.EOL
242240
})
243241
})
244-
writeFileAtomic.sync(getLogFile(), logOutput)
242+
cacheFile.write(getLogFile(), logOutput)
245243

246244
// truncate once it's been written.
247245
log.record.length = 0

lib/utils/metrics.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const path = require('path')
99
const npm = require('../npm.js')
1010
const regFetch = require('libnpm/fetch')
1111
const uuid = require('uuid')
12+
const cacheFile = require('./cache-file.js')
1213

1314
let inMetrics = false
1415

@@ -51,9 +52,9 @@ function saveMetrics (itWorked) {
5152
}
5253
}
5354
try {
54-
fs.writeFileSync(metricsFile, JSON.stringify(metrics))
55+
cacheFile.write(metricsFile, JSON.stringify(metrics))
5556
} catch (ex) {
56-
// we couldn't write the error metrics file, um, well, oh well.
57+
// we couldn't write and/or chown the error metrics file, oh well.
5758
}
5859
}
5960

@@ -72,6 +73,6 @@ function sendMetrics (metricsFile, metricsRegistry) {
7273
).then(() => {
7374
fs.unlinkSync(metricsFile)
7475
}, err => {
75-
fs.writeFileSync(path.join(path.dirname(metricsFile), 'last-send-metrics-error.txt'), err.stack)
76+
cacheFile.write(path.join(path.dirname(metricsFile), 'last-send-metrics-error.txt'), err.stack)
7677
})
7778
}

0 commit comments

Comments
 (0)