Skip to content

Commit

Permalink
fix: reserve paths case-insensitively
Browse files Browse the repository at this point in the history
The path reservation system prevents multiple file entries clobbering
one another while in flight, while allowing maximal parallelization
in unpacking otherwise.

However, on case-insensitive file systems, this can still introduce a
race condition if the entries in the archive have different cases, as
capital and lowercase entries will not be treated as reserving the same
path.

Normalize slashes and cases immediately when reserving paths, such that
paths which differ only in case or slash repetition are treated as
identical paths.
  • Loading branch information
isaacs committed Aug 11, 2021
1 parent fd6accb commit 5c5059a
Show file tree
Hide file tree
Showing 2 changed files with 5 additions and 4 deletions.
7 changes: 4 additions & 3 deletions lib/path-reservations.js
Expand Up @@ -8,6 +8,7 @@

const assert = require('assert')
const normPath = require('./normalize-windows-path.js')
const { join } = require('path')

module.exports = () => {
// path => [function or Set]
Expand All @@ -19,10 +20,9 @@ module.exports = () => {
const reservations = new Map()

// return a set of parent dirs for a given path
const { join } = require('path')
const getDirs = path =>
normPath(join(path)).split('/').slice(0, -1).reduce((set, path) =>
set.length ? set.concat(normPath(join(set[set.length-1], path)))
path.split('/').slice(0, -1).reduce((set, path) =>
set.length ? set.concat(normPath(join(set[set.length - 1], path)))
: [path], [])

// functions currently running
Expand Down Expand Up @@ -99,6 +99,7 @@ module.exports = () => {
}

const reserve = (paths, fn) => {
paths = paths.map(p => normPath(join(p)).toLowerCase())
const dirs = new Set(
paths.map(path => getDirs(path)).reduce((a, b) => a.concat(b))
)
Expand Down
2 changes: 1 addition & 1 deletion test/path-reservations.js
Expand Up @@ -49,7 +49,7 @@ t.test('basic race', t => {
}

t.ok(reserve(['a/b/c/d'], file), 'file starts right away')
t.notOk(reserve(['a/b/c/d', 'a/b/e'], link), 'link waits')
t.notOk(reserve(['a/B/c////D', 'a/b/e'], link), 'link waits')
t.notOk(reserve(['a/b/e/f'], dir), 'dir waits')
t.notOk(reserve(['a/b'], dir2), 'dir2 waits')
t.notOk(reserve(['a/b/x'], dir3), 'dir3 waits')
Expand Down

0 comments on commit 5c5059a

Please sign in to comment.