Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions lib/ensure/__tests__/symlink.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -399,4 +399,27 @@ describe('fse-ensure-symlink', () => {
assert.strictEqual(content, 'content')
})
})

// https://github.com/jprichardson/node-fs-extra/issues/925
describe('ensureSymlink() when dest is an existing broken symlink (issue #925)', () => {
it('should fail with EEXIST (not ENOENT) when dest is a broken symlink', async () => {
const srcpath = path.join(TEST_DIR, 'src-925.txt')
const dstpath = path.join(TEST_DIR, 'broken-link-925')
fs.writeFileSync(srcpath, 'src\n')
fs.symlinkSync(path.join(TEST_DIR, 'missing-925.txt'), dstpath)
assert.strictEqual(fs.lstatSync(dstpath).isSymbolicLink(), true)

await assert.rejects(ensureSymlink(srcpath, dstpath), { code: 'EEXIST' })
})

it('should fail with EEXIST (not ENOENT) when dest is a broken symlink (sync)', () => {
const srcpath = path.join(TEST_DIR, 'src-925-sync.txt')
const dstpath = path.join(TEST_DIR, 'broken-link-925-sync')
fs.writeFileSync(srcpath, 'src\n')
fs.symlinkSync(path.join(TEST_DIR, 'missing-925-sync.txt'), dstpath)
assert.strictEqual(fs.lstatSync(dstpath).isSymbolicLink(), true)

assert.throws(() => ensureSymlinkSync(srcpath, dstpath), { code: 'EEXIST' })
})
})
})
18 changes: 14 additions & 4 deletions lib/ensure/symlink.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,13 @@ async function createSymlink (srcpath, dstpath, type) {
}
}

const dstStat = await fs.stat(dstpath, { bigint: true })
if (areIdentical(srcStat, dstStat)) return
// Following the existing symlink fails if it's broken; in that case fall
// through so fs.symlink reports EEXIST, rather than leaking the stat ENOENT.
let dstStat
try {
dstStat = await fs.stat(dstpath, { bigint: true })
} catch { }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like it might be wise to ensure the error is ENOENT before swallowing, and throw otherwise.

if (dstStat && areIdentical(srcStat, dstStat)) return
}

const relative = await symlinkPaths(srcpath, dstpath)
Expand Down Expand Up @@ -72,8 +77,13 @@ function createSymlinkSync (srcpath, dstpath, type) {
}
}

const dstStat = fs.statSync(dstpath, { bigint: true })
if (areIdentical(srcStat, dstStat)) return
// Following the existing symlink fails if it's broken; in that case fall
// through so fs.symlinkSync reports EEXIST, rather than leaking the stat ENOENT.
let dstStat
try {
dstStat = fs.statSync(dstpath, { bigint: true })
} catch { }
if (dstStat && areIdentical(srcStat, dstStat)) return
}

const relative = symlinkPathsSync(srcpath, dstpath)
Expand Down
Loading