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,
|
||||
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
|
||||
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'`,
|
||||
@ -1238,10 +1240,12 @@ added: v0.1.31
|
||||
|
||||
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
|
||||
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 returned will be passed as a `Buffer` object.
|
||||
the returned value. If the `encoding` is set to `'buffer'`, the path returned
|
||||
will be passed as a `Buffer` object.
|
||||
|
||||
## fs.rename(oldPath, newPath, callback)
|
||||
<!-- 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)
|
||||
options = {};
|
||||
else if (typeof options === 'string')
|
||||
options = {encoding: options};
|
||||
else if (typeof options !== 'object')
|
||||
throw new TypeError('"options" must be a string or an object');
|
||||
nullCheck(path);
|
||||
return binding.realpath(pathModule._makeLong(path), options.encoding);
|
||||
nullCheck(p);
|
||||
|
||||
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) {
|
||||
options = {};
|
||||
} else if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
} else if (typeof options === 'string') {
|
||||
options = {encoding: options};
|
||||
} else if (typeof options !== 'object') {
|
||||
throw new TypeError('"options" must be a string or an object');
|
||||
}
|
||||
callback = makeCallback(callback);
|
||||
if (!nullCheck(path, callback))
|
||||
if (!nullCheck(p, callback))
|
||||
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) {
|
||||
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