Skip to content

Commit c82b33d

Browse files
committed
Add recursive traversal for hardlinked directories to NativeWatcher
1 parent 5fbf95e commit c82b33d

File tree

1 file changed

+45
-1
lines changed

1 file changed

+45
-1
lines changed

packages/metro-file-map/src/watchers/NativeWatcher.js

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* @flow strict-local
99
*/
1010

11-
import type {FSWatcher} from 'fs';
11+
import type {Dirent, FSWatcher} from 'fs';
1212

1313
import {AbstractWatcher} from './AbstractWatcher';
1414
import {includedByGlob, typeFromStat} from './common';
@@ -95,6 +95,42 @@ export default class NativeWatcher extends AbstractWatcher {
9595
}
9696
}
9797

98+
async _handleHardlinkDirectory(relativePath: string) {
99+
debug(
100+
'Crawling hardlinked directory %s (root: %s)',
101+
relativePath,
102+
this.root,
103+
);
104+
const direntQueue = [relativePath];
105+
let readdirPath: ?string;
106+
while ((readdirPath = direntQueue.pop()) != null) {
107+
if (readdirPath == null) {
108+
return;
109+
}
110+
let entries: $ReadOnlyArray<Dirent>;
111+
try {
112+
entries = await fsPromises.readdir(readdirPath, {withFileTypes: true});
113+
} catch (error) {
114+
this.emitError(error);
115+
continue;
116+
}
117+
for (const dirent of entries) {
118+
if (dirent.isDirectory()) {
119+
// We can ignore directories, to avoid triggering nested hardlink
120+
// handling, but also since the parent FileMap ultimately discards
121+
// directory events anyway
122+
direntQueue.push(path.join(readdirPath, dirent.name.toString()));
123+
} else if (dirent.isFile() || dirent.isSymbolicLink()) {
124+
this._handleEvent(
125+
path.join(readdirPath, dirent.name.toString()),
126+
).catch(error => {
127+
this.emitError(error);
128+
});
129+
}
130+
}
131+
}
132+
}
133+
98134
async _handleEvent(relativePath: string) {
99135
const absolutePath = path.resolve(this.root, relativePath);
100136
if (this.doIgnore(relativePath)) {
@@ -125,6 +161,14 @@ export default class NativeWatcher extends AbstractWatcher {
125161
size: stat.size,
126162
},
127163
});
164+
165+
// If we have a hardlink on a copy-on-write file system (indicated by nlink i.e. multiple links to same inode)
166+
// then we should manually crawl the entire directory, since we're not expecting events for files inside it
167+
if (type === 'd' && stat.nlink > 1) {
168+
this._handleHardlinkDirectory(relativePath).catch(error => {
169+
this.emitError(error);
170+
});
171+
}
128172
} catch (error) {
129173
if (error?.code !== 'ENOENT') {
130174
this.emitError(error);

0 commit comments

Comments
 (0)