Use absolute paths for external symlinks in Builder#copyDirectory.

Thanks to @worldsayshi for reporting this issue with a reproduction.

Fixes #8005.
Fixes #2876.
Fixes #7154.
This commit is contained in:
Ben Newman
2016-11-04 16:34:51 -04:00
parent 2e00dc3663
commit af51b8160c

View File

@@ -1,3 +1,4 @@
import assert from "assert";
import {WatchSet, readAndWatchFile, sha1} from '../fs/watch.js';
import files from '../fs/files.js';
import NpmDiscards from './npm-discards.js';
@@ -65,6 +66,8 @@ export default class Builder {
this.writtenHashes = {};
this.previousWrittenHashes = {};
this._realpathCache = Object.create(null);
// foo/bar => foo/.build1234.bar
// Should we include a random number? The advantage is that multiple
// builds can run in parallel. The disadvantage is that stale build
@@ -443,7 +446,11 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}`
});
}
let walk = (absFrom, relTo) => {
const walk = (
absFrom,
relTo,
_currentRealRootDir = absFrom
) => {
if (symlink && ! (relTo in this.usedAsFile)) {
this._ensureDirectory(files.pathDirname(relTo));
const absTo = files.pathResolve(this.buildPath, relTo);
@@ -454,14 +461,55 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}`
this._ensureDirectory(relTo);
optimisticReaddir(absFrom).forEach(item => {
const thisAbsFrom = files.pathResolve(absFrom, item);
let thisAbsFrom = files.pathResolve(absFrom, item);
const thisRelTo = files.pathJoin(relTo, item);
if (specificPaths && !(thisRelTo in specificPaths)) {
return;
}
const fileStatus = optimisticLStat(thisAbsFrom);
// Returns files.realpath(thisAbsFrom), iff it is external to
// _currentRealRootDir, using caching because this function might
// be called more than once.
let cachedExternalPath;
const getExternalPath = () => {
if (typeof cachedExternalPath !== "undefined") {
return cachedExternalPath;
}
const real = files.realpath(thisAbsFrom, this._realpathCache);
const isExternal =
files.pathRelative(_currentRealRootDir, real).startsWith("..");
// Now cachedExternalPath is either a string or false.
return cachedExternalPath = isExternal && real;
};
let fileStatus = optimisticLStat(thisAbsFrom);
if (! symlink && fileStatus.isSymbolicLink()) {
// If copyDirectory is not allowed to create symbolic links to
// external files, and this file is a symbolic link that points
// to an external file, update fileStatus so that we copy this
// file as a normal file rather than as a symbolic link.
const externalPath = getExternalPath();
if (externalPath) {
// Copy from the real path rather than the link path.
thisAbsFrom = externalPath;
// Update fileStatus to match the actual file rather than the
// symbolic link, thus forcing the file to be copied below.
fileStatus = optimisticLStat(externalPath);
if (fileStatus.isDirectory()) {
// Update _currentRealRootDir so that we can judge
// isExternal relative to this new root directory when
// traversing nested directories.
_currentRealRootDir = externalPath;
}
}
}
let itemForMatch = item;
const isDirectory = fileStatus.isDirectory();
@@ -482,12 +530,15 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}`
if (isDirectory) {
if (typeof directoryFilter !== "function" ||
directoryFilter(thisAbsFrom)) {
walk(thisAbsFrom, thisRelTo);
walk(thisAbsFrom, thisRelTo, _currentRealRootDir);
}
} else if (fileStatus.isSymbolicLink()) {
symlinkWithOverwrite(
files.readlink(thisAbsFrom),
// Symbolic links pointing to relative external paths are less
// portable than absolute links, so getExternalPath() is
// preferred if it returns a path.
getExternalPath() || files.readlink(thisAbsFrom),
files.pathResolve(this.buildPath, thisRelTo)
);
@@ -518,7 +569,7 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}`
});
};
walk(from, to);
walk(files.realpath(from, this._realpathCache), to);
}
// Returns a new Builder-compatible object that works just like a