• 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    console.log('fs.symlinkSync(%j, %j, %j)', t[1], t[0], 'file');
113    fs.symlinkSync(t[1], t[0], 'file');
114    unlink.push(t[0]);
115  });
116  const result = realpathSync(entry);
117  assertEqualPath(result, path.resolve(expected));
118  asynctest(realpath, [entry], callback, function(err, result) {
119    assertEqualPath(result, path.resolve(expected));
120  });
121}
122
123function test_simple_absolute_symlink(realpath, realpathSync, callback) {
124  console.log('test_simple_absolute_symlink');
125
126  // This one should still run, even if skipSymlinks is set,
127  // because it uses a junction.
128  const type = skipSymlinks ? 'junction' : 'dir';
129
130  console.log('using type=%s', type);
131
132  const entry = `${tmpAbsDir}/symlink`;
133  const expected = fixtures.path('nested-index', 'one');
134  [
135    [entry, expected],
136  ].forEach(function(t) {
137    try { fs.unlinkSync(t[0]); } catch {}
138    console.error('fs.symlinkSync(%j, %j, %j)', t[1], t[0], type);
139    fs.symlinkSync(t[1], t[0], type);
140    unlink.push(t[0]);
141  });
142  const result = realpathSync(entry);
143  assertEqualPath(result, path.resolve(expected));
144  asynctest(realpath, [entry], callback, function(err, result) {
145    assertEqualPath(result, path.resolve(expected));
146  });
147}
148
149function test_deep_relative_file_symlink(realpath, realpathSync, callback) {
150  console.log('test_deep_relative_file_symlink');
151  if (skipSymlinks) {
152    common.printSkipMessage('symlink test (no privs)');
153    return callback();
154  }
155
156  const expected = fixtures.path('cycles', 'root.js');
157  const linkData1 = path
158                      .relative(path.join(targetsAbsDir, 'nested-index', 'one'),
159                                expected);
160  const linkPath1 = path.join(targetsAbsDir,
161                              'nested-index', 'one', 'symlink1.js');
162  try { fs.unlinkSync(linkPath1); } catch {}
163  fs.symlinkSync(linkData1, linkPath1, 'file');
164
165  const linkData2 = '../one/symlink1.js';
166  const entry = path.join(targetsAbsDir,
167                          'nested-index', 'two', 'symlink1-b.js');
168  try { fs.unlinkSync(entry); } catch {}
169  fs.symlinkSync(linkData2, entry, 'file');
170  unlink.push(linkPath1);
171  unlink.push(entry);
172
173  assertEqualPath(realpathSync(entry), path.resolve(expected));
174  asynctest(realpath, [entry], callback, function(err, result) {
175    assertEqualPath(result, path.resolve(expected));
176  });
177}
178
179function test_deep_relative_dir_symlink(realpath, realpathSync, callback) {
180  console.log('test_deep_relative_dir_symlink');
181  if (skipSymlinks) {
182    common.printSkipMessage('symlink test (no privs)');
183    return callback();
184  }
185  const expected = fixtures.path('cycles', 'folder');
186  const path1b = path.join(targetsAbsDir, 'nested-index', 'one');
187  const linkPath1b = path.join(path1b, 'symlink1-dir');
188  const linkData1b = path.relative(path1b, expected);
189  try { fs.unlinkSync(linkPath1b); } catch {}
190  fs.symlinkSync(linkData1b, linkPath1b, 'dir');
191
192  const linkData2b = '../one/symlink1-dir';
193  const entry = path.join(targetsAbsDir,
194                          'nested-index', 'two', 'symlink12-dir');
195  try { fs.unlinkSync(entry); } catch {}
196  fs.symlinkSync(linkData2b, entry, 'dir');
197  unlink.push(linkPath1b);
198  unlink.push(entry);
199
200  assertEqualPath(realpathSync(entry), path.resolve(expected));
201
202  asynctest(realpath, [entry], callback, function(err, result) {
203    assertEqualPath(result, path.resolve(expected));
204  });
205}
206
207function test_cyclic_link_protection(realpath, realpathSync, callback) {
208  console.log('test_cyclic_link_protection');
209  if (skipSymlinks) {
210    common.printSkipMessage('symlink test (no privs)');
211    return callback();
212  }
213  const entry = path.join(tmpDir, '/cycles/realpath-3a');
214  [
215    [entry, '../cycles/realpath-3b'],
216    [path.join(tmpDir, '/cycles/realpath-3b'), '../cycles/realpath-3c'],
217    [path.join(tmpDir, '/cycles/realpath-3c'), '../cycles/realpath-3a'],
218  ].forEach(function(t) {
219    try { fs.unlinkSync(t[0]); } catch {}
220    fs.symlinkSync(t[1], t[0], 'dir');
221    unlink.push(t[0]);
222  });
223  assert.throws(() => {
224    realpathSync(entry);
225  }, { code: 'ELOOP', name: 'Error' });
226  asynctest(
227    realpath, [entry], callback, common.mustCall(function(err, result) {
228      assert.strictEqual(err.path, entry);
229      assert.strictEqual(result, undefined);
230      return true;
231    }));
232}
233
234function test_cyclic_link_overprotection(realpath, realpathSync, callback) {
235  console.log('test_cyclic_link_overprotection');
236  if (skipSymlinks) {
237    common.printSkipMessage('symlink test (no privs)');
238    return callback();
239  }
240  const cycles = `${tmpDir}/cycles`;
241  const expected = realpathSync(cycles);
242  const folder = `${cycles}/folder`;
243  const link = `${folder}/cycles`;
244  let testPath = cycles;
245  testPath += '/folder/cycles'.repeat(10);
246  try { fs.unlinkSync(link); } catch {}
247  fs.symlinkSync(cycles, link, 'dir');
248  unlink.push(link);
249  assertEqualPath(realpathSync(testPath), path.resolve(expected));
250  asynctest(realpath, [testPath], callback, function(er, res) {
251    assertEqualPath(res, path.resolve(expected));
252  });
253}
254
255function test_relative_input_cwd(realpath, realpathSync, callback) {
256  console.log('test_relative_input_cwd');
257  if (skipSymlinks) {
258    common.printSkipMessage('symlink test (no privs)');
259    return callback();
260  }
261
262  // We need to calculate the relative path to the tmp dir from cwd
263  const entrydir = process.cwd();
264  const entry = path.relative(entrydir,
265                              path.join(`${tmpDir}/cycles/realpath-3a`));
266  const expected = `${tmpDir}/cycles/root.js`;
267  [
268    [entry, '../cycles/realpath-3b'],
269    [`${tmpDir}/cycles/realpath-3b`, '../cycles/realpath-3c'],
270    [`${tmpDir}/cycles/realpath-3c`, 'root.js'],
271  ].forEach(function(t) {
272    const fn = t[0];
273    console.error('fn=%j', fn);
274    try { fs.unlinkSync(fn); } catch {}
275    const b = path.basename(t[1]);
276    const type = (b === 'root.js' ? 'file' : 'dir');
277    console.log('fs.symlinkSync(%j, %j, %j)', t[1], fn, type);
278    fs.symlinkSync(t[1], fn, 'file');
279    unlink.push(fn);
280  });
281
282  const origcwd = process.cwd();
283  process.chdir(entrydir);
284  assertEqualPath(realpathSync(entry), path.resolve(expected));
285  asynctest(realpath, [entry], callback, function(err, result) {
286    process.chdir(origcwd);
287    assertEqualPath(result, path.resolve(expected));
288    return true;
289  });
290}
291
292function test_deep_symlink_mix(realpath, realpathSync, callback) {
293  console.log('test_deep_symlink_mix');
294  if (common.isWindows) {
295    // This one is a mix of files and directories, and it's quite tricky
296    // to get the file/dir links sorted out correctly.
297    common.printSkipMessage('symlink test (no privs)');
298    return callback();
299  }
300
301  // /tmp/node-test-realpath-f1 -> $tmpDir/node-test-realpath-d1/foo
302  // /tmp/node-test-realpath-d1 -> $tmpDir/node-test-realpath-d2
303  // /tmp/node-test-realpath-d2/foo -> $tmpDir/node-test-realpath-f2
304  // /tmp/node-test-realpath-f2
305  //   -> $tmpDir/targets/nested-index/one/realpath-c
306  // $tmpDir/targets/nested-index/one/realpath-c
307  //   -> $tmpDir/targets/nested-index/two/realpath-c
308  // $tmpDir/targets/nested-index/two/realpath-c -> $tmpDir/cycles/root.js
309  // $tmpDir/targets/cycles/root.js (hard)
310
311  const entry = tmp('node-test-realpath-f1');
312  try { fs.unlinkSync(tmp('node-test-realpath-d2/foo')); } catch {}
313  try { fs.rmdirSync(tmp('node-test-realpath-d2')); } catch {}
314  fs.mkdirSync(tmp('node-test-realpath-d2'), 0o700);
315  try {
316    [
317      [entry, `${tmpDir}/node-test-realpath-d1/foo`],
318      [tmp('node-test-realpath-d1'),
319       `${tmpDir}/node-test-realpath-d2`],
320      [tmp('node-test-realpath-d2/foo'), '../node-test-realpath-f2'],
321      [tmp('node-test-realpath-f2'),
322       `${targetsAbsDir}/nested-index/one/realpath-c`],
323      [`${targetsAbsDir}/nested-index/one/realpath-c`,
324       `${targetsAbsDir}/nested-index/two/realpath-c`],
325      [`${targetsAbsDir}/nested-index/two/realpath-c`,
326       `${tmpDir}/cycles/root.js`],
327    ].forEach(function(t) {
328      try { fs.unlinkSync(t[0]); } catch {}
329      fs.symlinkSync(t[1], t[0]);
330      unlink.push(t[0]);
331    });
332  } finally {
333    unlink.push(tmp('node-test-realpath-d2'));
334  }
335  const expected = `${tmpAbsDir}/cycles/root.js`;
336  assertEqualPath(realpathSync(entry), path.resolve(expected));
337  asynctest(realpath, [entry], callback, function(err, result) {
338    assertEqualPath(result, path.resolve(expected));
339    return true;
340  });
341}
342
343function test_non_symlinks(realpath, realpathSync, callback) {
344  console.log('test_non_symlinks');
345  const entrydir = path.dirname(tmpAbsDir);
346  const entry = `${tmpAbsDir.substr(entrydir.length + 1)}/cycles/root.js`;
347  const expected = `${tmpAbsDir}/cycles/root.js`;
348  const origcwd = process.cwd();
349  process.chdir(entrydir);
350  assertEqualPath(realpathSync(entry), path.resolve(expected));
351  asynctest(realpath, [entry], callback, function(err, result) {
352    process.chdir(origcwd);
353    assertEqualPath(result, path.resolve(expected));
354    return true;
355  });
356}
357
358const upone = path.join(process.cwd(), '..');
359function test_escape_cwd(realpath, realpathSync, cb) {
360  console.log('test_escape_cwd');
361  asynctest(realpath, ['..'], cb, function(er, uponeActual) {
362    assertEqualPath(
363      upone, uponeActual,
364      `realpath("..") expected: ${path.resolve(upone)} actual:${uponeActual}`);
365  });
366}
367
368function test_upone_actual(realpath, realpathSync, cb) {
369  console.log('test_upone_actual');
370  const uponeActual = realpathSync('..');
371  assertEqualPath(upone, uponeActual);
372  cb();
373}
374
375// Going up with .. multiple times
376// .
377// `-- a/
378//     |-- b/
379//     |   `-- e -> ..
380//     `-- d -> ..
381// realpath(a/b/e/d/a/b/e/d/a) ==> a
382function test_up_multiple(realpath, realpathSync, cb) {
383  console.error('test_up_multiple');
384  if (skipSymlinks) {
385    common.printSkipMessage('symlink test (no privs)');
386    return cb();
387  }
388  const tmpdir = require('../common/tmpdir');
389  tmpdir.refresh();
390  fs.mkdirSync(tmp('a'), 0o755);
391  fs.mkdirSync(tmp('a/b'), 0o755);
392  fs.symlinkSync('..', tmp('a/d'), 'dir');
393  unlink.push(tmp('a/d'));
394  fs.symlinkSync('..', tmp('a/b/e'), 'dir');
395  unlink.push(tmp('a/b/e'));
396
397  const abedabed = tmp('abedabed'.split('').join('/'));
398  const abedabed_real = tmp('');
399
400  const abedabeda = tmp('abedabeda'.split('').join('/'));
401  const abedabeda_real = tmp('a');
402
403  assertEqualPath(realpathSync(abedabeda), abedabeda_real);
404  assertEqualPath(realpathSync(abedabed), abedabed_real);
405
406  realpath(abedabeda, function(er, real) {
407    assert.ifError(er);
408    assertEqualPath(abedabeda_real, real);
409    realpath(abedabed, function(er, real) {
410      assert.ifError(er);
411      assertEqualPath(abedabed_real, real);
412      cb();
413    });
414  });
415}
416
417
418// Going up with .. multiple times with options = null
419// .
420// `-- a/
421//     |-- b/
422//     |   `-- e -> ..
423//     `-- d -> ..
424// realpath(a/b/e/d/a/b/e/d/a) ==> a
425function test_up_multiple_with_null_options(realpath, realpathSync, cb) {
426  console.error('test_up_multiple');
427  if (skipSymlinks) {
428    common.printSkipMessage('symlink test (no privs)');
429    return cb();
430  }
431  const tmpdir = require('../common/tmpdir');
432  tmpdir.refresh();
433  fs.mkdirSync(tmp('a'), 0o755);
434  fs.mkdirSync(tmp('a/b'), 0o755);
435  fs.symlinkSync('..', tmp('a/d'), 'dir');
436  unlink.push(tmp('a/d'));
437  fs.symlinkSync('..', tmp('a/b/e'), 'dir');
438  unlink.push(tmp('a/b/e'));
439
440  const abedabed = tmp('abedabed'.split('').join('/'));
441  const abedabed_real = tmp('');
442
443  const abedabeda = tmp('abedabeda'.split('').join('/'));
444  const abedabeda_real = tmp('a');
445
446  assertEqualPath(realpathSync(abedabeda), abedabeda_real);
447  assertEqualPath(realpathSync(abedabed), abedabed_real);
448
449  realpath(abedabeda, null, function(er, real) {
450    assert.ifError(er);
451    assertEqualPath(abedabeda_real, real);
452    realpath(abedabed, null, function(er, real) {
453      assert.ifError(er);
454      assertEqualPath(abedabed_real, real);
455      cb();
456    });
457  });
458}
459
460// Absolute symlinks with children.
461// .
462// `-- a/
463//     |-- b/
464//     |   `-- c/
465//     |       `-- x.txt
466//     `-- link -> /tmp/node-test-realpath-abs-kids/a/b/
467// realpath(root+'/a/link/c/x.txt') ==> root+'/a/b/c/x.txt'
468function test_abs_with_kids(realpath, realpathSync, cb) {
469  console.log('test_abs_with_kids');
470
471  // This one should still run, even if skipSymlinks is set,
472  // because it uses a junction.
473  const type = skipSymlinks ? 'junction' : 'dir';
474
475  console.log('using type=%s', type);
476
477  const root = `${tmpAbsDir}/node-test-realpath-abs-kids`;
478  function cleanup() {
479    ['/a/b/c/x.txt',
480     '/a/link',
481    ].forEach(function(file) {
482      try { fs.unlinkSync(root + file); } catch {}
483    });
484    ['/a/b/c',
485     '/a/b',
486     '/a',
487     '',
488    ].forEach(function(folder) {
489      try { fs.rmdirSync(root + folder); } catch {}
490    });
491  }
492
493  function setup() {
494    cleanup();
495    ['',
496     '/a',
497     '/a/b',
498     '/a/b/c',
499    ].forEach(function(folder) {
500      console.log(`mkdir ${root}${folder}`);
501      fs.mkdirSync(root + folder, 0o700);
502    });
503    fs.writeFileSync(`${root}/a/b/c/x.txt`, 'foo');
504    fs.symlinkSync(`${root}/a/b`, `${root}/a/link`, type);
505  }
506  setup();
507  const linkPath = `${root}/a/link/c/x.txt`;
508  const expectPath = `${root}/a/b/c/x.txt`;
509  const actual = realpathSync(linkPath);
510  // console.log({link:linkPath,expect:expectPath,actual:actual},'sync');
511  assertEqualPath(actual, path.resolve(expectPath));
512  asynctest(realpath, [linkPath], cb, function(er, actual) {
513    // console.log({link:linkPath,expect:expectPath,actual:actual},'async');
514    assertEqualPath(actual, path.resolve(expectPath));
515    cleanup();
516  });
517}
518
519function test_root(realpath, realpathSync, cb) {
520  assertEqualPath(root, realpathSync('/'));
521  realpath('/', function(err, result) {
522    assert.ifError(err);
523    assertEqualPath(root, result);
524    cb();
525  });
526}
527
528function test_root_with_null_options(realpath, realpathSync, cb) {
529  realpath('/', null, function(err, result) {
530    assert.ifError(err);
531    assertEqualPath(root, result);
532    cb();
533  });
534}
535
536// ----------------------------------------------------------------------------
537
538const tests = [
539  test_simple_error_callback,
540  test_simple_error_cb_with_null_options,
541  test_simple_relative_symlink,
542  test_simple_absolute_symlink,
543  test_deep_relative_file_symlink,
544  test_deep_relative_dir_symlink,
545  test_cyclic_link_protection,
546  test_cyclic_link_overprotection,
547  test_relative_input_cwd,
548  test_deep_symlink_mix,
549  test_non_symlinks,
550  test_escape_cwd,
551  test_upone_actual,
552  test_abs_with_kids,
553  test_up_multiple,
554  test_up_multiple_with_null_options,
555  test_root,
556  test_root_with_null_options,
557];
558const numtests = tests.length;
559let testsRun = 0;
560function runNextTest(err) {
561  assert.ifError(err);
562  const test = tests.shift();
563  if (!test) {
564    return console.log(`${numtests} subtests completed OK for fs.realpath`);
565  }
566  testsRun++;
567  test(fs.realpath, fs.realpathSync, common.mustSucceed(() => {
568    testsRun++;
569    test(fs.realpath.native,
570         fs.realpathSync.native,
571         common.mustCall(runNextTest));
572  }));
573}
574
575function runTest() {
576  const tmpDirs = ['cycles', 'cycles/folder'];
577  tmpDirs.forEach(function(t) {
578    t = tmp(t);
579    fs.mkdirSync(t, 0o700);
580  });
581  fs.writeFileSync(tmp('cycles/root.js'), "console.error('roooot!');");
582  console.error('start tests');
583  runNextTest();
584}
585
586
587process.on('exit', function() {
588  assert.strictEqual(2 * numtests, testsRun);
589  assert.strictEqual(async_completed, async_expected);
590});
591