cacache 12.0.2 → 13.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +100 -1
- package/README.md +3 -27
- package/get.js +158 -139
- package/index.js +39 -1
- package/lib/content/path.js +7 -5
- package/lib/content/read.js +120 -54
- package/lib/content/rm.js +5 -4
- package/lib/content/write.js +120 -97
- package/lib/entry-index.js +135 -112
- package/lib/memoization.js +15 -10
- package/lib/util/disposer.js +30 -0
- package/lib/util/fix-owner.js +42 -19
- package/lib/util/hash-to-segments.js +1 -5
- package/lib/util/move-file.js +29 -24
- package/lib/util/tmp.js +8 -3
- package/lib/verify.js +161 -97
- package/ls.js +1 -1
- package/package.json +20 -22
- package/put.js +56 -48
- package/rm.js +5 -2
- package/README.es.md +0 -628
- package/en.js +0 -3
- package/es.js +0 -3
- package/lib/util/y.js +0 -25
- package/locales/en.js +0 -47
- package/locales/en.json +0 -7
- package/locales/es.js +0 -49
- package/locales/es.json +0 -6
package/lib/entry-index.js
CHANGED
|
@@ -1,29 +1,25 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const util = require('util')
|
|
4
4
|
|
|
5
|
-
const contentPath = require('./content/path')
|
|
6
5
|
const crypto = require('crypto')
|
|
7
6
|
const figgyPudding = require('figgy-pudding')
|
|
8
|
-
const fixOwner = require('./util/fix-owner')
|
|
9
7
|
const fs = require('graceful-fs')
|
|
10
|
-
const
|
|
11
|
-
const ms = require('mississippi')
|
|
8
|
+
const Minipass = require('minipass')
|
|
12
9
|
const path = require('path')
|
|
13
10
|
const ssri = require('ssri')
|
|
14
|
-
const
|
|
15
|
-
|
|
11
|
+
const contentPath = require('./content/path')
|
|
12
|
+
const fixOwner = require('./util/fix-owner')
|
|
13
|
+
const hashToSegments = require('./util/hash-to-segments')
|
|
16
14
|
const indexV = require('../package.json')['cache-version'].index
|
|
17
15
|
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const concat = ms.concat
|
|
22
|
-
const from = ms.from
|
|
16
|
+
const appendFile = util.promisify(fs.appendFile)
|
|
17
|
+
const readFile = util.promisify(fs.readFile)
|
|
18
|
+
const readdir = util.promisify(fs.readdir)
|
|
23
19
|
|
|
24
20
|
module.exports.NotFoundError = class NotFoundError extends Error {
|
|
25
21
|
constructor (cache, key) {
|
|
26
|
-
super(
|
|
22
|
+
super(`No cache entry for ${key} found in ${cache}`)
|
|
27
23
|
this.code = 'ENOENT'
|
|
28
24
|
this.cache = cache
|
|
29
25
|
this.key = key
|
|
@@ -36,6 +32,7 @@ const IndexOpts = figgyPudding({
|
|
|
36
32
|
})
|
|
37
33
|
|
|
38
34
|
module.exports.insert = insert
|
|
35
|
+
|
|
39
36
|
function insert (cache, key, integrity, opts) {
|
|
40
37
|
opts = IndexOpts(opts)
|
|
41
38
|
const bucket = bucketPath(cache, key)
|
|
@@ -46,34 +43,38 @@ function insert (cache, key, integrity, opts) {
|
|
|
46
43
|
size: opts.size,
|
|
47
44
|
metadata: opts.metadata
|
|
48
45
|
}
|
|
49
|
-
return fixOwner
|
|
50
|
-
cache, path.dirname(bucket)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
46
|
+
return fixOwner
|
|
47
|
+
.mkdirfix(cache, path.dirname(bucket))
|
|
48
|
+
.then(() => {
|
|
49
|
+
const stringified = JSON.stringify(entry)
|
|
50
|
+
// NOTE - Cleverness ahoy!
|
|
51
|
+
//
|
|
52
|
+
// This works because it's tremendously unlikely for an entry to corrupt
|
|
53
|
+
// another while still preserving the string length of the JSON in
|
|
54
|
+
// question. So, we just slap the length in there and verify it on read.
|
|
55
|
+
//
|
|
56
|
+
// Thanks to @isaacs for the whiteboarding session that ended up with this.
|
|
57
|
+
return appendFile(bucket, `\n${hashEntry(stringified)}\t${stringified}`)
|
|
58
|
+
})
|
|
59
|
+
.then(() => fixOwner.chownr(cache, bucket))
|
|
60
|
+
.catch((err) => {
|
|
61
|
+
if (err.code === 'ENOENT') {
|
|
62
|
+
return undefined
|
|
63
|
+
}
|
|
64
|
+
throw err
|
|
65
|
+
// There's a class of race conditions that happen when things get deleted
|
|
66
|
+
// during fixOwner, or between the two mkdirfix/chownr calls.
|
|
67
|
+
//
|
|
68
|
+
// It's perfectly fine to just not bother in those cases and lie
|
|
69
|
+
// that the index entry was written. Because it's a cache.
|
|
70
|
+
})
|
|
71
|
+
.then(() => {
|
|
72
|
+
return formatEntry(cache, entry)
|
|
73
|
+
})
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
module.exports.insert.sync = insertSync
|
|
77
|
+
|
|
77
78
|
function insertSync (cache, key, integrity, opts) {
|
|
78
79
|
opts = IndexOpts(opts)
|
|
79
80
|
const bucket = bucketPath(cache, key)
|
|
@@ -86,9 +87,7 @@ function insertSync (cache, key, integrity, opts) {
|
|
|
86
87
|
}
|
|
87
88
|
fixOwner.mkdirfix.sync(cache, path.dirname(bucket))
|
|
88
89
|
const stringified = JSON.stringify(entry)
|
|
89
|
-
fs.appendFileSync(
|
|
90
|
-
bucket, `\n${hashEntry(stringified)}\t${stringified}`
|
|
91
|
-
)
|
|
90
|
+
fs.appendFileSync(bucket, `\n${hashEntry(stringified)}\t${stringified}`)
|
|
92
91
|
try {
|
|
93
92
|
fixOwner.chownr.sync(cache, bucket)
|
|
94
93
|
} catch (err) {
|
|
@@ -100,26 +99,30 @@ function insertSync (cache, key, integrity, opts) {
|
|
|
100
99
|
}
|
|
101
100
|
|
|
102
101
|
module.exports.find = find
|
|
102
|
+
|
|
103
103
|
function find (cache, key) {
|
|
104
104
|
const bucket = bucketPath(cache, key)
|
|
105
|
-
return bucketEntries(bucket)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
return bucketEntries(bucket)
|
|
106
|
+
.then((entries) => {
|
|
107
|
+
return entries.reduce((latest, next) => {
|
|
108
|
+
if (next && next.key === key) {
|
|
109
|
+
return formatEntry(cache, next)
|
|
110
|
+
} else {
|
|
111
|
+
return latest
|
|
112
|
+
}
|
|
113
|
+
}, null)
|
|
114
|
+
})
|
|
115
|
+
.catch((err) => {
|
|
116
|
+
if (err.code === 'ENOENT') {
|
|
117
|
+
return null
|
|
109
118
|
} else {
|
|
110
|
-
|
|
119
|
+
throw err
|
|
111
120
|
}
|
|
112
|
-
}
|
|
113
|
-
}).catch(err => {
|
|
114
|
-
if (err.code === 'ENOENT') {
|
|
115
|
-
return null
|
|
116
|
-
} else {
|
|
117
|
-
throw err
|
|
118
|
-
}
|
|
119
|
-
})
|
|
121
|
+
})
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
module.exports.find.sync = findSync
|
|
125
|
+
|
|
123
126
|
function findSync (cache, key) {
|
|
124
127
|
const bucket = bucketPath(cache, key)
|
|
125
128
|
try {
|
|
@@ -140,70 +143,80 @@ function findSync (cache, key) {
|
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
module.exports.delete = del
|
|
146
|
+
|
|
143
147
|
function del (cache, key, opts) {
|
|
144
148
|
return insert(cache, key, null, opts)
|
|
145
149
|
}
|
|
146
150
|
|
|
147
151
|
module.exports.delete.sync = delSync
|
|
152
|
+
|
|
148
153
|
function delSync (cache, key, opts) {
|
|
149
154
|
return insertSync(cache, key, null, opts)
|
|
150
155
|
}
|
|
151
156
|
|
|
152
157
|
module.exports.lsStream = lsStream
|
|
158
|
+
|
|
153
159
|
function lsStream (cache) {
|
|
154
160
|
const indexDir = bucketDir(cache)
|
|
155
|
-
const stream =
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
161
|
+
const stream = new Minipass({ objectMode: true })
|
|
162
|
+
|
|
163
|
+
readdirOrEmpty(indexDir).then(buckets => Promise.all(
|
|
164
|
+
buckets.map(bucket => {
|
|
165
|
+
const bucketPath = path.join(indexDir, bucket)
|
|
166
|
+
return readdirOrEmpty(bucketPath).then(subbuckets => Promise.all(
|
|
167
|
+
subbuckets.map(subbucket => {
|
|
168
|
+
const subbucketPath = path.join(bucketPath, subbucket)
|
|
169
|
+
|
|
170
|
+
// "/cachename/<bucket 0xFF>/<bucket 0xFF>./*"
|
|
171
|
+
return readdirOrEmpty(subbucketPath).then(entries => Promise.all(
|
|
172
|
+
entries.map(entry => {
|
|
173
|
+
const entryPath = path.join(subbucketPath, entry)
|
|
174
|
+
return bucketEntries(entryPath).then(entries =>
|
|
175
|
+
// using a Map here prevents duplicate keys from
|
|
176
|
+
// showing up twice, I guess?
|
|
177
|
+
entries.reduce((acc, entry) => {
|
|
178
|
+
acc.set(entry.key, entry)
|
|
179
|
+
return acc
|
|
180
|
+
}, new Map())
|
|
181
|
+
).then(reduced => {
|
|
182
|
+
// reduced is a map of key => entry
|
|
183
|
+
for (const entry of reduced.values()) {
|
|
184
|
+
const formatted = formatEntry(cache, entry)
|
|
185
|
+
if (formatted) {
|
|
186
|
+
stream.write(formatted)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}).catch(err => {
|
|
190
|
+
if (err.code === 'ENOENT') { return undefined }
|
|
191
|
+
throw err
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
))
|
|
195
|
+
})
|
|
196
|
+
))
|
|
181
197
|
})
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
198
|
+
))
|
|
199
|
+
.then(
|
|
200
|
+
() => stream.end(),
|
|
201
|
+
err => stream.emit('error', err)
|
|
202
|
+
)
|
|
187
203
|
|
|
188
204
|
return stream
|
|
189
205
|
}
|
|
190
206
|
|
|
191
207
|
module.exports.ls = ls
|
|
208
|
+
|
|
192
209
|
function ls (cache) {
|
|
193
|
-
return
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}))
|
|
200
|
-
})
|
|
210
|
+
return lsStream(cache).collect().then(entries =>
|
|
211
|
+
entries.reduce((acc, xs) => {
|
|
212
|
+
acc[xs.key] = xs
|
|
213
|
+
return acc
|
|
214
|
+
}, {})
|
|
215
|
+
)
|
|
201
216
|
}
|
|
202
217
|
|
|
203
218
|
function bucketEntries (bucket, filter) {
|
|
204
|
-
return
|
|
205
|
-
bucket, 'utf8'
|
|
206
|
-
).then(data => _bucketEntries(data, filter))
|
|
219
|
+
return readFile(bucket, 'utf8').then((data) => _bucketEntries(data, filter))
|
|
207
220
|
}
|
|
208
221
|
|
|
209
222
|
function bucketEntriesSync (bucket, filter) {
|
|
@@ -212,9 +225,11 @@ function bucketEntriesSync (bucket, filter) {
|
|
|
212
225
|
}
|
|
213
226
|
|
|
214
227
|
function _bucketEntries (data, filter) {
|
|
215
|
-
|
|
216
|
-
data.split('\n').forEach(entry => {
|
|
217
|
-
if (!entry) {
|
|
228
|
+
const entries = []
|
|
229
|
+
data.split('\n').forEach((entry) => {
|
|
230
|
+
if (!entry) {
|
|
231
|
+
return
|
|
232
|
+
}
|
|
218
233
|
const pieces = entry.split('\t')
|
|
219
234
|
if (!pieces[1] || hashEntry(pieces[1]) !== pieces[0]) {
|
|
220
235
|
// Hash is no good! Corruption or malice? Doesn't matter!
|
|
@@ -235,25 +250,30 @@ function _bucketEntries (data, filter) {
|
|
|
235
250
|
return entries
|
|
236
251
|
}
|
|
237
252
|
|
|
238
|
-
module.exports.
|
|
253
|
+
module.exports.bucketDir = bucketDir
|
|
254
|
+
|
|
239
255
|
function bucketDir (cache) {
|
|
240
256
|
return path.join(cache, `index-v${indexV}`)
|
|
241
257
|
}
|
|
242
258
|
|
|
243
|
-
module.exports.
|
|
259
|
+
module.exports.bucketPath = bucketPath
|
|
260
|
+
|
|
244
261
|
function bucketPath (cache, key) {
|
|
245
262
|
const hashed = hashKey(key)
|
|
246
|
-
return path.join.apply(
|
|
247
|
-
|
|
248
|
-
|
|
263
|
+
return path.join.apply(
|
|
264
|
+
path,
|
|
265
|
+
[bucketDir(cache)].concat(hashToSegments(hashed))
|
|
266
|
+
)
|
|
249
267
|
}
|
|
250
268
|
|
|
251
|
-
module.exports.
|
|
269
|
+
module.exports.hashKey = hashKey
|
|
270
|
+
|
|
252
271
|
function hashKey (key) {
|
|
253
272
|
return hash(key, 'sha256')
|
|
254
273
|
}
|
|
255
274
|
|
|
256
|
-
module.exports.
|
|
275
|
+
module.exports.hashEntry = hashEntry
|
|
276
|
+
|
|
257
277
|
function hashEntry (str) {
|
|
258
278
|
return hash(str, 'sha1')
|
|
259
279
|
}
|
|
@@ -267,7 +287,9 @@ function hash (str, digest) {
|
|
|
267
287
|
|
|
268
288
|
function formatEntry (cache, entry) {
|
|
269
289
|
// Treat null digests as deletions. They'll shadow any previous entries.
|
|
270
|
-
if (!entry.integrity) {
|
|
290
|
+
if (!entry.integrity) {
|
|
291
|
+
return null
|
|
292
|
+
}
|
|
271
293
|
return {
|
|
272
294
|
key: entry.key,
|
|
273
295
|
integrity: entry.integrity,
|
|
@@ -279,10 +301,11 @@ function formatEntry (cache, entry) {
|
|
|
279
301
|
}
|
|
280
302
|
|
|
281
303
|
function readdirOrEmpty (dir) {
|
|
282
|
-
return
|
|
283
|
-
.
|
|
284
|
-
|
|
285
|
-
}
|
|
304
|
+
return readdir(dir).catch((err) => {
|
|
305
|
+
if (err.code === 'ENOENT' || err.code === 'ENOTDIR') {
|
|
306
|
+
return []
|
|
307
|
+
}
|
|
286
308
|
|
|
287
|
-
|
|
309
|
+
throw err
|
|
310
|
+
})
|
|
288
311
|
}
|
package/lib/memoization.js
CHANGED
|
@@ -5,19 +5,14 @@ const LRU = require('lru-cache')
|
|
|
5
5
|
const MAX_SIZE = 50 * 1024 * 1024 // 50MB
|
|
6
6
|
const MAX_AGE = 3 * 60 * 1000
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const MEMOIZED = new LRU({
|
|
9
9
|
max: MAX_SIZE,
|
|
10
10
|
maxAge: MAX_AGE,
|
|
11
|
-
length: (entry, key) =>
|
|
12
|
-
if (key.startsWith('key:')) {
|
|
13
|
-
return entry.data.length
|
|
14
|
-
} else if (key.startsWith('digest:')) {
|
|
15
|
-
return entry.length
|
|
16
|
-
}
|
|
17
|
-
}
|
|
11
|
+
length: (entry, key) => key.startsWith('key:') ? entry.data.length : entry.length
|
|
18
12
|
})
|
|
19
13
|
|
|
20
14
|
module.exports.clearMemoized = clearMemoized
|
|
15
|
+
|
|
21
16
|
function clearMemoized () {
|
|
22
17
|
const old = {}
|
|
23
18
|
MEMOIZED.forEach((v, k) => {
|
|
@@ -28,22 +23,26 @@ function clearMemoized () {
|
|
|
28
23
|
}
|
|
29
24
|
|
|
30
25
|
module.exports.put = put
|
|
26
|
+
|
|
31
27
|
function put (cache, entry, data, opts) {
|
|
32
28
|
pickMem(opts).set(`key:${cache}:${entry.key}`, { entry, data })
|
|
33
29
|
putDigest(cache, entry.integrity, data, opts)
|
|
34
30
|
}
|
|
35
31
|
|
|
36
32
|
module.exports.put.byDigest = putDigest
|
|
33
|
+
|
|
37
34
|
function putDigest (cache, integrity, data, opts) {
|
|
38
35
|
pickMem(opts).set(`digest:${cache}:${integrity}`, data)
|
|
39
36
|
}
|
|
40
37
|
|
|
41
38
|
module.exports.get = get
|
|
39
|
+
|
|
42
40
|
function get (cache, key, opts) {
|
|
43
41
|
return pickMem(opts).get(`key:${cache}:${key}`)
|
|
44
42
|
}
|
|
45
43
|
|
|
46
44
|
module.exports.get.byDigest = getDigest
|
|
45
|
+
|
|
47
46
|
function getDigest (cache, integrity, opts) {
|
|
48
47
|
return pickMem(opts).get(`digest:${cache}:${integrity}`)
|
|
49
48
|
}
|
|
@@ -52,8 +51,14 @@ class ObjProxy {
|
|
|
52
51
|
constructor (obj) {
|
|
53
52
|
this.obj = obj
|
|
54
53
|
}
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
|
|
55
|
+
get (key) {
|
|
56
|
+
return this.obj[key]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
set (key, val) {
|
|
60
|
+
this.obj[key] = val
|
|
61
|
+
}
|
|
57
62
|
}
|
|
58
63
|
|
|
59
64
|
function pickMem (opts) {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
module.exports.disposer = disposer
|
|
4
|
+
|
|
5
|
+
function disposer (creatorFn, disposerFn, fn) {
|
|
6
|
+
const runDisposer = (resource, result, shouldThrow = false) => {
|
|
7
|
+
return disposerFn(resource)
|
|
8
|
+
.then(
|
|
9
|
+
// disposer resolved, do something with original fn's promise
|
|
10
|
+
() => {
|
|
11
|
+
if (shouldThrow) {
|
|
12
|
+
throw result
|
|
13
|
+
}
|
|
14
|
+
return result
|
|
15
|
+
},
|
|
16
|
+
// Disposer fn failed, crash process
|
|
17
|
+
(err) => {
|
|
18
|
+
throw err
|
|
19
|
+
// Or process.exit?
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return creatorFn
|
|
24
|
+
.then((resource) => {
|
|
25
|
+
// fn(resource) can throw, so wrap in a promise here
|
|
26
|
+
return Promise.resolve().then(() => fn(resource))
|
|
27
|
+
.then((result) => runDisposer(resource, result))
|
|
28
|
+
.catch((err) => runDisposer(resource, err, true))
|
|
29
|
+
})
|
|
30
|
+
}
|
package/lib/util/fix-owner.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const util = require('util')
|
|
4
4
|
|
|
5
|
-
const chownr =
|
|
6
|
-
const mkdirp =
|
|
5
|
+
const chownr = util.promisify(require('chownr'))
|
|
6
|
+
const mkdirp = util.promisify(require('mkdirp'))
|
|
7
7
|
const inflight = require('promise-inflight')
|
|
8
8
|
const inferOwner = require('infer-owner')
|
|
9
9
|
|
|
@@ -32,32 +32,44 @@ const getSelf = () => {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
module.exports.chownr = fixOwner
|
|
35
|
+
|
|
35
36
|
function fixOwner (cache, filepath) {
|
|
36
37
|
if (!process.getuid) {
|
|
37
38
|
// This platform doesn't need ownership fixing
|
|
38
|
-
return
|
|
39
|
+
return Promise.resolve()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getSelf()
|
|
43
|
+
if (self.uid !== 0) {
|
|
44
|
+
// almost certainly can't chown anyway
|
|
45
|
+
return Promise.resolve()
|
|
39
46
|
}
|
|
40
|
-
|
|
47
|
+
|
|
48
|
+
return Promise.resolve(inferOwner(cache)).then((owner) => {
|
|
41
49
|
const { uid, gid } = owner
|
|
42
|
-
getSelf()
|
|
43
50
|
|
|
44
51
|
// No need to override if it's already what we used.
|
|
45
52
|
if (self.uid === uid && self.gid === gid) {
|
|
46
53
|
return
|
|
47
54
|
}
|
|
48
55
|
|
|
49
|
-
return inflight(
|
|
50
|
-
|
|
51
|
-
() => chownr(
|
|
56
|
+
return inflight('fixOwner: fixing ownership on ' + filepath, () =>
|
|
57
|
+
chownr(
|
|
52
58
|
filepath,
|
|
53
59
|
typeof uid === 'number' ? uid : self.uid,
|
|
54
60
|
typeof gid === 'number' ? gid : self.gid
|
|
55
|
-
).catch(
|
|
61
|
+
).catch((err) => {
|
|
62
|
+
if (err.code === 'ENOENT') {
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
65
|
+
throw err
|
|
66
|
+
})
|
|
56
67
|
)
|
|
57
68
|
})
|
|
58
69
|
}
|
|
59
70
|
|
|
60
71
|
module.exports.chownr.sync = fixOwnerSync
|
|
72
|
+
|
|
61
73
|
function fixOwnerSync (cache, filepath) {
|
|
62
74
|
if (!process.getuid) {
|
|
63
75
|
// This platform doesn't need ownership fixing
|
|
@@ -65,6 +77,11 @@ function fixOwnerSync (cache, filepath) {
|
|
|
65
77
|
}
|
|
66
78
|
const { uid, gid } = inferOwner.sync(cache)
|
|
67
79
|
getSelf()
|
|
80
|
+
if (self.uid !== 0) {
|
|
81
|
+
// almost certainly can't chown anyway
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
68
85
|
if (self.uid === uid && self.gid === gid) {
|
|
69
86
|
// No need to override if it's already what we used.
|
|
70
87
|
return
|
|
@@ -85,24 +102,30 @@ function fixOwnerSync (cache, filepath) {
|
|
|
85
102
|
}
|
|
86
103
|
|
|
87
104
|
module.exports.mkdirfix = mkdirfix
|
|
105
|
+
|
|
88
106
|
function mkdirfix (cache, p, cb) {
|
|
89
107
|
// we have to infer the owner _before_ making the directory, even though
|
|
90
108
|
// we aren't going to use the results, since the cache itself might not
|
|
91
109
|
// exist yet. If we mkdirp it, then our current uid/gid will be assumed
|
|
92
110
|
// to be correct if it creates the cache folder in the process.
|
|
93
|
-
return
|
|
94
|
-
return mkdirp(p)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
111
|
+
return Promise.resolve(inferOwner(cache)).then(() => {
|
|
112
|
+
return mkdirp(p)
|
|
113
|
+
.then((made) => {
|
|
114
|
+
if (made) {
|
|
115
|
+
return fixOwner(cache, made).then(() => made)
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
.catch((err) => {
|
|
119
|
+
if (err.code === 'EEXIST') {
|
|
120
|
+
return fixOwner(cache, p).then(() => null)
|
|
121
|
+
}
|
|
122
|
+
throw err
|
|
123
|
+
})
|
|
102
124
|
})
|
|
103
125
|
}
|
|
104
126
|
|
|
105
127
|
module.exports.mkdirfix.sync = mkdirfixSync
|
|
128
|
+
|
|
106
129
|
function mkdirfixSync (cache, p) {
|
|
107
130
|
try {
|
|
108
131
|
inferOwner.sync(cache)
|
package/lib/util/move-file.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const fs = require('graceful-fs')
|
|
4
|
-
const
|
|
5
|
-
const chmod =
|
|
6
|
-
const unlink =
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
const util = require('util')
|
|
5
|
+
const chmod = util.promisify(fs.chmod)
|
|
6
|
+
const unlink = util.promisify(fs.unlink)
|
|
7
|
+
const stat = util.promisify(fs.stat)
|
|
8
|
+
const move = require('move-concurrently')
|
|
9
|
+
const pinflight = require('promise-inflight')
|
|
9
10
|
|
|
10
11
|
module.exports = moveFile
|
|
12
|
+
|
|
11
13
|
function moveFile (src, dest) {
|
|
12
14
|
// This isn't quite an fs.rename -- the assumption is that
|
|
13
15
|
// if `dest` already exists, and we get certain errors while
|
|
@@ -18,34 +20,37 @@ function moveFile (src, dest) {
|
|
|
18
20
|
// content their own way.
|
|
19
21
|
//
|
|
20
22
|
// Note that, as the name suggests, this strictly only supports file moves.
|
|
21
|
-
return
|
|
22
|
-
fs.link(src, dest, err => {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
fs.link(src, dest, (err) => {
|
|
23
25
|
if (err) {
|
|
24
26
|
if (err.code === 'EEXIST' || err.code === 'EBUSY') {
|
|
25
27
|
// file already exists, so whatever
|
|
26
28
|
} else if (err.code === 'EPERM' && process.platform === 'win32') {
|
|
27
29
|
// file handle stayed open even past graceful-fs limits
|
|
28
30
|
} else {
|
|
29
|
-
return
|
|
31
|
+
return reject(err)
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
|
-
return
|
|
34
|
+
return resolve()
|
|
33
35
|
})
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
36
|
+
})
|
|
37
|
+
.then(() => {
|
|
38
|
+
// content should never change for any reason, so make it read-only
|
|
39
|
+
return Promise.all([
|
|
40
|
+
unlink(src),
|
|
41
|
+
process.platform !== 'win32' && chmod(dest, '0444')
|
|
42
|
+
])
|
|
43
|
+
})
|
|
44
|
+
.catch(() => {
|
|
45
|
+
return pinflight('cacache-move-file:' + dest, () => {
|
|
46
|
+
return stat(dest).catch((err) => {
|
|
47
|
+
if (err.code !== 'ENOENT') {
|
|
48
|
+
// Something else is wrong here. Bail bail bail
|
|
49
|
+
throw err
|
|
50
|
+
}
|
|
51
|
+
// file doesn't already exist! let's try a rename -> copy fallback
|
|
52
|
+
return move(src, dest, { Promise, fs })
|
|
53
|
+
})
|
|
48
54
|
})
|
|
49
55
|
})
|
|
50
|
-
})
|
|
51
56
|
}
|