Skip to content

Commit

Permalink
Fix bug in symlink traversal with indirect targets (#1084)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #1084

Fixes a bug where symlinks beginning with indirections (`../`) may fail to traverse in some `projectRoot` / `watchFolders` configurations.

This occurs due to incomplete "normalisation" of a symlink target in `metro-file-map`, relative to the project root. Given a project root of `/project` and a symlink `/project/src/link-to-foo  -> ../../project/foo.js`, we would normalise the target to `../project/foo.js` rather than simply `foo.js` - we were not taking into account that further normalisation could be possible after joining with `projectRoot`.

"Normal" paths are defined as being *fully* normalised relative to the project root. The unnecessary indirection out and back into the project root fails because `TreeFS` is a DAG, and internally there is no entry for `rootNode.get('..').get('project')` - this would be a cycle back to `rootNode`.

Changelog:
```
 * **[Fix]:** Symlinks with indirections may not be resolvable
```

Reviewed By: yungsters

Differential Revision: D49323614

fbshipit-source-id: e01a10a0455b0af22ebc3cdd7c34ca8fd888d0f4
  • Loading branch information
robhogan committed Jan 9, 2024
1 parent 4cc6b08 commit 8ef5a39
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 2 deletions.
25 changes: 25 additions & 0 deletions packages/metro-file-map/src/lib/TreeFS.js
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,31 @@ export default class TreeFS implements MutableFileSystem {
}
}

_resolveSymlinkTargetToNormalPath(
symlinkNode: FileMetaData,
canonicalPathOfSymlink: Path,
): Path {
let normalSymlinkTarget = this.#cachedNormalSymlinkTargets.get(symlinkNode);
if (normalSymlinkTarget != null) {
return normalSymlinkTarget;
}

const literalSymlinkTarget = symlinkNode[H.SYMLINK];
invariant(
typeof literalSymlinkTarget === 'string',
'Expected symlink target to be populated.',
);
const absoluteSymlinkTarget = path.resolve(
this.#rootDir,
canonicalPathOfSymlink,
'..', // Symlink target is relative to its containing directory.
literalSymlinkTarget, // May be absolute, in which case the above are ignored
);
normalSymlinkTarget = path.relative(this.#rootDir, absoluteSymlinkTarget);
this.#cachedNormalSymlinkTargets.set(symlinkNode, normalSymlinkTarget);
return normalSymlinkTarget;
}

_getFileData(
filePath: Path,
opts: {followLeaf: boolean} = {followLeaf: true},
Expand Down
5 changes: 3 additions & 2 deletions packages/metro-file-map/src/lib/__tests__/TreeFS-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ describe.each([['win32'], ['posix']])('TreeFS on %s', platform => {
[p('foo/link-to-bar.js'), ['', 0, 0, 0, '', '', p('../bar.js')]],
[p('foo/link-to-another.js'), ['', 0, 0, 0, '', '', p('another.js')]],
[p('../outside/external.js'), ['', 0, 0, 0, '', '', 0]],
[p('bar.js'), ['', 234, 0, 0, '', '', 0]],
[p('link-to-foo'), ['', 456, 0, 0, '', '', p('./foo')]],
[p('bar.js'), ['bar', 234, 0, 0, '', '', 0]],
[p('link-to-foo'), ['', 456, 0, 0, '', '', p('./../project/foo')]],
[p('abs-link-out'), ['', 456, 0, 0, '', '', p('/outside/./baz/..')]],
[p('root'), ['', 0, 0, 0, '', '', '..']],
[p('link-to-nowhere'), ['', 0, 0, 0, '', '', p('./nowhere')]],
[p('link-to-self'), ['', 0, 0, 0, '', '', p('./link-to-self')]],
Expand Down

0 comments on commit 8ef5a39

Please sign in to comment.