fs: restore JS implementation of realpath
This reverts parts of b488b19eaf
restoring javascript implementation of realpath and realpathSync.
Fixes: https://github.com/nodejs/node/issues/7175
Fixes: https://github.com/nodejs/node/issues/6861
Fixes: https://github.com/nodejs/node/issues/7294
Fixes: https://github.com/nodejs/node/issues/7192
Fixes: https://github.com/nodejs/node/issues/7044
Fixes: https://github.com/nodejs/node/issues/6624
Fixes: https://github.com/nodejs/node/issues/6978
PR-URL: https://github.com/nodejs/node/pull/7899
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
This commit is contained in:
parent
f6070a1a02
commit
08996fde3c
@ -1222,6 +1222,8 @@ added: v0.1.31
|
|||||||
Asynchronous realpath(3). The `callback` gets two arguments `(err,
|
Asynchronous realpath(3). The `callback` gets two arguments `(err,
|
||||||
resolvedPath)`. May use `process.cwd` to resolve relative paths.
|
resolvedPath)`. May use `process.cwd` to resolve relative paths.
|
||||||
|
|
||||||
|
Only paths that can be converted to UTF8 strings are supported.
|
||||||
|
|
||||||
The optional `options` argument can be a string specifying an encoding, or an
|
The optional `options` argument can be a string specifying an encoding, or an
|
||||||
object with an `encoding` property specifying the character encoding to use for
|
object with an `encoding` property specifying the character encoding to use for
|
||||||
the path passed to the callback. If the `encoding` is set to `'buffer'`,
|
the path passed to the callback. If the `encoding` is set to `'buffer'`,
|
||||||
@ -1238,10 +1240,12 @@ added: v0.1.31
|
|||||||
|
|
||||||
Synchronous realpath(3). Returns the resolved path.
|
Synchronous realpath(3). Returns the resolved path.
|
||||||
|
|
||||||
|
Only paths that can be converted to UTF8 strings are supported.
|
||||||
|
|
||||||
The optional `options` argument can be a string specifying an encoding, or an
|
The optional `options` argument can be a string specifying an encoding, or an
|
||||||
object with an `encoding` property specifying the character encoding to use for
|
object with an `encoding` property specifying the character encoding to use for
|
||||||
the path passed to the callback. If the `encoding` is set to `'buffer'`,
|
the returned value. If the `encoding` is set to `'buffer'`, the path returned
|
||||||
the path returned will be passed as a `Buffer` object.
|
will be passed as a `Buffer` object.
|
||||||
|
|
||||||
## fs.rename(oldPath, newPath, callback)
|
## fs.rename(oldPath, newPath, callback)
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
|
225
lib/fs.js
225
lib/fs.js
@ -1563,38 +1563,239 @@ fs.unwatchFile = function(filename, listener) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
fs.realpathSync = function realpathSync(path, options) {
|
// Regexp that finds the next portion of a (partial) path
|
||||||
|
// result is [base_with_slash, base], e.g. ['somedir/', 'somedir']
|
||||||
|
const nextPartRe = isWindows ?
|
||||||
|
/(.*?)(?:[\/\\]+|$)/g :
|
||||||
|
/(.*?)(?:[\/]+|$)/g;
|
||||||
|
|
||||||
|
// Regex to find the device root, including trailing slash. E.g. 'c:\\'.
|
||||||
|
const splitRootRe = isWindows ?
|
||||||
|
/^(?:[a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?[\\\/]*/ :
|
||||||
|
/^[\/]*/;
|
||||||
|
|
||||||
|
function encodeRealpathResult(result, options, err) {
|
||||||
|
if (!options || !options.encoding || options.encoding === 'utf8' || err)
|
||||||
|
return result;
|
||||||
|
const asBuffer = Buffer.from(result);
|
||||||
|
if (options.encoding === 'buffer') {
|
||||||
|
return asBuffer;
|
||||||
|
} else {
|
||||||
|
return asBuffer.toString(options.encoding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.realpathSync = function realpathSync(p, options) {
|
||||||
if (!options)
|
if (!options)
|
||||||
options = {};
|
options = {};
|
||||||
else if (typeof options === 'string')
|
else if (typeof options === 'string')
|
||||||
options = {encoding: options};
|
options = {encoding: options};
|
||||||
else if (typeof options !== 'object')
|
else if (typeof options !== 'object')
|
||||||
throw new TypeError('"options" must be a string or an object');
|
throw new TypeError('"options" must be a string or an object');
|
||||||
nullCheck(path);
|
nullCheck(p);
|
||||||
return binding.realpath(pathModule._makeLong(path), options.encoding);
|
|
||||||
|
p = p.toString('utf8');
|
||||||
|
p = pathModule.resolve(p);
|
||||||
|
|
||||||
|
const seenLinks = {};
|
||||||
|
const knownHard = {};
|
||||||
|
|
||||||
|
// current character position in p
|
||||||
|
var pos;
|
||||||
|
// the partial path so far, including a trailing slash if any
|
||||||
|
var current;
|
||||||
|
// the partial path without a trailing slash (except when pointing at a root)
|
||||||
|
var base;
|
||||||
|
// the partial path scanned in the previous round, with slash
|
||||||
|
var previous;
|
||||||
|
|
||||||
|
start();
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
// Skip over roots
|
||||||
|
var m = splitRootRe.exec(p);
|
||||||
|
pos = m[0].length;
|
||||||
|
current = m[0];
|
||||||
|
base = m[0];
|
||||||
|
previous = '';
|
||||||
|
|
||||||
|
// On windows, check that the root exists. On unix there is no need.
|
||||||
|
if (isWindows && !knownHard[base]) {
|
||||||
|
fs.lstatSync(base);
|
||||||
|
knownHard[base] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// walk down the path, swapping out linked pathparts for their real
|
||||||
|
// values
|
||||||
|
// NB: p.length changes.
|
||||||
|
while (pos < p.length) {
|
||||||
|
// find the next part
|
||||||
|
nextPartRe.lastIndex = pos;
|
||||||
|
var result = nextPartRe.exec(p);
|
||||||
|
previous = current;
|
||||||
|
current += result[0];
|
||||||
|
base = previous + result[1];
|
||||||
|
pos = nextPartRe.lastIndex;
|
||||||
|
|
||||||
|
// continue if not a symlink
|
||||||
|
if (knownHard[base]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var resolvedLink;
|
||||||
|
var stat = fs.lstatSync(base);
|
||||||
|
if (!stat.isSymbolicLink()) {
|
||||||
|
knownHard[base] = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the link if it wasn't read before
|
||||||
|
// dev/ino always return 0 on windows, so skip the check.
|
||||||
|
var linkTarget = null;
|
||||||
|
if (!isWindows) {
|
||||||
|
var id = stat.dev.toString(32) + ':' + stat.ino.toString(32);
|
||||||
|
if (seenLinks.hasOwnProperty(id)) {
|
||||||
|
linkTarget = seenLinks[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (linkTarget === null) {
|
||||||
|
fs.statSync(base);
|
||||||
|
linkTarget = fs.readlinkSync(base);
|
||||||
|
}
|
||||||
|
resolvedLink = pathModule.resolve(previous, linkTarget);
|
||||||
|
|
||||||
|
if (!isWindows) seenLinks[id] = linkTarget;
|
||||||
|
|
||||||
|
// resolve the link, then start over
|
||||||
|
p = pathModule.resolve(resolvedLink, p.slice(pos));
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
|
return encodeRealpathResult(p, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
fs.realpath = function realpath(path, options, callback) {
|
fs.realpath = function realpath(p, options, callback) {
|
||||||
|
if (typeof callback !== 'function') {
|
||||||
|
callback = maybeCallback(options);
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
|
||||||
if (!options) {
|
if (!options) {
|
||||||
options = {};
|
options = {};
|
||||||
} else if (typeof options === 'function') {
|
} else if (typeof options === 'function') {
|
||||||
callback = options;
|
|
||||||
options = {};
|
options = {};
|
||||||
} else if (typeof options === 'string') {
|
} else if (typeof options === 'string') {
|
||||||
options = {encoding: options};
|
options = {encoding: options};
|
||||||
} else if (typeof options !== 'object') {
|
} else if (typeof options !== 'object') {
|
||||||
throw new TypeError('"options" must be a string or an object');
|
throw new TypeError('"options" must be a string or an object');
|
||||||
}
|
}
|
||||||
callback = makeCallback(callback);
|
if (!nullCheck(p, callback))
|
||||||
if (!nullCheck(path, callback))
|
|
||||||
return;
|
return;
|
||||||
var req = new FSReqWrap();
|
|
||||||
req.oncomplete = callback;
|
|
||||||
binding.realpath(pathModule._makeLong(path), options.encoding, req);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
p = p.toString('utf8');
|
||||||
|
p = pathModule.resolve(p);
|
||||||
|
|
||||||
|
const seenLinks = {};
|
||||||
|
const knownHard = {};
|
||||||
|
|
||||||
|
// current character position in p
|
||||||
|
var pos;
|
||||||
|
// the partial path so far, including a trailing slash if any
|
||||||
|
var current;
|
||||||
|
// the partial path without a trailing slash (except when pointing at a root)
|
||||||
|
var base;
|
||||||
|
// the partial path scanned in the previous round, with slash
|
||||||
|
var previous;
|
||||||
|
|
||||||
|
start();
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
// Skip over roots
|
||||||
|
var m = splitRootRe.exec(p);
|
||||||
|
pos = m[0].length;
|
||||||
|
current = m[0];
|
||||||
|
base = m[0];
|
||||||
|
previous = '';
|
||||||
|
|
||||||
|
// On windows, check that the root exists. On unix there is no need.
|
||||||
|
if (isWindows && !knownHard[base]) {
|
||||||
|
fs.lstat(base, function(err) {
|
||||||
|
if (err) return callback(err);
|
||||||
|
knownHard[base] = true;
|
||||||
|
LOOP();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
process.nextTick(LOOP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// walk down the path, swapping out linked pathparts for their real
|
||||||
|
// values
|
||||||
|
function LOOP() {
|
||||||
|
// stop if scanned past end of path
|
||||||
|
if (pos >= p.length) {
|
||||||
|
return callback(null, encodeRealpathResult(p, options));
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the next part
|
||||||
|
nextPartRe.lastIndex = pos;
|
||||||
|
var result = nextPartRe.exec(p);
|
||||||
|
previous = current;
|
||||||
|
current += result[0];
|
||||||
|
base = previous + result[1];
|
||||||
|
pos = nextPartRe.lastIndex;
|
||||||
|
|
||||||
|
// continue if not a symlink
|
||||||
|
if (knownHard[base]) {
|
||||||
|
return process.nextTick(LOOP);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs.lstat(base, gotStat);
|
||||||
|
}
|
||||||
|
|
||||||
|
function gotStat(err, stat) {
|
||||||
|
if (err) return callback(err);
|
||||||
|
|
||||||
|
// if not a symlink, skip to the next path part
|
||||||
|
if (!stat.isSymbolicLink()) {
|
||||||
|
knownHard[base] = true;
|
||||||
|
return process.nextTick(LOOP);
|
||||||
|
}
|
||||||
|
|
||||||
|
// stat & read the link if not read before
|
||||||
|
// call gotTarget as soon as the link target is known
|
||||||
|
// dev/ino always return 0 on windows, so skip the check.
|
||||||
|
if (!isWindows) {
|
||||||
|
var id = stat.dev.toString(32) + ':' + stat.ino.toString(32);
|
||||||
|
if (seenLinks.hasOwnProperty(id)) {
|
||||||
|
return gotTarget(null, seenLinks[id], base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fs.stat(base, function(err) {
|
||||||
|
if (err) return callback(err);
|
||||||
|
|
||||||
|
fs.readlink(base, function(err, target) {
|
||||||
|
if (!isWindows) seenLinks[id] = target;
|
||||||
|
gotTarget(err, target);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function gotTarget(err, target, base) {
|
||||||
|
if (err) return callback(err);
|
||||||
|
|
||||||
|
var resolvedLink = pathModule.resolve(previous, target);
|
||||||
|
gotResolvedLink(resolvedLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
function gotResolvedLink(resolvedLink) {
|
||||||
|
// resolve the link, then start over
|
||||||
|
p = pathModule.resolve(resolvedLink, p.slice(pos));
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
fs.mkdtemp = function(prefix, options, callback) {
|
fs.mkdtemp = function(prefix, options, callback) {
|
||||||
if (!prefix || typeof prefix !== 'string')
|
if (!prefix || typeof prefix !== 'string')
|
||||||
|
88
test/parallel/test-fs-realpath-buffer-encoding.js
Normal file
88
test/parallel/test-fs-realpath-buffer-encoding.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const string_dir = fs.realpathSync(common.fixturesDir);
|
||||||
|
const buffer_dir = Buffer.from(string_dir);
|
||||||
|
|
||||||
|
const encodings = ['ascii', 'utf8', 'utf16le', 'ucs2',
|
||||||
|
'base64', 'binary', 'hex'];
|
||||||
|
var expected = {};
|
||||||
|
encodings.forEach((encoding) => {
|
||||||
|
expected[encoding] = buffer_dir.toString(encoding);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// test sync version
|
||||||
|
for (var encoding in expected) {
|
||||||
|
const expected_value = expected[encoding];
|
||||||
|
let result;
|
||||||
|
|
||||||
|
result = fs.realpathSync(string_dir, {encoding: encoding});
|
||||||
|
assert.strictEqual(result, expected_value);
|
||||||
|
|
||||||
|
result = fs.realpathSync(string_dir, encoding);
|
||||||
|
assert.strictEqual(result, expected_value);
|
||||||
|
|
||||||
|
result = fs.realpathSync(buffer_dir, {encoding: encoding});
|
||||||
|
assert.strictEqual(result, expected_value);
|
||||||
|
|
||||||
|
result = fs.realpathSync(buffer_dir, encoding);
|
||||||
|
assert.strictEqual(result, expected_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
let buffer_result;
|
||||||
|
buffer_result = fs.realpathSync(string_dir, {encoding: 'buffer'});
|
||||||
|
assert.deepStrictEqual(buffer_result, buffer_dir);
|
||||||
|
|
||||||
|
buffer_result = fs.realpathSync(string_dir, 'buffer');
|
||||||
|
assert.deepStrictEqual(buffer_result, buffer_dir);
|
||||||
|
|
||||||
|
buffer_result = fs.realpathSync(buffer_dir, {encoding: 'buffer'});
|
||||||
|
assert.deepStrictEqual(buffer_result, buffer_dir);
|
||||||
|
|
||||||
|
buffer_result = fs.realpathSync(buffer_dir, 'buffer');
|
||||||
|
assert.deepStrictEqual(buffer_result, buffer_dir);
|
||||||
|
|
||||||
|
// test async version
|
||||||
|
for (encoding in expected) {
|
||||||
|
const expected_value = expected[encoding];
|
||||||
|
|
||||||
|
fs.realpath(string_dir, {encoding: encoding}, common.mustCall((err, res) => {
|
||||||
|
assert(!err);
|
||||||
|
assert.strictEqual(res, expected_value);
|
||||||
|
}));
|
||||||
|
fs.realpath(string_dir, encoding, common.mustCall((err, res) => {
|
||||||
|
assert(!err);
|
||||||
|
assert.strictEqual(res, expected_value);
|
||||||
|
}));
|
||||||
|
fs.realpath(buffer_dir, {encoding: encoding}, common.mustCall((err, res) => {
|
||||||
|
assert(!err);
|
||||||
|
assert.strictEqual(res, expected_value);
|
||||||
|
}));
|
||||||
|
fs.realpath(buffer_dir, encoding, common.mustCall((err, res) => {
|
||||||
|
assert(!err);
|
||||||
|
assert.strictEqual(res, expected_value);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.realpath(string_dir, {encoding: 'buffer'}, common.mustCall((err, res) => {
|
||||||
|
assert(!err);
|
||||||
|
assert.deepStrictEqual(res, buffer_dir);
|
||||||
|
}));
|
||||||
|
|
||||||
|
fs.realpath(string_dir, 'buffer', common.mustCall((err, res) => {
|
||||||
|
assert(!err);
|
||||||
|
assert.deepStrictEqual(res, buffer_dir);
|
||||||
|
}));
|
||||||
|
|
||||||
|
fs.realpath(buffer_dir, {encoding: 'buffer'}, common.mustCall((err, res) => {
|
||||||
|
assert(!err);
|
||||||
|
assert.deepStrictEqual(res, buffer_dir);
|
||||||
|
}));
|
||||||
|
|
||||||
|
fs.realpath(buffer_dir, 'buffer', common.mustCall((err, res) => {
|
||||||
|
assert(!err);
|
||||||
|
assert.deepStrictEqual(res, buffer_dir);
|
||||||
|
}));
|
53
test/parallel/test-fs-realpath-on-substed-drive.js
Normal file
53
test/parallel/test-fs-realpath-on-substed-drive.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const fs = require('fs');
|
||||||
|
const spawnSync = require('child_process').spawnSync;
|
||||||
|
|
||||||
|
if (!common.isWindows) {
|
||||||
|
common.skip('Test for Windows only');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let result;
|
||||||
|
|
||||||
|
// create a subst drive
|
||||||
|
const driveLetters = 'ABCDEFGHIJKLMNOPQRSTUWXYZ';
|
||||||
|
let drive;
|
||||||
|
for (var i = 0; i < driveLetters.length; ++i) {
|
||||||
|
drive = `${driveLetters[i]}:`;
|
||||||
|
result = spawnSync('subst', [drive, common.fixturesDir]);
|
||||||
|
if (result.status === 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (i === driveLetters.length) {
|
||||||
|
common.skip('Cannot create subst drive');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// schedule cleanup (and check if all callbacks where called)
|
||||||
|
process.on('exit', function() {
|
||||||
|
spawnSync('subst', ['/d', drive]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// test:
|
||||||
|
const filename = `${drive}\\empty.js`;
|
||||||
|
const filenameBuffer = Buffer.from(filename);
|
||||||
|
|
||||||
|
result = fs.realpathSync(filename);
|
||||||
|
assert.strictEqual(result, filename);
|
||||||
|
|
||||||
|
result = fs.realpathSync(filename, 'buffer');
|
||||||
|
assert(Buffer.isBuffer(result));
|
||||||
|
assert(result.equals(filenameBuffer));
|
||||||
|
|
||||||
|
fs.realpath(filename, common.mustCall(function(err, result) {
|
||||||
|
assert(!err);
|
||||||
|
assert.strictEqual(result, filename);
|
||||||
|
}));
|
||||||
|
|
||||||
|
fs.realpath(filename, 'buffer', common.mustCall(function(err, result) {
|
||||||
|
assert(!err);
|
||||||
|
assert(Buffer.isBuffer(result));
|
||||||
|
assert(result.equals(filenameBuffer));
|
||||||
|
}));
|
Loading…
x
Reference in New Issue
Block a user