Skip to content

Commit 08996fd

Browse files
committed
fs: restore JS implementation of realpath
This reverts parts of b488b19 restoring javascript implementation of realpath and realpathSync. Fixes: #7175 Fixes: #6861 Fixes: #7294 Fixes: #7192 Fixes: #7044 Fixes: #6624 Fixes: #6978 PR-URL: #7899 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent f6070a1 commit 08996fd

File tree

4 files changed

+360
-14
lines changed

4 files changed

+360
-14
lines changed

doc/api/fs.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,6 +1222,8 @@ added: v0.1.31
12221222
Asynchronous realpath(3). The `callback` gets two arguments `(err,
12231223
resolvedPath)`. May use `process.cwd` to resolve relative paths.
12241224

1225+
Only paths that can be converted to UTF8 strings are supported.
1226+
12251227
The optional `options` argument can be a string specifying an encoding, or an
12261228
object with an `encoding` property specifying the character encoding to use for
12271229
the path passed to the callback. If the `encoding` is set to `'buffer'`,
@@ -1238,10 +1240,12 @@ added: v0.1.31
12381240

12391241
Synchronous realpath(3). Returns the resolved path.
12401242

1243+
Only paths that can be converted to UTF8 strings are supported.
1244+
12411245
The optional `options` argument can be a string specifying an encoding, or an
12421246
object with an `encoding` property specifying the character encoding to use for
1243-
the path passed to the callback. If the `encoding` is set to `'buffer'`,
1244-
the path returned will be passed as a `Buffer` object.
1247+
the returned value. If the `encoding` is set to `'buffer'`, the path returned
1248+
will be passed as a `Buffer` object.
12451249

12461250
## fs.rename(oldPath, newPath, callback)
12471251
<!-- YAML

lib/fs.js

Lines changed: 213 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1563,38 +1563,239 @@ fs.unwatchFile = function(filename, listener) {
15631563
};
15641564

15651565

1566-
fs.realpathSync = function realpathSync(path, options) {
1566+
// Regexp that finds the next portion of a (partial) path
1567+
// result is [base_with_slash, base], e.g. ['somedir/', 'somedir']
1568+
const nextPartRe = isWindows ?
1569+
/(.*?)(?:[\/\\]+|$)/g :
1570+
/(.*?)(?:[\/]+|$)/g;
1571+
1572+
// Regex to find the device root, including trailing slash. E.g. 'c:\\'.
1573+
const splitRootRe = isWindows ?
1574+
/^(?:[a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?[\\\/]*/ :
1575+
/^[\/]*/;
1576+
1577+
function encodeRealpathResult(result, options, err) {
1578+
if (!options || !options.encoding || options.encoding === 'utf8' || err)
1579+
return result;
1580+
const asBuffer = Buffer.from(result);
1581+
if (options.encoding === 'buffer') {
1582+
return asBuffer;
1583+
} else {
1584+
return asBuffer.toString(options.encoding);
1585+
}
1586+
}
1587+
1588+
fs.realpathSync = function realpathSync(p, options) {
15671589
if (!options)
15681590
options = {};
15691591
else if (typeof options === 'string')
15701592
options = {encoding: options};
15711593
else if (typeof options !== 'object')
15721594
throw new TypeError('"options" must be a string or an object');
1573-
nullCheck(path);
1574-
return binding.realpath(pathModule._makeLong(path), options.encoding);
1595+
nullCheck(p);
1596+
1597+
p = p.toString('utf8');
1598+
p = pathModule.resolve(p);
1599+
1600+
const seenLinks = {};
1601+
const knownHard = {};
1602+
1603+
// current character position in p
1604+
var pos;
1605+
// the partial path so far, including a trailing slash if any
1606+
var current;
1607+
// the partial path without a trailing slash (except when pointing at a root)
1608+
var base;
1609+
// the partial path scanned in the previous round, with slash
1610+
var previous;
1611+
1612+
start();
1613+
1614+
function start() {
1615+
// Skip over roots
1616+
var m = splitRootRe.exec(p);
1617+
pos = m[0].length;
1618+
current = m[0];
1619+
base = m[0];
1620+
previous = '';
1621+
1622+
// On windows, check that the root exists. On unix there is no need.
1623+
if (isWindows && !knownHard[base]) {
1624+
fs.lstatSync(base);
1625+
knownHard[base] = true;
1626+
}
1627+
}
1628+
1629+
// walk down the path, swapping out linked pathparts for their real
1630+
// values
1631+
// NB: p.length changes.
1632+
while (pos < p.length) {
1633+
// find the next part
1634+
nextPartRe.lastIndex = pos;
1635+
var result = nextPartRe.exec(p);
1636+
previous = current;
1637+
current += result[0];
1638+
base = previous + result[1];
1639+
pos = nextPartRe.lastIndex;
1640+
1641+
// continue if not a symlink
1642+
if (knownHard[base]) {
1643+
continue;
1644+
}
1645+
1646+
var resolvedLink;
1647+
var stat = fs.lstatSync(base);
1648+
if (!stat.isSymbolicLink()) {
1649+
knownHard[base] = true;
1650+
continue;
1651+
}
1652+
1653+
// read the link if it wasn't read before
1654+
// dev/ino always return 0 on windows, so skip the check.
1655+
var linkTarget = null;
1656+
if (!isWindows) {
1657+
var id = stat.dev.toString(32) + ':' + stat.ino.toString(32);
1658+
if (seenLinks.hasOwnProperty(id)) {
1659+
linkTarget = seenLinks[id];
1660+
}
1661+
}
1662+
if (linkTarget === null) {
1663+
fs.statSync(base);
1664+
linkTarget = fs.readlinkSync(base);
1665+
}
1666+
resolvedLink = pathModule.resolve(previous, linkTarget);
1667+
1668+
if (!isWindows) seenLinks[id] = linkTarget;
1669+
1670+
// resolve the link, then start over
1671+
p = pathModule.resolve(resolvedLink, p.slice(pos));
1672+
start();
1673+
}
1674+
1675+
return encodeRealpathResult(p, options);
15751676
};
15761677

15771678

1578-
fs.realpath = function realpath(path, options, callback) {
1679+
fs.realpath = function realpath(p, options, callback) {
1680+
if (typeof callback !== 'function') {
1681+
callback = maybeCallback(options);
1682+
options = {};
1683+
}
1684+
15791685
if (!options) {
15801686
options = {};
15811687
} else if (typeof options === 'function') {
1582-
callback = options;
15831688
options = {};
15841689
} else if (typeof options === 'string') {
15851690
options = {encoding: options};
15861691
} else if (typeof options !== 'object') {
15871692
throw new TypeError('"options" must be a string or an object');
15881693
}
1589-
callback = makeCallback(callback);
1590-
if (!nullCheck(path, callback))
1694+
if (!nullCheck(p, callback))
15911695
return;
1592-
var req = new FSReqWrap();
1593-
req.oncomplete = callback;
1594-
binding.realpath(pathModule._makeLong(path), options.encoding, req);
1595-
return;
1596-
};
15971696

1697+
p = p.toString('utf8');
1698+
p = pathModule.resolve(p);
1699+
1700+
const seenLinks = {};
1701+
const knownHard = {};
1702+
1703+
// current character position in p
1704+
var pos;
1705+
// the partial path so far, including a trailing slash if any
1706+
var current;
1707+
// the partial path without a trailing slash (except when pointing at a root)
1708+
var base;
1709+
// the partial path scanned in the previous round, with slash
1710+
var previous;
1711+
1712+
start();
1713+
1714+
function start() {
1715+
// Skip over roots
1716+
var m = splitRootRe.exec(p);
1717+
pos = m[0].length;
1718+
current = m[0];
1719+
base = m[0];
1720+
previous = '';
1721+
1722+
// On windows, check that the root exists. On unix there is no need.
1723+
if (isWindows && !knownHard[base]) {
1724+
fs.lstat(base, function(err) {
1725+
if (err) return callback(err);
1726+
knownHard[base] = true;
1727+
LOOP();
1728+
});
1729+
} else {
1730+
process.nextTick(LOOP);
1731+
}
1732+
}
1733+
1734+
// walk down the path, swapping out linked pathparts for their real
1735+
// values
1736+
function LOOP() {
1737+
// stop if scanned past end of path
1738+
if (pos >= p.length) {
1739+
return callback(null, encodeRealpathResult(p, options));
1740+
}
1741+
1742+
// find the next part
1743+
nextPartRe.lastIndex = pos;
1744+
var result = nextPartRe.exec(p);
1745+
previous = current;
1746+
current += result[0];
1747+
base = previous + result[1];
1748+
pos = nextPartRe.lastIndex;
1749+
1750+
// continue if not a symlink
1751+
if (knownHard[base]) {
1752+
return process.nextTick(LOOP);
1753+
}
1754+
1755+
return fs.lstat(base, gotStat);
1756+
}
1757+
1758+
function gotStat(err, stat) {
1759+
if (err) return callback(err);
1760+
1761+
// if not a symlink, skip to the next path part
1762+
if (!stat.isSymbolicLink()) {
1763+
knownHard[base] = true;
1764+
return process.nextTick(LOOP);
1765+
}
1766+
1767+
// stat & read the link if not read before
1768+
// call gotTarget as soon as the link target is known
1769+
// dev/ino always return 0 on windows, so skip the check.
1770+
if (!isWindows) {
1771+
var id = stat.dev.toString(32) + ':' + stat.ino.toString(32);
1772+
if (seenLinks.hasOwnProperty(id)) {
1773+
return gotTarget(null, seenLinks[id], base);
1774+
}
1775+
}
1776+
fs.stat(base, function(err) {
1777+
if (err) return callback(err);
1778+
1779+
fs.readlink(base, function(err, target) {
1780+
if (!isWindows) seenLinks[id] = target;
1781+
gotTarget(err, target);
1782+
});
1783+
});
1784+
}
1785+
1786+
function gotTarget(err, target, base) {
1787+
if (err) return callback(err);
1788+
1789+
var resolvedLink = pathModule.resolve(previous, target);
1790+
gotResolvedLink(resolvedLink);
1791+
}
1792+
1793+
function gotResolvedLink(resolvedLink) {
1794+
// resolve the link, then start over
1795+
p = pathModule.resolve(resolvedLink, p.slice(pos));
1796+
start();
1797+
}
1798+
};
15981799

15991800
fs.mkdtemp = function(prefix, options, callback) {
16001801
if (!prefix || typeof prefix !== 'string')
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const fs = require('fs');
5+
6+
const string_dir = fs.realpathSync(common.fixturesDir);
7+
const buffer_dir = Buffer.from(string_dir);
8+
9+
const encodings = ['ascii', 'utf8', 'utf16le', 'ucs2',
10+
'base64', 'binary', 'hex'];
11+
var expected = {};
12+
encodings.forEach((encoding) => {
13+
expected[encoding] = buffer_dir.toString(encoding);
14+
});
15+
16+
17+
// test sync version
18+
for (var encoding in expected) {
19+
const expected_value = expected[encoding];
20+
let result;
21+
22+
result = fs.realpathSync(string_dir, {encoding: encoding});
23+
assert.strictEqual(result, expected_value);
24+
25+
result = fs.realpathSync(string_dir, encoding);
26+
assert.strictEqual(result, expected_value);
27+
28+
result = fs.realpathSync(buffer_dir, {encoding: encoding});
29+
assert.strictEqual(result, expected_value);
30+
31+
result = fs.realpathSync(buffer_dir, encoding);
32+
assert.strictEqual(result, expected_value);
33+
}
34+
35+
let buffer_result;
36+
buffer_result = fs.realpathSync(string_dir, {encoding: 'buffer'});
37+
assert.deepStrictEqual(buffer_result, buffer_dir);
38+
39+
buffer_result = fs.realpathSync(string_dir, 'buffer');
40+
assert.deepStrictEqual(buffer_result, buffer_dir);
41+
42+
buffer_result = fs.realpathSync(buffer_dir, {encoding: 'buffer'});
43+
assert.deepStrictEqual(buffer_result, buffer_dir);
44+
45+
buffer_result = fs.realpathSync(buffer_dir, 'buffer');
46+
assert.deepStrictEqual(buffer_result, buffer_dir);
47+
48+
// test async version
49+
for (encoding in expected) {
50+
const expected_value = expected[encoding];
51+
52+
fs.realpath(string_dir, {encoding: encoding}, common.mustCall((err, res) => {
53+
assert(!err);
54+
assert.strictEqual(res, expected_value);
55+
}));
56+
fs.realpath(string_dir, encoding, common.mustCall((err, res) => {
57+
assert(!err);
58+
assert.strictEqual(res, expected_value);
59+
}));
60+
fs.realpath(buffer_dir, {encoding: encoding}, common.mustCall((err, res) => {
61+
assert(!err);
62+
assert.strictEqual(res, expected_value);
63+
}));
64+
fs.realpath(buffer_dir, encoding, common.mustCall((err, res) => {
65+
assert(!err);
66+
assert.strictEqual(res, expected_value);
67+
}));
68+
}
69+
70+
fs.realpath(string_dir, {encoding: 'buffer'}, common.mustCall((err, res) => {
71+
assert(!err);
72+
assert.deepStrictEqual(res, buffer_dir);
73+
}));
74+
75+
fs.realpath(string_dir, 'buffer', common.mustCall((err, res) => {
76+
assert(!err);
77+
assert.deepStrictEqual(res, buffer_dir);
78+
}));
79+
80+
fs.realpath(buffer_dir, {encoding: 'buffer'}, common.mustCall((err, res) => {
81+
assert(!err);
82+
assert.deepStrictEqual(res, buffer_dir);
83+
}));
84+
85+
fs.realpath(buffer_dir, 'buffer', common.mustCall((err, res) => {
86+
assert(!err);
87+
assert.deepStrictEqual(res, buffer_dir);
88+
}));

0 commit comments

Comments
 (0)