Skip to content

Commit 987cbbc

Browse files
isaacsry
authored andcommitted
Handle cyclic links smarter in fs.realpath
Rather than aborting in the face of *any* repeated link in a given path, instead only abort if such a cycle actually makes a given path unresolvable. Test for this by doing a normal stat. Still use the seenLinks object to cache link contents so as to cut own a little bit on readlink calls. Also add a pathological test that fails without the change to fs.js.
1 parent 4c514a7 commit 987cbbc

2 files changed

Lines changed: 40 additions & 7 deletions

File tree

lib/fs.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -508,9 +508,11 @@ function realpathSync (p) {
508508
continue;
509509
}
510510
var id = stat.dev.toString(32)+':'+stat.ino.toString(32);
511-
if (seenLinks[id]) throw new Error("cyclic link at "+part);
512-
seenLinks[id] = true;
513-
var target = fs.readlinkSync(part);
511+
if (!seenLinks[id]) {
512+
fs.statSync(part);
513+
seenLinks[id] = fs.readlinkSync(part);
514+
}
515+
var target = seenLinks[id];
514516
if (target.charAt(0) === '/') {
515517
// absolute. Start over.
516518
buf = [''];
@@ -565,9 +567,13 @@ function realpath (p, cb) {
565567
return process.nextTick(LOOP);
566568
}
567569
var id = stat.dev.toString(32)+':'+stat.ino.toString(32);
568-
if (seenLinks[id]) return cb(new Error("cyclic link at "+part));
569-
seenLinks[id] = true;
570-
fs.readlink(part, gotTarget);
570+
if (seenLinks[id]) return gotTarget(null, seenLinks[id]);
571+
fs.stat(part, function (er) {
572+
if (er) return cb(er)
573+
fs.readlink(part, function (er, target) {
574+
gotTarget(er, seenLinks[id] = target);
575+
});
576+
})
571577
}
572578
function gotTarget (er, target) {
573579
if (er) return cb(er);

test/simple/test-fs-realpath.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ function bashRealpath(path, callback) {
3232
// sub-tests:
3333

3434
function test_simple_relative_symlink(callback) {
35+
console.log("test_simple_relative_symlink");
3536
var entry = common.fixturesDir+'/cycles/symlink',
3637
expected = common.fixturesDir+'/cycles/root.js';
3738
[
@@ -51,6 +52,7 @@ function test_simple_relative_symlink(callback) {
5152
}
5253

5354
function test_simple_absolute_symlink(callback) {
55+
console.log("test_simple_absolute_symlink");
5456
bashRealpath(common.fixturesDir, function(err, fixturesAbsDir) {
5557
if (err) return callback(err);
5658
var entry = fixturesAbsDir+'/cycles/symlink',
@@ -73,6 +75,7 @@ function test_simple_absolute_symlink(callback) {
7375
}
7476

7577
function test_deep_relative_file_symlink(callback) {
78+
console.log("test_deep_relative_file_symlink");
7679
var expected = path.join(common.fixturesDir, 'cycles', 'root.js');
7780
var linkData1 = "../../cycles/root.js";
7881
var linkPath1 = path.join(common.fixturesDir, "nested-index", 'one', 'symlink1.js');
@@ -94,6 +97,7 @@ function test_deep_relative_file_symlink(callback) {
9497
}
9598

9699
function test_deep_relative_dir_symlink(callback) {
100+
console.log("test_deep_relative_dir_symlink");
97101
var expected = path.join(common.fixturesDir, 'cycles', 'folder');
98102
var linkData1b = "../../cycles/folder";
99103
var linkPath1b = path.join(common.fixturesDir, "nested-index", 'one', 'symlink1-dir');
@@ -116,6 +120,7 @@ function test_deep_relative_dir_symlink(callback) {
116120
}
117121

118122
function test_cyclic_link_protection(callback) {
123+
console.log("test_cyclic_link_protection");
119124
var entry = common.fixturesDir+'/cycles/realpath-3a';
120125
[
121126
[entry, '../cycles/realpath-3b'],
@@ -133,7 +138,24 @@ function test_cyclic_link_protection(callback) {
133138
});
134139
}
135140

141+
function test_cyclic_link_overprotection (callback) {
142+
console.log("test_cyclic_link_overprotection");
143+
var cycles = common.fixturesDir+'/cycles';
144+
var expected = fs.realpathSync(cycles);
145+
var folder = cycles+'/folder';
146+
var link = folder+'/cycles';
147+
var testPath = cycles;
148+
for (var i = 0; i < 10; i ++) testPath += '/folder/cycles';
149+
try {fs.unlinkSync(link)} catch (ex) {}
150+
fs.symlinkSync(cycles, link);
151+
assert.equal(fs.realpathSync(testPath), expected);
152+
asynctest(fs.realpath, [testPath], callback, function (er, res) {
153+
assert.equal(res, expected);
154+
});
155+
}
156+
136157
function test_relative_input_cwd(callback) {
158+
console.log("test_relative_input_cwd");
137159
var p = common.fixturesDir.lastIndexOf('/');
138160
var entrydir = common.fixturesDir.substr(0, p);
139161
var entry = common.fixturesDir.substr(p+1)+'/cycles/realpath-3a';
@@ -161,6 +183,7 @@ function test_relative_input_cwd(callback) {
161183
}
162184

163185
function test_deep_symlink_mix(callback) {
186+
console.log("test_deep_symlink_mix");
164187
// todo: check to see that common.fixturesDir is not rooted in the
165188
// same directory as our test symlink.
166189
// obtain our current realpath using bash (so we can test ourselves)
@@ -209,6 +232,7 @@ function test_deep_symlink_mix(callback) {
209232
}
210233

211234
function test_non_symlinks(callback) {
235+
console.log("test_non_symlinks");
212236
bashRealpath(common.fixturesDir, function(err, fixturesAbsDir) {
213237
if (err) return callback(err);
214238
var p = fixturesAbsDir.lastIndexOf('/');
@@ -229,6 +253,7 @@ function test_non_symlinks(callback) {
229253

230254
var upone = path.join(process.cwd(), "..");
231255
function test_escape_cwd (cb) {
256+
console.log("test_escape_cwd");
232257
asynctest(fs.realpath, [".."], cb, function(er, uponeActual){
233258
assert.equal(upone, uponeActual,
234259
"realpath('..') expected: "+upone+" actual:"+uponeActual);
@@ -247,6 +272,7 @@ assert.equal(upone, uponeActual,
247272
// `-- link -> /tmp/node-test-realpath-abs-kids/a/b/
248273
// realpath(root+'/a/link/c/x.txt') ==> root+'/a/b/c/x.txt'
249274
function test_abs_with_kids (cb) {
275+
console.log("test_abs_with_kids");
250276
bashRealpath(common.fixturesDir, function(err, fixturesAbsDir) {
251277
var root = fixturesAbsDir+'/node-test-realpath-abs-kids';
252278
function cleanup () {
@@ -298,11 +324,12 @@ var tests = [
298324
test_deep_relative_file_symlink,
299325
test_deep_relative_dir_symlink,
300326
test_cyclic_link_protection,
327+
test_cyclic_link_overprotection,
301328
test_relative_input_cwd,
302329
test_deep_symlink_mix,
303330
test_non_symlinks,
304331
test_escape_cwd,
305-
test_abs_with_kids
332+
test_abs_with_kids
306333
];
307334
var numtests = tests.length;
308335
function runNextTest(err) {

0 commit comments

Comments
 (0)