Skip to content

Commit

Permalink
fix(store): don't prematurely bail out of adding source files if ENOE…
Browse files Browse the repository at this point in the history
…NT is thrown (#6932)

Broken symbolic links will cause a `stat'-call to throw resulting in an
arbitrary amount of promises that won't get to settle before the index is
returned.

Processing subdirectories in the following iteration in the event loop makes
this consistently reproducible.

---------

Co-authored-by: Martin Madsen <mj@blackbird.online>
Co-authored-by: Zoltan Kochan <z@kochan.io>
  • Loading branch information
3 people committed Aug 24, 2023
1 parent f2009d1 commit 0fd9e6a
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 31 deletions.
5 changes: 5 additions & 0 deletions .changeset/breezy-carpets-obey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pnpm/store.cafs": patch
---

Don't prematurely bail out of adding source files if ENOENT is thrown [#6932](https://github.com/pnpm/pnpm/pull/6932).
64 changes: 33 additions & 31 deletions store/cafs/src/addFilesFromDir.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { promises as fs } from 'fs'
import { promises as fs, type Stats } from 'fs'
import path from 'path'
import type {
DeferredManifestPromise,
Expand Down Expand Up @@ -39,39 +39,41 @@ async function _retrieveFileIntegrities (
index: FilesIndex,
deferredManifest?: DeferredManifestPromise
) {
try {
const files = await fs.readdir(currDir)
await Promise.all(files.map(async (file) => {
const fullPath = path.join(currDir, file)
const stat = await fs.stat(fullPath)
if (stat.isDirectory()) {
await _retrieveFileIntegrities(cafs, rootDir, fullPath, index)
const files = await fs.readdir(currDir, { withFileTypes: true })
await Promise.all(files.map(async (file) => {
const fullPath = path.join(currDir, file.name)
if (file.isDirectory()) {
await _retrieveFileIntegrities(cafs, rootDir, fullPath, index)
return
}
if (file.isFile()) {
const relativePath = path.relative(rootDir, fullPath)
let stat: Stats
try {
stat = await fs.stat(fullPath)
} catch (err: any) { // eslint-disable-line
if (err.code !== 'ENOENT') {
throw err
}
return
}
if (stat.isFile()) {
const relativePath = path.relative(rootDir, fullPath)
const writeResult = limit(async () => {
if ((deferredManifest != null) && rootDir === currDir && file === 'package.json') {
const buffer = await gfs.readFile(fullPath)
parseJsonBuffer(buffer, deferredManifest)
return cafs.addBuffer(buffer, stat.mode)
}
if (stat.size < MAX_BULK_SIZE) {
const buffer = await gfs.readFile(fullPath)
return cafs.addBuffer(buffer, stat.mode)
}
return cafs.addStream(gfs.createReadStream(fullPath), stat.mode)
})
index[relativePath] = {
mode: stat.mode,
size: stat.size,
writeResult,
const writeResult = limit(async () => {
if ((deferredManifest != null) && rootDir === currDir && file.name === 'package.json') {
const buffer = await gfs.readFile(fullPath)
parseJsonBuffer(buffer, deferredManifest)
return cafs.addBuffer(buffer, stat.mode)
}
if (stat.size < MAX_BULK_SIZE) {
const buffer = await gfs.readFile(fullPath)
return cafs.addBuffer(buffer, stat.mode)
}
return cafs.addStream(gfs.createReadStream(fullPath), stat.mode)
})
index[relativePath] = {
mode: stat.mode,
size: stat.size,
writeResult,
}
}))
} catch (err: any) { // eslint-disable-line
if (err.code !== 'ENOENT') {
throw err
}
}
}))
}
1 change: 1 addition & 0 deletions store/cafs/test/fixtures/broken-symlink/doesnt-exist
Empty file.
10 changes: 10 additions & 0 deletions store/cafs/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ describe('cafs', () => {
expect(fs.readFileSync(filePath, 'utf8')).toBe('foo\n')
expect(await manifest.promise).toEqual(undefined)
})

it('ignores broken symlinks when traversing subdirectories', async () => {
const storeDir = tempy.directory()
const srcDir = path.join(__dirname, 'fixtures/broken-symlink')
const manifest = pDefer<DependencyManifest>()
const addFiles = async () => createCafs(storeDir).addFilesFromDir(srcDir, manifest)

const filesIndex = await addFiles()
expect(filesIndex['subdir/should-exist.txt']).toBeDefined()
})
})

describe('checkPkgFilesIntegrity()', () => {
Expand Down

0 comments on commit 0fd9e6a

Please sign in to comment.