• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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