fs.realpathSync 源码解析
const emptyObj = ObjectCreate(null);
function realpathSync(p, options) {
options = getOptions(options, emptyObj);
p = toPathIfFileURL(p);
if (typeof p !== "string") {
p += "";
}
validatePath(p);
p = pathModule.resolve(p);
const cache = options[realpathCacheKey];
const maybeCachedResult = cache && cache.get(p);
if (maybeCachedResult) {
return maybeCachedResult;
}
const seenLinks = ObjectCreate(null);
const knownHard = ObjectCreate(null);
const original = p;
// Current character position in p
let pos;
// The partial path so far, including a trailing slash if any
let current;
// The partial path without a trailing slash (except when pointing at a root)
let base;
// The partial path scanned in the previous round, with slash
let previous;
// Skip over roots
current = base = splitRoot(p);
pos = current.length;
// On windows, check that the root exists. On unix there is no need.
if (isWindows) {
const ctx = { path: base };
binding.lstat(pathModule.toNamespacedPath(base), false, undefined, ctx);
handleErrorFromBinding(ctx);
knownHard[base] = true;
}
// Walk down the path, swapping out linked path parts for their real
// values
// NB: p.length changes.
while (pos < p.length) {
// p: 真实路径
// find the next part
const result = nextPart(p, pos);
previous = current;
if (result === -1) {
const last = p.slice(pos);
current += last;
base = previous + last;
pos = p.length;
} else {
current += p.slice(pos, result + 1);
base = previous + p.slice(pos, result);
pos = result + 1;
}
// Continue if not a symlink, break if a pipe/socket
if (knownHard[base] || (cache && cache.get(base) === base)) {
if (isFileType(statValues, S_IFIFO) || isFileType(statValues, S_IFSOCK)) {
break;
}
continue;
}
let resolvedLink;
const maybeCachedResolved = cache && cache.get(base);
if (maybeCachedResolved) {
resolvedLink = maybeCachedResolved;
} else {
// Use stats array directly to avoid creating an fs.Stats instance just
// for our internal use.
const baseLong = pathModule.toNamespacedPath(base);
const ctx = { path: base };
const stats = binding.lstat(baseLong, true, undefined, ctx);
handleErrorFromBinding(ctx);
if (!isFileType(stats, S_IFLNK)) {
knownHard[base] = true;
if (cache) cache.set(base, base);
continue;
}
// Read the link if it wasn't read before
// dev/ino always return 0 on windows, so skip the check.
let linkTarget = null;
let id;
if (!isWindows) {
const dev = stats[0].toString(32);
const ino = stats[7].toString(32);
id = `${dev}:${ino}`;
if (seenLinks[id]) {
linkTarget = seenLinks[id];
}
}
if (linkTarget === null) {
const ctx = { path: base };
binding.stat(baseLong, false, undefined, ctx);
handleErrorFromBinding(ctx);
linkTarget = binding.readlink(baseLong, undefined, undefined, ctx);
handleErrorFromBinding(ctx);
}
resolvedLink = pathModule.resolve(previous, linkTarget);
if (cache) cache.set(base, resolvedLink);
if (!isWindows) seenLinks[id] = linkTarget;
}
// Resolve the link, then start over
p = pathModule.resolve(resolvedLink, p.slice(pos));
// Skip over roots
current = base = splitRoot(p);
pos = current.length;
// On windows, check that the root exists. On unix there is no need.
if (isWindows && !knownHard[base]) {
const ctx = { path: base };
binding.lstat(pathModule.toNamespacedPath(base), false, undefined, ctx);
handleErrorFromBinding(ctx);
knownHard[base] = true;
}
}
if (cache) cache.set(original, p);
return encodeRealpathResult(p, options);
}
执行流程
1. 参数兜底
options = getOptions(options, emptyObj);
p = toPathIfFileURL(p);
if (typeof p !== "string") {
p += "";
}
validatePath(p);
p = pathModule.resolve(p);
- 如果没传 options 则赋值一个空对象给 options
- 如果是一个 url 路径则转换为绝对路径
- 如果路径不是 string 类型,将路径转为 string 类型
- 判断路径是否为有效路径
- 把路径转为绝对路径
2. 查询缓存
const cache = options[realpathCacheKey];
const maybeCachedResult = cache && cache.get(p);
if (maybeCachedResult) {
return maybeCachedResult;
}
如果从缓存中找到,则直接返回结果
3. 定义参数
const seenLinks = ObjectCreate(null);
const knownHard = ObjectCreate(null);
const original = p;
// Current character position in p
let pos;
// The partial path so far, including a trailing slash if any
let current;
// The partial path without a trailing slash (except when pointing at a root)
let base;
// The partial path scanned in the previous round, with slash
let previous;
// Skip over roots
current = base = splitRoot(p);
pos = current.length;
// On windows, check that the root exists. On unix there is no need.
if (isWindows) {
const ctx = { path: base };
binding.lstat(pathModule.toNamespacedPath(base), false, undefined, ctx);
handleErrorFromBinding(ctx);
knownHard[base] = true;
}
-
定义所有软链接的缓存 seenLinks
-
缓存 p 给 original
-
跳过 / 开始遍历
current = base = splitRoot(p);
pos = current.length;
4. 查找下一个 \
// find the next part
const result = nextPart(p, pos);
previous = current;
-
通过
nextPart()
方法找到下一个/
的位置function nextPart(p, i) {
return p.indexOf("/", i);
}
5. 找到当前值
if (result === -1) {
const last = p.slice(pos);
current += last;
base = previous + last;
pos = p.length;
} else {
current += p.slice(pos, result + 1);
base = previous + p.slice(pos, result);
pos = result + 1;
}
- 如果 下一个
\
所在位置不为-1- 获取当前值
- 更新 base 和 pos,用于下一次查找
6. 判断是否存在于缓存中
// Continue if not a symlink, break if a pipe/socket
if (knownHard[base] || (cache && cache.get(base) === base)) {
if (isFileType(statValues, S_IFIFO) || isFileType(statValues, S_IFSOCK)) {
break;
}
continue;
}
- 如果在缓存中存在 base 的话,跳过执行
7. 判断是否为软链接
let resolvedLink;
const maybeCachedResolved = cache && cache.get(base);
if (maybeCachedResolved) {
resolvedLink = maybeCachedResolved;
} else {
// Use stats array directly to avoid creating an fs.Stats instance just
// for our internal use.
const baseLong = pathModule.toNamespacedPath(base);
const ctx = { path: base };
const stats = binding.lstat(baseLong, true, undefined, ctx);
handleErrorFromBinding(ctx);
// !!! 判断是否为软链接
if (!isFileType(stats, S_IFLNK)) {
knownHard[base] = true;
if (cache) cache.set(base, base);
continue;
}
// Read the link if it wasn't read before
// dev/ino always return 0 on windows, so skip the check.
let linkTarget = null;
let id;
if (!isWindows) {
const dev = stats[0].toString(32);
const ino = stats[7].toString(32);
id = `${dev}:${ino}`;
if (seenLinks[id]) {
linkTarget = seenLinks[id];
}
}
if (linkTarget === null) {
const ctx = { path: base };
binding.stat(baseLong, false, undefined, ctx);
handleErrorFromBinding(ctx);
linkTarget = binding.readlink(baseLong, undefined, undefined, ctx);
handleErrorFromBinding(ctx);
}
resolvedLink = pathModule.resolve(previous, linkTarget);
if (cache) cache.set(base, resolvedLink);
if (!isWindows) seenLinks[id] = linkTarget;
}
-
从缓存中获取 base
-
如果缓存中没拿到
- 处理参数
-
判断是否为软链接
function isFileType(stats, fileType) {
return (stats[1 /* mode */] & S_IFMT) === fileType;
}运用操作系统相关的内容进行判断
- 如果不是软链接的话,将 base 放入已经判断过的 knownHard 中进行存储,表示已知
- 如果不是软链接,执行跳过
-
判断 linkTarget 的值
-
使用 binding.readlink 将软链接读取为相对路径
-
通过 pathModule.resolve 将相对路径转为绝对路径,至此,我们就获取到了真实路径
-
将真实路径放入 cache 中,再次进行循环,确保真实路径中不存在任何软链接
-
lstat 是一个命令,可以打印出在操作系统下跟文件相关的各种信息