跳到主要内容

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 是一个命令,可以打印出在操作系统下跟文件相关的各种信息