Skip to content

Commit 277aeff

Browse files
authored
Merge pull request #47 from Tyriar/40_infinite_linux_symlink
Prevent infinite loop when watching circular symlinks on Linux
2 parents 67771cf + f6dc411 commit 277aeff

File tree

3 files changed

+74
-19
lines changed

3 files changed

+74
-19
lines changed

includes/linux/InotifyTree.h

+7-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <sstream>
1010
#include <vector>
1111
#include <map>
12+
#include <unordered_set>
1213

1314
class InotifyTree {
1415
public:
@@ -32,7 +33,8 @@ class InotifyTree {
3233
int inotifyInstance,
3334
InotifyNode *parent,
3435
std::string directory,
35-
std::string name
36+
std::string name,
37+
ino_t inodeNumber
3638
);
3739

3840
void addChild(std::string name);
@@ -61,6 +63,7 @@ class InotifyTree {
6163
std::map<std::string, InotifyNode *> *mChildren;
6264
std::string mDirectory;
6365
std::string mFullPath;
66+
ino_t mInodeNumber;
6467
const int mInotifyInstance;
6568
std::string mName;
6669
InotifyNode *mParent;
@@ -72,10 +75,13 @@ class InotifyTree {
7275
void setError(std::string error);
7376
void addNodeReferenceByWD(int watchDescriptor, InotifyNode *node);
7477
void removeNodeReferenceByWD(int watchDescriptor);
78+
bool addInode(ino_t inodeNumber);
79+
void removeInode(ino_t inodeNumber);
7580

7681
std::string mError;
7782
const int mInotifyInstance;
7883
std::map<int, InotifyNode *> *mInotifyNodeByWatchDescriptor;
84+
std::unordered_set<ino_t> inodes;
7985
InotifyNode *mRoot;
8086

8187
friend class InotifyNode;

js/spec/index-spec.js

+22
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,28 @@ describe('Node Sentinel File Watcher', function() {
418418
.then(done, () =>
419419
watch.stop().then((err) => done.fail(err)));
420420
});
421+
422+
it('does not loop endlessly when watching directories with recursive symlinks', (done) => {
423+
fse.mkdirSync(path.join(workDir, 'test'));
424+
fse.symlinkSync(path.join(workDir, 'test'), path.join(workDir, 'test', 'link'));
425+
426+
let watch;
427+
428+
return nsfw(
429+
workDir,
430+
() => {},
431+
{ debounceMS: DEBOUNCE, errorCallback() {} }
432+
)
433+
.then(_w => {
434+
watch = _w;
435+
return watch.start();
436+
})
437+
.then(() => {
438+
return watch.stop();
439+
})
440+
.then(done, () =>
441+
watch.stop().then((err) => done.fail(err)));
442+
});
421443
});
422444

423445
describe('Errors', function() {

src/linux/InotifyTree.cpp

+45-18
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,20 @@ InotifyTree::InotifyTree(int inotifyInstance, std::string path):
1818
watchName = path.substr(location + 1);
1919
}
2020

21+
struct stat file;
22+
if (stat((directory + "/" + watchName).c_str(), &file) < 0) {
23+
mRoot = NULL;
24+
return;
25+
}
26+
27+
addInode(file.st_ino);
2128
mRoot = new InotifyNode(
2229
this,
2330
mInotifyInstance,
2431
NULL,
2532
directory,
26-
watchName
33+
watchName,
34+
file.st_ino
2735
);
2836

2937
if (
@@ -114,6 +122,14 @@ void InotifyTree::setError(std::string error) {
114122
mError = error;
115123
}
116124

125+
bool InotifyTree::addInode(ino_t inodeNumber) {
126+
return inodes.insert(inodeNumber).second;
127+
}
128+
129+
void InotifyTree::removeInode(ino_t inodeNumber) {
130+
inodes.erase(inodeNumber);
131+
}
132+
117133
InotifyTree::~InotifyTree() {
118134
if (isRootAlive()) {
119135
delete mRoot;
@@ -128,9 +144,11 @@ InotifyTree::InotifyNode::InotifyNode(
128144
int inotifyInstance,
129145
InotifyNode *parent,
130146
std::string directory,
131-
std::string name
147+
std::string name,
148+
ino_t inodeNumber
132149
):
133150
mDirectory(directory),
151+
mInodeNumber(inodeNumber),
134152
mInotifyInstance(inotifyInstance),
135153
mName(name),
136154
mParent(parent),
@@ -170,7 +188,8 @@ InotifyTree::InotifyNode::InotifyNode(
170188

171189
if (
172190
stat(filePath.c_str(), &file) < 0 ||
173-
!S_ISDIR(file.st_mode)
191+
!S_ISDIR(file.st_mode) ||
192+
!mTree->addInode(file.st_ino) // Skip this inode if already watching
174193
) {
175194
continue;
176195
}
@@ -180,7 +199,8 @@ InotifyTree::InotifyNode::InotifyNode(
180199
mInotifyInstance,
181200
this,
182201
mFullPath,
183-
fileName
202+
fileName,
203+
file.st_ino
184204
);
185205

186206
if (child->isAlive()) {
@@ -198,6 +218,8 @@ InotifyTree::InotifyNode::InotifyNode(
198218
}
199219

200220
InotifyTree::InotifyNode::~InotifyNode() {
221+
mTree->removeInode(mInodeNumber);
222+
201223
if (mWatchDescriptorInitialized) {
202224
inotify_rm_watch(mInotifyInstance, mWatchDescriptor);
203225
mTree->removeNodeReferenceByWD(mWatchDescriptor);
@@ -211,21 +233,26 @@ InotifyTree::InotifyNode::~InotifyNode() {
211233
}
212234

213235
void InotifyTree::InotifyNode::addChild(std::string name) {
214-
InotifyNode *child = new InotifyNode(
215-
mTree,
216-
mInotifyInstance,
217-
this,
218-
mFullPath,
219-
name
220-
);
236+
struct stat file;
221237

222-
if (
223-
child->isAlive() &&
224-
child->inotifyInit()
225-
) {
226-
(*mChildren)[name] = child;
227-
} else {
228-
delete child;
238+
if (stat(createFullPath(mFullPath, name).c_str(), &file) >= 0 && mTree->addInode(file.st_ino)) {
239+
InotifyNode *child = new InotifyNode(
240+
mTree,
241+
mInotifyInstance,
242+
this,
243+
mFullPath,
244+
name,
245+
file.st_ino
246+
);
247+
248+
if (
249+
child->isAlive() &&
250+
child->inotifyInit()
251+
) {
252+
(*mChildren)[name] = child;
253+
} else {
254+
delete child;
255+
}
229256
}
230257
}
231258

0 commit comments

Comments
 (0)