1// Copyright Joyent, Inc. and other Node contributors. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a 4// copy of this software and associated documentation files (the 5// "Software"), to deal in the Software without restriction, including 6// without limitation the rights to use, copy, modify, merge, publish, 7// distribute, sublicense, and/or sell copies of the Software, and to permit 8// persons to whom the Software is furnished to do so, subject to the 9// following conditions: 10// 11// The above copyright notice and this permission notice shall be included 12// in all copies or substantial portions of the Software. 13// 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20// USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22'use strict'; 23const common = require('../common'); 24const fixtures = require('../common/fixtures'); 25const tmpdir = require('../common/tmpdir'); 26 27if (!common.isMainThread) 28 common.skip('process.chdir is not available in Workers'); 29 30const assert = require('assert'); 31const fs = require('fs'); 32const path = require('path'); 33let async_completed = 0; 34let async_expected = 0; 35const unlink = []; 36const skipSymlinks = !common.canCreateSymLink(); 37const tmpDir = tmpdir.path; 38 39tmpdir.refresh(); 40 41let root = '/'; 42let assertEqualPath = assert.strictEqual; 43if (common.isWindows) { 44 // Something like "C:\\" 45 root = process.cwd().substr(0, 3); 46 assertEqualPath = function(path_left, path_right, message) { 47 assert 48 .strictEqual(path_left.toLowerCase(), path_right.toLowerCase(), message); 49 }; 50} 51 52process.nextTick(runTest); 53 54function tmp(p) { 55 return path.join(tmpDir, p); 56} 57 58const targetsAbsDir = path.join(tmpDir, 'targets'); 59const tmpAbsDir = tmpDir; 60 61// Set up targetsAbsDir and expected subdirectories 62fs.mkdirSync(targetsAbsDir); 63fs.mkdirSync(path.join(targetsAbsDir, 'nested-index')); 64fs.mkdirSync(path.join(targetsAbsDir, 'nested-index', 'one')); 65fs.mkdirSync(path.join(targetsAbsDir, 'nested-index', 'two')); 66 67function asynctest(testBlock, args, callback, assertBlock) { 68 async_expected++; 69 testBlock.apply(testBlock, args.concat(function(err) { 70 let ignoreError = false; 71 if (assertBlock) { 72 try { 73 ignoreError = assertBlock.apply(assertBlock, arguments); 74 } catch (e) { 75 err = e; 76 } 77 } 78 async_completed++; 79 callback(ignoreError ? null : err); 80 })); 81} 82 83// sub-tests: 84function test_simple_error_callback(realpath, realpathSync, cb) { 85 realpath('/this/path/does/not/exist', common.mustCall(function(err, s) { 86 assert(err); 87 assert(!s); 88 cb(); 89 })); 90} 91 92function test_simple_error_cb_with_null_options(realpath, realpathSync, cb) { 93 realpath('/this/path/does/not/exist', null, common.mustCall(function(err, s) { 94 assert(err); 95 assert(!s); 96 cb(); 97 })); 98} 99 100function test_simple_relative_symlink(realpath, realpathSync, callback) { 101 console.log('test_simple_relative_symlink'); 102 if (skipSymlinks) { 103 common.printSkipMessage('symlink test (no privs)'); 104 return callback(); 105 } 106 const entry = `${tmpDir}/symlink`; 107 const expected = `${tmpDir}/cycles/root.js`; 108 [ 109 [entry, `../${path.basename(tmpDir)}/cycles/root.js`], 110 ].forEach(function(t) { 111 try { fs.unlinkSync(t[0]); } catch { 112 // Continue regardless of error. 113 } 114 console.log('fs.symlinkSync(%j, %j, %j)', t[1], t[0], 'file'); 115 fs.symlinkSync(t[1], t[0], 'file'); 116 unlink.push(t[0]); 117 }); 118 const result = realpathSync(entry); 119 assertEqualPath(result, path.resolve(expected)); 120 asynctest(realpath, [entry], callback, function(err, result) { 121 assertEqualPath(result, path.resolve(expected)); 122 }); 123} 124 125function test_simple_absolute_symlink(realpath, realpathSync, callback) { 126 console.log('test_simple_absolute_symlink'); 127 128 // This one should still run, even if skipSymlinks is set, 129 // because it uses a junction. 130 const type = skipSymlinks ? 'junction' : 'dir'; 131 132 console.log('using type=%s', type); 133 134 const entry = `${tmpAbsDir}/symlink`; 135 const expected = fixtures.path('nested-index', 'one'); 136 [ 137 [entry, expected], 138 ].forEach(function(t) { 139 try { fs.unlinkSync(t[0]); } catch { 140 // Continue regardless of error. 141 } 142 console.error('fs.symlinkSync(%j, %j, %j)', t[1], t[0], type); 143 fs.symlinkSync(t[1], t[0], type); 144 unlink.push(t[0]); 145 }); 146 const result = realpathSync(entry); 147 assertEqualPath(result, path.resolve(expected)); 148 asynctest(realpath, [entry], callback, function(err, result) { 149 assertEqualPath(result, path.resolve(expected)); 150 }); 151} 152 153function test_deep_relative_file_symlink(realpath, realpathSync, callback) { 154 console.log('test_deep_relative_file_symlink'); 155 if (skipSymlinks) { 156 common.printSkipMessage('symlink test (no privs)'); 157 return callback(); 158 } 159 160 const expected = fixtures.path('cycles', 'root.js'); 161 const linkData1 = path 162 .relative(path.join(targetsAbsDir, 'nested-index', 'one'), 163 expected); 164 const linkPath1 = path.join(targetsAbsDir, 165 'nested-index', 'one', 'symlink1.js'); 166 try { fs.unlinkSync(linkPath1); } catch { 167 // Continue regardless of error. 168 } 169 fs.symlinkSync(linkData1, linkPath1, 'file'); 170 171 const linkData2 = '../one/symlink1.js'; 172 const entry = path.join(targetsAbsDir, 173 'nested-index', 'two', 'symlink1-b.js'); 174 try { fs.unlinkSync(entry); } catch { 175 // Continue regardless of error. 176 } 177 fs.symlinkSync(linkData2, entry, 'file'); 178 unlink.push(linkPath1); 179 unlink.push(entry); 180 181 assertEqualPath(realpathSync(entry), path.resolve(expected)); 182 asynctest(realpath, [entry], callback, function(err, result) { 183 assertEqualPath(result, path.resolve(expected)); 184 }); 185} 186 187function test_deep_relative_dir_symlink(realpath, realpathSync, callback) { 188 console.log('test_deep_relative_dir_symlink'); 189 if (skipSymlinks) { 190 common.printSkipMessage('symlink test (no privs)'); 191 return callback(); 192 } 193 const expected = fixtures.path('cycles', 'folder'); 194 const path1b = path.join(targetsAbsDir, 'nested-index', 'one'); 195 const linkPath1b = path.join(path1b, 'symlink1-dir'); 196 const linkData1b = path.relative(path1b, expected); 197 try { fs.unlinkSync(linkPath1b); } catch { 198 // Continue regardless of error. 199 } 200 fs.symlinkSync(linkData1b, linkPath1b, 'dir'); 201 202 const linkData2b = '../one/symlink1-dir'; 203 const entry = path.join(targetsAbsDir, 204 'nested-index', 'two', 'symlink12-dir'); 205 try { fs.unlinkSync(entry); } catch { 206 // Continue regardless of error. 207 } 208 fs.symlinkSync(linkData2b, entry, 'dir'); 209 unlink.push(linkPath1b); 210 unlink.push(entry); 211 212 assertEqualPath(realpathSync(entry), path.resolve(expected)); 213 214 asynctest(realpath, [entry], callback, function(err, result) { 215 assertEqualPath(result, path.resolve(expected)); 216 }); 217} 218 219function test_cyclic_link_protection(realpath, realpathSync, callback) { 220 console.log('test_cyclic_link_protection'); 221 if (skipSymlinks) { 222 common.printSkipMessage('symlink test (no privs)'); 223 return callback(); 224 } 225 const entry = path.join(tmpDir, '/cycles/realpath-3a'); 226 [ 227 [entry, '../cycles/realpath-3b'], 228 [path.join(tmpDir, '/cycles/realpath-3b'), '../cycles/realpath-3c'], 229 [path.join(tmpDir, '/cycles/realpath-3c'), '../cycles/realpath-3a'], 230 ].forEach(function(t) { 231 try { fs.unlinkSync(t[0]); } catch { 232 // Continue regardless of error. 233 } 234 fs.symlinkSync(t[1], t[0], 'dir'); 235 unlink.push(t[0]); 236 }); 237 assert.throws(() => { 238 realpathSync(entry); 239 }, { code: 'ELOOP', name: 'Error' }); 240 asynctest( 241 realpath, [entry], callback, common.mustCall(function(err, result) { 242 assert.strictEqual(err.path, entry); 243 assert.strictEqual(result, undefined); 244 return true; 245 })); 246} 247 248function test_cyclic_link_overprotection(realpath, realpathSync, callback) { 249 console.log('test_cyclic_link_overprotection'); 250 if (skipSymlinks) { 251 common.printSkipMessage('symlink test (no privs)'); 252 return callback(); 253 } 254 const cycles = `${tmpDir}/cycles`; 255 const expected = realpathSync(cycles); 256 const folder = `${cycles}/folder`; 257 const link = `${folder}/cycles`; 258 let testPath = cycles; 259 testPath += '/folder/cycles'.repeat(10); 260 try { fs.unlinkSync(link); } catch { 261 // Continue regardless of error. 262 } 263 fs.symlinkSync(cycles, link, 'dir'); 264 unlink.push(link); 265 assertEqualPath(realpathSync(testPath), path.resolve(expected)); 266 asynctest(realpath, [testPath], callback, function(er, res) { 267 assertEqualPath(res, path.resolve(expected)); 268 }); 269} 270 271function test_relative_input_cwd(realpath, realpathSync, callback) { 272 console.log('test_relative_input_cwd'); 273 if (skipSymlinks) { 274 common.printSkipMessage('symlink test (no privs)'); 275 return callback(); 276 } 277 278 // We need to calculate the relative path to the tmp dir from cwd 279 const entrydir = process.cwd(); 280 const entry = path.relative(entrydir, 281 path.join(`${tmpDir}/cycles/realpath-3a`)); 282 const expected = `${tmpDir}/cycles/root.js`; 283 [ 284 [entry, '../cycles/realpath-3b'], 285 [`${tmpDir}/cycles/realpath-3b`, '../cycles/realpath-3c'], 286 [`${tmpDir}/cycles/realpath-3c`, 'root.js'], 287 ].forEach(function(t) { 288 const fn = t[0]; 289 console.error('fn=%j', fn); 290 try { fs.unlinkSync(fn); } catch { 291 // Continue regardless of error. 292 } 293 const b = path.basename(t[1]); 294 const type = (b === 'root.js' ? 'file' : 'dir'); 295 console.log('fs.symlinkSync(%j, %j, %j)', t[1], fn, type); 296 fs.symlinkSync(t[1], fn, 'file'); 297 unlink.push(fn); 298 }); 299 300 const origcwd = process.cwd(); 301 process.chdir(entrydir); 302 assertEqualPath(realpathSync(entry), path.resolve(expected)); 303 asynctest(realpath, [entry], callback, function(err, result) { 304 process.chdir(origcwd); 305 assertEqualPath(result, path.resolve(expected)); 306 return true; 307 }); 308} 309 310function test_deep_symlink_mix(realpath, realpathSync, callback) { 311 console.log('test_deep_symlink_mix'); 312 if (common.isWindows) { 313 // This one is a mix of files and directories, and it's quite tricky 314 // to get the file/dir links sorted out correctly. 315 common.printSkipMessage('symlink test (no privs)'); 316 return callback(); 317 } 318 319 // /tmp/node-test-realpath-f1 -> $tmpDir/node-test-realpath-d1/foo 320 // /tmp/node-test-realpath-d1 -> $tmpDir/node-test-realpath-d2 321 // /tmp/node-test-realpath-d2/foo -> $tmpDir/node-test-realpath-f2 322 // /tmp/node-test-realpath-f2 323 // -> $tmpDir/targets/nested-index/one/realpath-c 324 // $tmpDir/targets/nested-index/one/realpath-c 325 // -> $tmpDir/targets/nested-index/two/realpath-c 326 // $tmpDir/targets/nested-index/two/realpath-c -> $tmpDir/cycles/root.js 327 // $tmpDir/targets/cycles/root.js (hard) 328 329 const entry = tmp('node-test-realpath-f1'); 330 try { fs.unlinkSync(tmp('node-test-realpath-d2/foo')); } catch { 331 // Continue regardless of error. 332 } 333 try { fs.rmdirSync(tmp('node-test-realpath-d2')); } catch { 334 // Continue regardless of error. 335 } 336 fs.mkdirSync(tmp('node-test-realpath-d2'), 0o700); 337 try { 338 [ 339 [entry, `${tmpDir}/node-test-realpath-d1/foo`], 340 [tmp('node-test-realpath-d1'), 341 `${tmpDir}/node-test-realpath-d2`], 342 [tmp('node-test-realpath-d2/foo'), '../node-test-realpath-f2'], 343 [tmp('node-test-realpath-f2'), 344 `${targetsAbsDir}/nested-index/one/realpath-c`], 345 [`${targetsAbsDir}/nested-index/one/realpath-c`, 346 `${targetsAbsDir}/nested-index/two/realpath-c`], 347 [`${targetsAbsDir}/nested-index/two/realpath-c`, 348 `${tmpDir}/cycles/root.js`], 349 ].forEach(function(t) { 350 try { fs.unlinkSync(t[0]); } catch { 351 // Continue regardless of error. 352 } 353 fs.symlinkSync(t[1], t[0]); 354 unlink.push(t[0]); 355 }); 356 } finally { 357 unlink.push(tmp('node-test-realpath-d2')); 358 } 359 const expected = `${tmpAbsDir}/cycles/root.js`; 360 assertEqualPath(realpathSync(entry), path.resolve(expected)); 361 asynctest(realpath, [entry], callback, function(err, result) { 362 assertEqualPath(result, path.resolve(expected)); 363 return true; 364 }); 365} 366 367function test_non_symlinks(realpath, realpathSync, callback) { 368 console.log('test_non_symlinks'); 369 const entrydir = path.dirname(tmpAbsDir); 370 const entry = `${tmpAbsDir.substr(entrydir.length + 1)}/cycles/root.js`; 371 const expected = `${tmpAbsDir}/cycles/root.js`; 372 const origcwd = process.cwd(); 373 process.chdir(entrydir); 374 assertEqualPath(realpathSync(entry), path.resolve(expected)); 375 asynctest(realpath, [entry], callback, function(err, result) { 376 process.chdir(origcwd); 377 assertEqualPath(result, path.resolve(expected)); 378 return true; 379 }); 380} 381 382const upone = path.join(process.cwd(), '..'); 383function test_escape_cwd(realpath, realpathSync, cb) { 384 console.log('test_escape_cwd'); 385 asynctest(realpath, ['..'], cb, function(er, uponeActual) { 386 assertEqualPath( 387 upone, uponeActual, 388 `realpath("..") expected: ${path.resolve(upone)} actual:${uponeActual}`); 389 }); 390} 391 392function test_upone_actual(realpath, realpathSync, cb) { 393 console.log('test_upone_actual'); 394 const uponeActual = realpathSync('..'); 395 assertEqualPath(upone, uponeActual); 396 cb(); 397} 398 399// Going up with .. multiple times 400// . 401// `-- a/ 402// |-- b/ 403// | `-- e -> .. 404// `-- d -> .. 405// realpath(a/b/e/d/a/b/e/d/a) ==> a 406function test_up_multiple(realpath, realpathSync, cb) { 407 console.error('test_up_multiple'); 408 if (skipSymlinks) { 409 common.printSkipMessage('symlink test (no privs)'); 410 return cb(); 411 } 412 const tmpdir = require('../common/tmpdir'); 413 tmpdir.refresh(); 414 fs.mkdirSync(tmp('a'), 0o755); 415 fs.mkdirSync(tmp('a/b'), 0o755); 416 fs.symlinkSync('..', tmp('a/d'), 'dir'); 417 unlink.push(tmp('a/d')); 418 fs.symlinkSync('..', tmp('a/b/e'), 'dir'); 419 unlink.push(tmp('a/b/e')); 420 421 const abedabed = tmp('abedabed'.split('').join('/')); 422 const abedabed_real = tmp(''); 423 424 const abedabeda = tmp('abedabeda'.split('').join('/')); 425 const abedabeda_real = tmp('a'); 426 427 assertEqualPath(realpathSync(abedabeda), abedabeda_real); 428 assertEqualPath(realpathSync(abedabed), abedabed_real); 429 430 realpath(abedabeda, function(er, real) { 431 assert.ifError(er); 432 assertEqualPath(abedabeda_real, real); 433 realpath(abedabed, function(er, real) { 434 assert.ifError(er); 435 assertEqualPath(abedabed_real, real); 436 cb(); 437 }); 438 }); 439} 440 441 442// Going up with .. multiple times with options = null 443// . 444// `-- a/ 445// |-- b/ 446// | `-- e -> .. 447// `-- d -> .. 448// realpath(a/b/e/d/a/b/e/d/a) ==> a 449function test_up_multiple_with_null_options(realpath, realpathSync, cb) { 450 console.error('test_up_multiple'); 451 if (skipSymlinks) { 452 common.printSkipMessage('symlink test (no privs)'); 453 return cb(); 454 } 455 const tmpdir = require('../common/tmpdir'); 456 tmpdir.refresh(); 457 fs.mkdirSync(tmp('a'), 0o755); 458 fs.mkdirSync(tmp('a/b'), 0o755); 459 fs.symlinkSync('..', tmp('a/d'), 'dir'); 460 unlink.push(tmp('a/d')); 461 fs.symlinkSync('..', tmp('a/b/e'), 'dir'); 462 unlink.push(tmp('a/b/e')); 463 464 const abedabed = tmp('abedabed'.split('').join('/')); 465 const abedabed_real = tmp(''); 466 467 const abedabeda = tmp('abedabeda'.split('').join('/')); 468 const abedabeda_real = tmp('a'); 469 470 assertEqualPath(realpathSync(abedabeda), abedabeda_real); 471 assertEqualPath(realpathSync(abedabed), abedabed_real); 472 473 realpath(abedabeda, null, function(er, real) { 474 assert.ifError(er); 475 assertEqualPath(abedabeda_real, real); 476 realpath(abedabed, null, function(er, real) { 477 assert.ifError(er); 478 assertEqualPath(abedabed_real, real); 479 cb(); 480 }); 481 }); 482} 483 484// Absolute symlinks with children. 485// . 486// `-- a/ 487// |-- b/ 488// | `-- c/ 489// | `-- x.txt 490// `-- link -> /tmp/node-test-realpath-abs-kids/a/b/ 491// realpath(root+'/a/link/c/x.txt') ==> root+'/a/b/c/x.txt' 492function test_abs_with_kids(realpath, realpathSync, cb) { 493 console.log('test_abs_with_kids'); 494 495 // This one should still run, even if skipSymlinks is set, 496 // because it uses a junction. 497 const type = skipSymlinks ? 'junction' : 'dir'; 498 499 console.log('using type=%s', type); 500 501 const root = `${tmpAbsDir}/node-test-realpath-abs-kids`; 502 function cleanup() { 503 ['/a/b/c/x.txt', 504 '/a/link', 505 ].forEach(function(file) { 506 try { fs.unlinkSync(root + file); } catch { 507 // Continue regardless of error. 508 } 509 }); 510 ['/a/b/c', 511 '/a/b', 512 '/a', 513 '', 514 ].forEach(function(folder) { 515 try { fs.rmdirSync(root + folder); } catch { 516 // Continue regardless of error. 517 } 518 }); 519 } 520 521 function setup() { 522 cleanup(); 523 ['', 524 '/a', 525 '/a/b', 526 '/a/b/c', 527 ].forEach(function(folder) { 528 console.log(`mkdir ${root}${folder}`); 529 fs.mkdirSync(root + folder, 0o700); 530 }); 531 fs.writeFileSync(`${root}/a/b/c/x.txt`, 'foo'); 532 fs.symlinkSync(`${root}/a/b`, `${root}/a/link`, type); 533 } 534 setup(); 535 const linkPath = `${root}/a/link/c/x.txt`; 536 const expectPath = `${root}/a/b/c/x.txt`; 537 const actual = realpathSync(linkPath); 538 // console.log({link:linkPath,expect:expectPath,actual:actual},'sync'); 539 assertEqualPath(actual, path.resolve(expectPath)); 540 asynctest(realpath, [linkPath], cb, function(er, actual) { 541 // console.log({link:linkPath,expect:expectPath,actual:actual},'async'); 542 assertEqualPath(actual, path.resolve(expectPath)); 543 cleanup(); 544 }); 545} 546 547function test_root(realpath, realpathSync, cb) { 548 assertEqualPath(root, realpathSync('/')); 549 realpath('/', function(err, result) { 550 assert.ifError(err); 551 assertEqualPath(root, result); 552 cb(); 553 }); 554} 555 556function test_root_with_null_options(realpath, realpathSync, cb) { 557 realpath('/', null, function(err, result) { 558 assert.ifError(err); 559 assertEqualPath(root, result); 560 cb(); 561 }); 562} 563 564// ---------------------------------------------------------------------------- 565 566const tests = [ 567 test_simple_error_callback, 568 test_simple_error_cb_with_null_options, 569 test_simple_relative_symlink, 570 test_simple_absolute_symlink, 571 test_deep_relative_file_symlink, 572 test_deep_relative_dir_symlink, 573 test_cyclic_link_protection, 574 test_cyclic_link_overprotection, 575 test_relative_input_cwd, 576 test_deep_symlink_mix, 577 test_non_symlinks, 578 test_escape_cwd, 579 test_upone_actual, 580 test_abs_with_kids, 581 test_up_multiple, 582 test_up_multiple_with_null_options, 583 test_root, 584 test_root_with_null_options, 585]; 586const numtests = tests.length; 587let testsRun = 0; 588function runNextTest(err) { 589 assert.ifError(err); 590 const test = tests.shift(); 591 if (!test) { 592 return console.log(`${numtests} subtests completed OK for fs.realpath`); 593 } 594 testsRun++; 595 test(fs.realpath, fs.realpathSync, common.mustSucceed(() => { 596 testsRun++; 597 test(fs.realpath.native, 598 fs.realpathSync.native, 599 common.mustCall(runNextTest)); 600 })); 601} 602 603function runTest() { 604 const tmpDirs = ['cycles', 'cycles/folder']; 605 tmpDirs.forEach(function(t) { 606 t = tmp(t); 607 fs.mkdirSync(t, 0o700); 608 }); 609 fs.writeFileSync(tmp('cycles/root.js'), "console.error('roooot!');"); 610 console.error('start tests'); 611 runNextTest(); 612} 613 614 615process.on('exit', function() { 616 assert.strictEqual(2 * numtests, testsRun); 617 assert.strictEqual(async_completed, async_expected); 618}); 619