From 8611e3b93b1978a964c90429652f416fdad9e831 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Thu, 5 Oct 2017 13:10:03 +0200 Subject: [PATCH] fs: expose realpath(3) bindings Make the `uv_fs_realpath()` binding (which calls the libc `realpath()` on UNIX and `GetFinalPathNameByHandle()` on Windows) available as the `fs.realpath.native()` and `fs.realpathSync.native()` functions. The binding was already available as `process.binding('fs').realpath` but was not exposed or tested - and partly broken as a result. Fixes: https://github.com/nodejs/node/issues/8715 PR-URL: https://github.com/nodejs/node/pull/15776 Refs: https://github.com/nodejs/node/pull/7899 Reviewed-By: Anna Henningsen Reviewed-By: Colin Ihrig Reviewed-By: James M Snell Reviewed-By: Jeremiah Senkpiel Reviewed-By: Minwoo Jung Reviewed-By: Trevor Norris --- lib/fs.js | 20 +++ src/node_file.cc | 15 +-- test/parallel/test-fs-realpath-native.js | 12 ++ test/parallel/test-fs-realpath.js | 150 +++++++++++------------ 4 files changed, 110 insertions(+), 87 deletions(-) create mode 100644 test/parallel/test-fs-realpath-native.js diff --git a/lib/fs.js b/lib/fs.js index e1407ba4f1ed9b..ce76ebc4d64195 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -1722,6 +1722,14 @@ fs.realpathSync = function realpathSync(p, options) { }; +fs.realpathSync.native = function(path, options) { + options = getOptions(options, {}); + handleError((path = getPathFromURL(path))); + nullCheck(path); + return binding.realpath(path, options.encoding); +}; + + fs.realpath = function realpath(p, options, callback) { callback = maybeCallback(typeof options === 'function' ? options : callback); if (!options) @@ -1858,6 +1866,18 @@ fs.realpath = function realpath(p, options, callback) { } }; + +fs.realpath.native = function(path, options, callback) { + callback = maybeCallback(callback || options); + options = getOptions(options, {}); + if (handleError((path = getPathFromURL(path)), callback)) return; + if (!nullCheck(path, callback)) return; + const req = new FSReqWrap(); + req.oncomplete = callback; + return binding.realpath(path, options.encoding, req); +}; + + fs.mkdtemp = function(prefix, options, callback) { callback = makeCallback(typeof options === 'function' ? options : callback); options = getOptions(options, {}); diff --git a/src/node_file.cc b/src/node_file.cc index c657ece93f30ed..5d06960953a013 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -827,24 +827,15 @@ static void MKDir(const FunctionCallbackInfo& args) { } static void RealPath(const FunctionCallbackInfo& args) { + CHECK_GE(args.Length(), 2); Environment* env = Environment::GetCurrent(args); - - const int argc = args.Length(); - - if (argc < 1) - return TYPE_ERROR("path required"); - BufferValue path(env->isolate(), args[0]); ASSERT_PATH(path) const enum encoding encoding = ParseEncoding(env->isolate(), args[1], UTF8); - Local callback = Null(env->isolate()); - if (argc == 3) - callback = args[2]; - - if (callback->IsObject()) { - ASYNC_CALL(realpath, callback, encoding, *path); + if (args[2]->IsObject()) { + ASYNC_CALL(realpath, args[2], encoding, *path); } else { SYNC_CALL(realpath, *path, *path); const char* link_path = static_cast(SYNC_REQ.ptr); diff --git a/test/parallel/test-fs-realpath-native.js b/test/parallel/test-fs-realpath-native.js new file mode 100644 index 00000000000000..43d6b8ca80734b --- /dev/null +++ b/test/parallel/test-fs-realpath-native.js @@ -0,0 +1,12 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); + +if (!common.isOSX) common.skip('MacOS-only test.'); + +assert.strictEqual(fs.realpathSync.native('/users'), '/Users'); +fs.realpath.native('/users', common.mustCall((err, res) => { + assert.ifError(err); + assert.strictEqual(res, '/Users'); +})); diff --git a/test/parallel/test-fs-realpath.js b/test/parallel/test-fs-realpath.js index 3af0092f617c0a..013e8015147d2f 100644 --- a/test/parallel/test-fs-realpath.js +++ b/test/parallel/test-fs-realpath.js @@ -94,19 +94,19 @@ function asynctest(testBlock, args, callback, assertBlock) { } // sub-tests: -function test_simple_error_callback(cb) { - fs.realpath('/this/path/does/not/exist', common.mustCall(function(err, s) { +function test_simple_error_callback(realpath, realpathSync, cb) { + realpath('/this/path/does/not/exist', common.mustCall(function(err, s) { assert(err); assert(!s); cb(); })); } -function test_simple_relative_symlink(callback) { +function test_simple_relative_symlink(realpath, realpathSync, callback) { console.log('test_simple_relative_symlink'); if (skipSymlinks) { common.printSkipMessage('symlink test (no privs)'); - return runNextTest(); + return callback(); } const entry = `${tmpDir}/symlink`; const expected = `${tmpDir}/cycles/root.js`; @@ -118,14 +118,14 @@ function test_simple_relative_symlink(callback) { fs.symlinkSync(t[1], t[0], 'file'); unlink.push(t[0]); }); - const result = fs.realpathSync(entry); + const result = realpathSync(entry); assertEqualPath(result, path.resolve(expected)); - asynctest(fs.realpath, [entry], callback, function(err, result) { + asynctest(realpath, [entry], callback, function(err, result) { assertEqualPath(result, path.resolve(expected)); }); } -function test_simple_absolute_symlink(callback) { +function test_simple_absolute_symlink(realpath, realpathSync, callback) { console.log('test_simple_absolute_symlink'); // this one should still run, even if skipSymlinks is set, @@ -144,18 +144,18 @@ function test_simple_absolute_symlink(callback) { fs.symlinkSync(t[1], t[0], type); unlink.push(t[0]); }); - const result = fs.realpathSync(entry); + const result = realpathSync(entry); assertEqualPath(result, path.resolve(expected)); - asynctest(fs.realpath, [entry], callback, function(err, result) { + asynctest(realpath, [entry], callback, function(err, result) { assertEqualPath(result, path.resolve(expected)); }); } -function test_deep_relative_file_symlink(callback) { +function test_deep_relative_file_symlink(realpath, realpathSync, callback) { console.log('test_deep_relative_file_symlink'); if (skipSymlinks) { common.printSkipMessage('symlink test (no privs)'); - return runNextTest(); + return callback(); } const expected = fixtures.path('cycles', 'root.js'); @@ -175,17 +175,17 @@ function test_deep_relative_file_symlink(callback) { unlink.push(linkPath1); unlink.push(entry); - assertEqualPath(fs.realpathSync(entry), path.resolve(expected)); - asynctest(fs.realpath, [entry], callback, function(err, result) { + assertEqualPath(realpathSync(entry), path.resolve(expected)); + asynctest(realpath, [entry], callback, function(err, result) { assertEqualPath(result, path.resolve(expected)); }); } -function test_deep_relative_dir_symlink(callback) { +function test_deep_relative_dir_symlink(realpath, realpathSync, callback) { console.log('test_deep_relative_dir_symlink'); if (skipSymlinks) { common.printSkipMessage('symlink test (no privs)'); - return runNextTest(); + return callback(); } const expected = fixtures.path('cycles', 'folder'); const path1b = path.join(targetsAbsDir, 'nested-index', 'one'); @@ -202,18 +202,18 @@ function test_deep_relative_dir_symlink(callback) { unlink.push(linkPath1b); unlink.push(entry); - assertEqualPath(fs.realpathSync(entry), path.resolve(expected)); + assertEqualPath(realpathSync(entry), path.resolve(expected)); - asynctest(fs.realpath, [entry], callback, function(err, result) { + asynctest(realpath, [entry], callback, function(err, result) { assertEqualPath(result, path.resolve(expected)); }); } -function test_cyclic_link_protection(callback) { +function test_cyclic_link_protection(realpath, realpathSync, callback) { console.log('test_cyclic_link_protection'); if (skipSymlinks) { common.printSkipMessage('symlink test (no privs)'); - return runNextTest(); + return callback(); } const entry = path.join(tmpDir, '/cycles/realpath-3a'); [ @@ -226,24 +226,24 @@ function test_cyclic_link_protection(callback) { unlink.push(t[0]); }); assert.throws(() => { - fs.realpathSync(entry); + realpathSync(entry); }, common.expectsError({ code: 'ELOOP', type: Error })); asynctest( - fs.realpath, [entry], callback, common.mustCall(function(err, result) { + realpath, [entry], callback, common.mustCall(function(err, result) { assert.strictEqual(err.path, entry); assert.strictEqual(result, undefined); return true; })); } -function test_cyclic_link_overprotection(callback) { +function test_cyclic_link_overprotection(realpath, realpathSync, callback) { console.log('test_cyclic_link_overprotection'); if (skipSymlinks) { common.printSkipMessage('symlink test (no privs)'); - return runNextTest(); + return callback(); } const cycles = `${tmpDir}/cycles`; - const expected = fs.realpathSync(cycles); + const expected = realpathSync(cycles); const folder = `${cycles}/folder`; const link = `${folder}/cycles`; let testPath = cycles; @@ -251,17 +251,17 @@ function test_cyclic_link_overprotection(callback) { try { fs.unlinkSync(link); } catch (ex) {} fs.symlinkSync(cycles, link, 'dir'); unlink.push(link); - assertEqualPath(fs.realpathSync(testPath), path.resolve(expected)); - asynctest(fs.realpath, [testPath], callback, function(er, res) { + assertEqualPath(realpathSync(testPath), path.resolve(expected)); + asynctest(realpath, [testPath], callback, function(er, res) { assertEqualPath(res, path.resolve(expected)); }); } -function test_relative_input_cwd(callback) { +function test_relative_input_cwd(realpath, realpathSync, callback) { console.log('test_relative_input_cwd'); if (skipSymlinks) { common.printSkipMessage('symlink test (no privs)'); - return runNextTest(); + return callback(); } // we need to calculate the relative path to the tmp dir from cwd @@ -286,21 +286,21 @@ function test_relative_input_cwd(callback) { const origcwd = process.cwd(); process.chdir(entrydir); - assertEqualPath(fs.realpathSync(entry), path.resolve(expected)); - asynctest(fs.realpath, [entry], callback, function(err, result) { + assertEqualPath(realpathSync(entry), path.resolve(expected)); + asynctest(realpath, [entry], callback, function(err, result) { process.chdir(origcwd); assertEqualPath(result, path.resolve(expected)); return true; }); } -function test_deep_symlink_mix(callback) { +function test_deep_symlink_mix(realpath, realpathSync, callback) { console.log('test_deep_symlink_mix'); if (common.isWindows) { // This one is a mix of files and directories, and it's quite tricky // to get the file/dir links sorted out correctly. common.printSkipMessage('symlink test (no privs)'); - return runNextTest(); + return callback(); } /* @@ -339,22 +339,22 @@ function test_deep_symlink_mix(callback) { unlink.push(tmp('node-test-realpath-d2')); } const expected = `${tmpAbsDir}/cycles/root.js`; - assertEqualPath(fs.realpathSync(entry), path.resolve(expected)); - asynctest(fs.realpath, [entry], callback, function(err, result) { + assertEqualPath(realpathSync(entry), path.resolve(expected)); + asynctest(realpath, [entry], callback, function(err, result) { assertEqualPath(result, path.resolve(expected)); return true; }); } -function test_non_symlinks(callback) { +function test_non_symlinks(realpath, realpathSync, callback) { console.log('test_non_symlinks'); const entrydir = path.dirname(tmpAbsDir); const entry = `${tmpAbsDir.substr(entrydir.length + 1)}/cycles/root.js`; const expected = `${tmpAbsDir}/cycles/root.js`; const origcwd = process.cwd(); process.chdir(entrydir); - assertEqualPath(fs.realpathSync(entry), path.resolve(expected)); - asynctest(fs.realpath, [entry], callback, function(err, result) { + assertEqualPath(realpathSync(entry), path.resolve(expected)); + asynctest(realpath, [entry], callback, function(err, result) { process.chdir(origcwd); assertEqualPath(result, path.resolve(expected)); return true; @@ -362,19 +362,21 @@ function test_non_symlinks(callback) { } const upone = path.join(process.cwd(), '..'); -function test_escape_cwd(cb) { +function test_escape_cwd(realpath, realpathSync, cb) { console.log('test_escape_cwd'); - asynctest(fs.realpath, ['..'], cb, function(er, uponeActual) { + asynctest(realpath, ['..'], cb, function(er, uponeActual) { assertEqualPath( upone, uponeActual, `realpath("..") expected: ${path.resolve(upone)} actual:${uponeActual}`); }); } -const uponeActual = fs.realpathSync('..'); -assertEqualPath( - upone, uponeActual, - `realpathSync("..") expected: ${path.resolve(upone)} actual:${uponeActual}`); +function test_upone_actual(realpath, realpathSync, cb) { + console.log('test_upone_actual'); + const uponeActual = realpathSync('..'); + assertEqualPath(upone, uponeActual); + cb(); +} // going up with .. multiple times // . @@ -383,23 +385,13 @@ assertEqualPath( // | `-- e -> .. // `-- d -> .. // realpath(a/b/e/d/a/b/e/d/a) ==> a -function test_up_multiple(cb) { +function test_up_multiple(realpath, realpathSync, cb) { console.error('test_up_multiple'); if (skipSymlinks) { common.printSkipMessage('symlink test (no privs)'); - return runNextTest(); - } - function cleanup() { - ['a/b', - 'a' - ].forEach(function(folder) { - try { fs.rmdirSync(tmp(folder)); } catch (ex) {} - }); + return cb(); } - function setup() { - cleanup(); - } - setup(); + common.refreshTmpDir(); fs.mkdirSync(tmp('a'), 0o755); fs.mkdirSync(tmp('a/b'), 0o755); fs.symlinkSync('..', tmp('a/d'), 'dir'); @@ -413,16 +405,15 @@ function test_up_multiple(cb) { const abedabeda = tmp('abedabeda'.split('').join('/')); const abedabeda_real = tmp('a'); - assertEqualPath(fs.realpathSync(abedabeda), abedabeda_real); - assertEqualPath(fs.realpathSync(abedabed), abedabed_real); - fs.realpath(abedabeda, function(er, real) { + assertEqualPath(realpathSync(abedabeda), abedabeda_real); + assertEqualPath(realpathSync(abedabed), abedabed_real); + realpath(abedabeda, function(er, real) { assert.ifError(er); assertEqualPath(abedabeda_real, real); - fs.realpath(abedabed, function(er, real) { + realpath(abedabed, function(er, real) { assert.ifError(er); assertEqualPath(abedabed_real, real); cb(); - cleanup(); }); }); } @@ -436,7 +427,7 @@ function test_up_multiple(cb) { // | `-- x.txt // `-- link -> /tmp/node-test-realpath-abs-kids/a/b/ // realpath(root+'/a/link/c/x.txt') ==> root+'/a/b/c/x.txt' -function test_abs_with_kids(cb) { +function test_abs_with_kids(realpath, realpathSync, cb) { console.log('test_abs_with_kids'); // this one should still run, even if skipSymlinks is set, @@ -476,16 +467,25 @@ function test_abs_with_kids(cb) { setup(); const linkPath = `${root}/a/link/c/x.txt`; const expectPath = `${root}/a/b/c/x.txt`; - const actual = fs.realpathSync(linkPath); + const actual = realpathSync(linkPath); // console.log({link:linkPath,expect:expectPath,actual:actual},'sync'); assertEqualPath(actual, path.resolve(expectPath)); - asynctest(fs.realpath, [linkPath], cb, function(er, actual) { + asynctest(realpath, [linkPath], cb, function(er, actual) { // console.log({link:linkPath,expect:expectPath,actual:actual},'async'); assertEqualPath(actual, path.resolve(expectPath)); cleanup(); }); } +function test_root(realpath, realpathSync, cb) { + assertEqualPath(root, realpathSync('/')); + realpath('/', function(err, result) { + assert.ifError(err); + assertEqualPath(root, result); + cb(); + }); +} + // ---------------------------------------------------------------------------- const tests = [ @@ -500,8 +500,10 @@ const tests = [ test_deep_symlink_mix, test_non_symlinks, test_escape_cwd, + test_upone_actual, test_abs_with_kids, - test_up_multiple + test_up_multiple, + test_root, ]; const numtests = tests.length; let testsRun = 0; @@ -512,17 +514,15 @@ function runNextTest(err) { return console.log(`${numtests} subtests completed OK for fs.realpath`); } testsRun++; - test(runNextTest); + test(fs.realpath, fs.realpathSync, common.mustCall((err) => { + assert.ifError(err); + testsRun++; + test(fs.realpath.native, + fs.realpathSync.native, + common.mustCall(runNextTest)); + })); } - -assertEqualPath(root, fs.realpathSync('/')); -fs.realpath('/', function(err, result) { - assert.ifError(err); - assertEqualPath(root, result); -}); - - function runTest() { const tmpDirs = ['cycles', 'cycles/folder']; tmpDirs.forEach(function(t) { @@ -536,6 +536,6 @@ function runTest() { process.on('exit', function() { - assert.strictEqual(numtests, testsRun); + assert.strictEqual(2 * numtests, testsRun); assert.strictEqual(async_completed, async_expected); });