• 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  /*
302  /tmp/node-test-realpath-f1 -> $tmpDir/node-test-realpath-d1/foo
303  /tmp/node-test-realpath-d1 -> $tmpDir/node-test-realpath-d2
304  /tmp/node-test-realpath-d2/foo -> $tmpDir/node-test-realpath-f2
305  /tmp/node-test-realpath-f2
306    -> $tmpDir/targets/nested-index/one/realpath-c
307  $tmpDir/targets/nested-index/one/realpath-c
308    -> $tmpDir/targets/nested-index/two/realpath-c
309  $tmpDir/targets/nested-index/two/realpath-c -> $tmpDir/cycles/root.js
310  $tmpDir/targets/cycles/root.js (hard)
311  */
312  const entry = tmp('node-test-realpath-f1');
313  try { fs.unlinkSync(tmp('node-test-realpath-d2/foo')); } catch {}
314  try { fs.rmdirSync(tmp('node-test-realpath-d2')); } catch {}
315  fs.mkdirSync(tmp('node-test-realpath-d2'), 0o700);
316  try {
317    [
318      [entry, `${tmpDir}/node-test-realpath-d1/foo`],
319      [tmp('node-test-realpath-d1'),
320       `${tmpDir}/node-test-realpath-d2`],
321      [tmp('node-test-realpath-d2/foo'), '../node-test-realpath-f2'],
322      [tmp('node-test-realpath-f2'),
323       `${targetsAbsDir}/nested-index/one/realpath-c`],
324      [`${targetsAbsDir}/nested-index/one/realpath-c`,
325       `${targetsAbsDir}/nested-index/two/realpath-c`],
326      [`${targetsAbsDir}/nested-index/two/realpath-c`,
327       `${tmpDir}/cycles/root.js`]
328    ].forEach(function(t) {
329      try { fs.unlinkSync(t[0]); } catch {}
330      fs.symlinkSync(t[1], t[0]);
331      unlink.push(t[0]);
332    });
333  } finally {
334    unlink.push(tmp('node-test-realpath-d2'));
335  }
336  const expected = `${tmpAbsDir}/cycles/root.js`;
337  assertEqualPath(realpathSync(entry), path.resolve(expected));
338  asynctest(realpath, [entry], callback, function(err, result) {
339    assertEqualPath(result, path.resolve(expected));
340    return true;
341  });
342}
343
344function test_non_symlinks(realpath, realpathSync, callback) {
345  console.log('test_non_symlinks');
346  const entrydir = path.dirname(tmpAbsDir);
347  const entry = `${tmpAbsDir.substr(entrydir.length + 1)}/cycles/root.js`;
348  const expected = `${tmpAbsDir}/cycles/root.js`;
349  const origcwd = process.cwd();
350  process.chdir(entrydir);
351  assertEqualPath(realpathSync(entry), path.resolve(expected));
352  asynctest(realpath, [entry], callback, function(err, result) {
353    process.chdir(origcwd);
354    assertEqualPath(result, path.resolve(expected));
355    return true;
356  });
357}
358
359const upone = path.join(process.cwd(), '..');
360function test_escape_cwd(realpath, realpathSync, cb) {
361  console.log('test_escape_cwd');
362  asynctest(realpath, ['..'], cb, function(er, uponeActual) {
363    assertEqualPath(
364      upone, uponeActual,
365      `realpath("..") expected: ${path.resolve(upone)} actual:${uponeActual}`);
366  });
367}
368
369function test_upone_actual(realpath, realpathSync, cb) {
370  console.log('test_upone_actual');
371  const uponeActual = realpathSync('..');
372  assertEqualPath(upone, uponeActual);
373  cb();
374}
375
376// Going up with .. multiple times
377// .
378// `-- a/
379//     |-- b/
380//     |   `-- e -> ..
381//     `-- d -> ..
382// realpath(a/b/e/d/a/b/e/d/a) ==> a
383function test_up_multiple(realpath, realpathSync, cb) {
384  console.error('test_up_multiple');
385  if (skipSymlinks) {
386    common.printSkipMessage('symlink test (no privs)');
387    return cb();
388  }
389  const tmpdir = require('../common/tmpdir');
390  tmpdir.refresh();
391  fs.mkdirSync(tmp('a'), 0o755);
392  fs.mkdirSync(tmp('a/b'), 0o755);
393  fs.symlinkSync('..', tmp('a/d'), 'dir');
394  unlink.push(tmp('a/d'));
395  fs.symlinkSync('..', tmp('a/b/e'), 'dir');
396  unlink.push(tmp('a/b/e'));
397
398  const abedabed = tmp('abedabed'.split('').join('/'));
399  const abedabed_real = tmp('');
400
401  const abedabeda = tmp('abedabeda'.split('').join('/'));
402  const abedabeda_real = tmp('a');
403
404  assertEqualPath(realpathSync(abedabeda), abedabeda_real);
405  assertEqualPath(realpathSync(abedabed), abedabed_real);
406
407  realpath(abedabeda, function(er, real) {
408    assert.ifError(er);
409    assertEqualPath(abedabeda_real, real);
410    realpath(abedabed, function(er, real) {
411      assert.ifError(er);
412      assertEqualPath(abedabed_real, real);
413      cb();
414    });
415  });
416}
417
418
419// Going up with .. multiple times with options = null
420// .
421// `-- a/
422//     |-- b/
423//     |   `-- e -> ..
424//     `-- d -> ..
425// realpath(a/b/e/d/a/b/e/d/a) ==> a
426function test_up_multiple_with_null_options(realpath, realpathSync, cb) {
427  console.error('test_up_multiple');
428  if (skipSymlinks) {
429    common.printSkipMessage('symlink test (no privs)');
430    return cb();
431  }
432  const tmpdir = require('../common/tmpdir');
433  tmpdir.refresh();
434  fs.mkdirSync(tmp('a'), 0o755);
435  fs.mkdirSync(tmp('a/b'), 0o755);
436  fs.symlinkSync('..', tmp('a/d'), 'dir');
437  unlink.push(tmp('a/d'));
438  fs.symlinkSync('..', tmp('a/b/e'), 'dir');
439  unlink.push(tmp('a/b/e'));
440
441  const abedabed = tmp('abedabed'.split('').join('/'));
442  const abedabed_real = tmp('');
443
444  const abedabeda = tmp('abedabeda'.split('').join('/'));
445  const abedabeda_real = tmp('a');
446
447  assertEqualPath(realpathSync(abedabeda), abedabeda_real);
448  assertEqualPath(realpathSync(abedabed), abedabed_real);
449
450  realpath(abedabeda, null, function(er, real) {
451    assert.ifError(er);
452    assertEqualPath(abedabeda_real, real);
453    realpath(abedabed, null, function(er, real) {
454      assert.ifError(er);
455      assertEqualPath(abedabed_real, real);
456      cb();
457    });
458  });
459}
460
461// Absolute symlinks with children.
462// .
463// `-- a/
464//     |-- b/
465//     |   `-- c/
466//     |       `-- x.txt
467//     `-- link -> /tmp/node-test-realpath-abs-kids/a/b/
468// realpath(root+'/a/link/c/x.txt') ==> root+'/a/b/c/x.txt'
469function test_abs_with_kids(realpath, realpathSync, cb) {
470  console.log('test_abs_with_kids');
471
472  // This one should still run, even if skipSymlinks is set,
473  // because it uses a junction.
474  const type = skipSymlinks ? 'junction' : 'dir';
475
476  console.log('using type=%s', type);
477
478  const root = `${tmpAbsDir}/node-test-realpath-abs-kids`;
479  function cleanup() {
480    ['/a/b/c/x.txt',
481     '/a/link'
482    ].forEach(function(file) {
483      try { fs.unlinkSync(root + file); } catch {}
484    });
485    ['/a/b/c',
486     '/a/b',
487     '/a',
488     ''
489    ].forEach(function(folder) {
490      try { fs.rmdirSync(root + folder); } catch {}
491    });
492  }
493
494  function setup() {
495    cleanup();
496    ['',
497     '/a',
498     '/a/b',
499     '/a/b/c'
500    ].forEach(function(folder) {
501      console.log(`mkdir ${root}${folder}`);
502      fs.mkdirSync(root + folder, 0o700);
503    });
504    fs.writeFileSync(`${root}/a/b/c/x.txt`, 'foo');
505    fs.symlinkSync(`${root}/a/b`, `${root}/a/link`, type);
506  }
507  setup();
508  const linkPath = `${root}/a/link/c/x.txt`;
509  const expectPath = `${root}/a/b/c/x.txt`;
510  const actual = realpathSync(linkPath);
511  // console.log({link:linkPath,expect:expectPath,actual:actual},'sync');
512  assertEqualPath(actual, path.resolve(expectPath));
513  asynctest(realpath, [linkPath], cb, function(er, actual) {
514    // console.log({link:linkPath,expect:expectPath,actual:actual},'async');
515    assertEqualPath(actual, path.resolve(expectPath));
516    cleanup();
517  });
518}
519
520function test_root(realpath, realpathSync, cb) {
521  assertEqualPath(root, realpathSync('/'));
522  realpath('/', function(err, result) {
523    assert.ifError(err);
524    assertEqualPath(root, result);
525    cb();
526  });
527}
528
529function test_root_with_null_options(realpath, realpathSync, cb) {
530  realpath('/', null, function(err, result) {
531    assert.ifError(err);
532    assertEqualPath(root, result);
533    cb();
534  });
535}
536
537// ----------------------------------------------------------------------------
538
539const tests = [
540  test_simple_error_callback,
541  test_simple_error_cb_with_null_options,
542  test_simple_relative_symlink,
543  test_simple_absolute_symlink,
544  test_deep_relative_file_symlink,
545  test_deep_relative_dir_symlink,
546  test_cyclic_link_protection,
547  test_cyclic_link_overprotection,
548  test_relative_input_cwd,
549  test_deep_symlink_mix,
550  test_non_symlinks,
551  test_escape_cwd,
552  test_upone_actual,
553  test_abs_with_kids,
554  test_up_multiple,
555  test_up_multiple_with_null_options,
556  test_root,
557  test_root_with_null_options
558];
559const numtests = tests.length;
560let testsRun = 0;
561function runNextTest(err) {
562  assert.ifError(err);
563  const test = tests.shift();
564  if (!test) {
565    return console.log(`${numtests} subtests completed OK for fs.realpath`);
566  }
567  testsRun++;
568  test(fs.realpath, fs.realpathSync, common.mustCall((err) => {
569    assert.ifError(err);
570    testsRun++;
571    test(fs.realpath.native,
572         fs.realpathSync.native,
573         common.mustCall(runNextTest));
574  }));
575}
576
577function runTest() {
578  const tmpDirs = ['cycles', 'cycles/folder'];
579  tmpDirs.forEach(function(t) {
580    t = tmp(t);
581    fs.mkdirSync(t, 0o700);
582  });
583  fs.writeFileSync(tmp('cycles/root.js'), "console.error('roooot!');");
584  console.error('start tests');
585  runNextTest();
586}
587
588
589process.on('exit', function() {
590  assert.strictEqual(2 * numtests, testsRun);
591  assert.strictEqual(async_completed, async_expected);
592});
593