Loading...
--- /dev/null
+++ dyld/dyld-1335/cache_builder/BuilderFileSystem.cpp
@@ -0,0 +1,411 @@
+/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*-
+*
+* Copyright (c) 2017 Apple Inc. All rights reserved.
+*
+* @APPLE_LICENSE_HEADER_START@
+*
+* This file contains Original Code and/or Modifications of Original Code
+* as defined in and that are subject to the Apple Public Source License
+* Version 2.0 (the 'License'). You may not use this file except in
+* compliance with the License. Please obtain a copy of the License at
+* http://www.opensource.apple.com/apsl/ and read it before using this
+* file.
+*
+* The Original Code and all software distributed under the License are
+* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+* Please see the License for the specific language governing rights and
+* limitations under the License.
+*
+* @APPLE_LICENSE_HEADER_END@
+*/
+
+#include "BuilderFileSystem.h"
+#include "BuilderOptions.h"
+
+using namespace cache_builder;
+
+//
+// MARK: --- SymlinkResolver methods ---
+//
+
+static void normalizePath(std::string& path) {
+ // Remove a bunch of stuff we don't need, like trailing slashes.
+ while ( !path.empty() && (path.back() == '/'))
+ path.pop_back();
+}
+
+void SymlinkResolver::addFile(Diagnostics& diags, std::string path) {
+ if (path.front() != '/') {
+ diags.error("Path must start with '/'");
+ return;
+ }
+ if (symlinks.find(path) != symlinks.end()) {
+ diags.error("Cannot add regular file as it is already a symlink");
+ return;
+ }
+ filePaths.insert(path);
+}
+
+void SymlinkResolver::addSymlink(Diagnostics& diags, std::string fromPath, std::string toPath) {
+ normalizePath(fromPath);
+ normalizePath(toPath);
+ if (fromPath.front() != '/') {
+ diags.error("Path must start with '/'");
+ return;
+ }
+ if (filePaths.find(fromPath) != filePaths.end()) {
+ diags.error("Cannot add symlink from '%s' as it is already a regular path", fromPath.c_str());
+ return;
+ }
+ auto itAndInserted = symlinks.insert({ fromPath, toPath });
+ if (!itAndInserted.second) {
+ // The path is already a symlink. Make sure its a dupe.
+ if (toPath != itAndInserted.first->second) {
+ diags.error("Duplicate symlink for path '%s'", fromPath.c_str());
+ return;
+ }
+ }
+}
+
+std::string SymlinkResolver::realPath(Diagnostics& diags, const std::string& originalPath,
+ void (^callback)(const std::string& intermediateSymlink)) const {
+ // First make sure the path doesn't have any magic in it.
+ std::string path = originalPath;
+ normalizePath(path);
+
+ std::set<std::string> seenSymlinks;
+
+ // Now see if any prefix is a symlink
+ if (path.front() != '/')
+ return path;
+
+ std::string::size_type prev_pos = 0;
+ while (prev_pos != std::string::npos) {
+ std::string::size_type pos = path.find("/", prev_pos + 1);
+
+ // First look to see if this path component is special, eg, ., .., etc.
+ std::string component = path.substr(prev_pos, pos - prev_pos);
+ if (component == "/..") {
+ // Fold with the previous path component.
+ if (prev_pos == 0) {
+ // This is the root path, and .. applied to / is just /
+ path = path.substr(3);
+ prev_pos = 0;
+ } else {
+ std::string::size_type lastSlashPos = path.rfind("/", prev_pos - 1);
+ path = path.substr(0, lastSlashPos) + path.substr(pos);
+ prev_pos = lastSlashPos;
+ }
+ continue;
+ } else if (component == "/.") {
+ if (prev_pos == 0) {
+ // Path starts with /./ so just remove the first one.
+ path = path.substr(2);
+ } else {
+ if (pos == std::string::npos) {
+ // Trailing . on the path
+ path = path.substr(0, prev_pos );
+ } else {
+ path = path.substr(0, prev_pos) + path.substr(pos);
+ }
+ }
+ continue;
+ } else if (component == "/") {
+ // Path must contain // somewhere so strip out the duplicates.
+ if (prev_pos == 0) {
+ // Path starts with // so just remove the first one.
+ path = path.substr(1);
+ } else {
+ if (pos == std::string::npos) {
+ // Trailing / on the path
+ path = path.substr(0, prev_pos);
+ prev_pos = pos;
+ } else {
+ path = path.substr(0, pos) + path.substr(pos + 1);
+ }
+ }
+ continue;
+ }
+
+ // Path is not special, so see if it is a symlink to something.
+ std::string prefix = path.substr(0, pos);
+ //printf("%s\n", prefix.c_str());
+ auto it = symlinks.find(prefix);
+ if (it == symlinks.end()) {
+ // This is not a symlink so move to the next prefix.
+ prev_pos = pos;
+ continue;
+ }
+
+ // If we've already done this prefix then error out.
+ if (seenSymlinks.count(prefix)) {
+ diags.error("Loop in symlink processing for '%s'", originalPath.c_str());
+ return std::string();
+ }
+
+ seenSymlinks.insert(prefix);
+
+ // This is a symlink, so resolve the new path.
+ std::string toPath = it->second;
+ if (toPath.front() == '/') {
+ // Symlink points to an absolute address so substitute the whole prefix for the new path
+ // If we didn't substitute the last component of the path then there is also a path suffix.
+ std::string pathSuffix = "";
+ if (pos != std::string::npos) {
+ std::string::size_type nextSlashPos = path.find("/", pos + 1);
+ if (nextSlashPos != std::string::npos)
+ pathSuffix = path.substr(nextSlashPos);
+ }
+ path = toPath + pathSuffix;
+ prev_pos = 0;
+ continue;
+ }
+
+ // Symlink points to a relative path so we need to do more processing to get the real path.
+
+ // First calculate which part of the previous prefix we'll keep. Eg, in /a/b/c where "b -> blah", we want to keep /a here.
+ std::string prevPrefix = path.substr(0, prev_pos);
+ //printf("prevPrefix %s\n", prevPrefix.c_str());
+
+ // If we didn't substitute the last component of the path then there is also a path suffix.
+ std::string pathSuffix = "";
+ if (prefix.size() != path.size())
+ pathSuffix = path.substr(pos);
+
+ // The new path is the remaining prefix, plus the symlink target, plus any remaining suffix from the original path.
+ path = prevPrefix + "/" + toPath + pathSuffix;
+ prev_pos = 0;
+ }
+
+ // Notify the caller if we found any intermediate symlinks
+ if ( callback != nullptr ) {
+ for ( const std::string& symlink : seenSymlinks ) {
+ if ( symlink == originalPath )
+ continue;
+ // The intermediate symlink is hopefully a prefix on the final path
+ // If so, then chop it up to get the symlink we could follow to the final path
+
+ Diagnostics symlinkDiag;
+ std::string resolvedSymlink = this->realPath(symlinkDiag, symlink);
+ if ( symlinkDiag.hasError() )
+ continue;
+
+ if ( !path.starts_with(resolvedSymlink) )
+ continue;
+
+ // Now substitute the start of the path for the symlink
+ std::string suffix = path.substr(resolvedSymlink.size());
+ resolvedSymlink = symlink + suffix;
+
+ // One last sanity check that we really are a valid symlink
+ if ( this->realPath(symlinkDiag, resolvedSymlink).empty() )
+ continue;
+ if ( symlinkDiag.hasError() )
+ continue;
+
+ callback(resolvedSymlink);
+ }
+ }
+ return path;
+}
+
+std::vector<cache_builder::FileAlias> SymlinkResolver::getResolvedSymlinks(void (^callback)(const std::string& error)) const
+{
+ std::vector<cache_builder::FileAlias> aliases;
+ for (auto& fromPathAndToPath : symlinks) {
+ Diagnostics diags;
+ std::string newPath = realPath(diags, fromPathAndToPath.first);
+ if (diags.hasError()) {
+ callback(diags.errorMessage());
+ continue;
+ }
+
+ if (filePaths.count(newPath)) {
+ aliases.push_back({ newPath, fromPathAndToPath.first });
+ // printf("symlink ('%s' -> '%s') resolved to '%s'\n", fromPathAndToPath.first.c_str(), fromPathAndToPath.second.c_str(), newPath.c_str());
+ }
+ }
+ return aliases;
+}
+
+std::vector<cache_builder::FileAlias> SymlinkResolver::getIntermediateSymlinks() const
+{
+ auto unusedCallback = ^(const std::string& error) { };
+ std::vector<cache_builder::FileAlias> aliases = this->getResolvedSymlinks(unusedCallback);
+
+ std::vector<cache_builder::FileAlias> intermediateAliases;
+ for ( const cache_builder::FileAlias& alias : aliases ) {
+ Diagnostics diag;
+
+ __block std::vector<std::string> seenSymlinks;
+ auto callback = ^(const std::string& intermediateSymlink) {
+ seenSymlinks.push_back(intermediateSymlink);
+ };
+ std::string realPath = this->realPath(diag, alias.aliasPath, callback);
+
+ for ( const std::string& intermediateSymlink : seenSymlinks ) {
+ intermediateAliases.push_back({ alias.realPath, intermediateSymlink });
+ }
+ }
+
+ return intermediateAliases;
+}
+
+//
+// MARK: --- FileSystemMRM methods ---
+//
+
+bool FileSystemMRM::getRealPath(const char possiblePath[MAXPATHLEN], char realPath[MAXPATHLEN]) const
+{
+ Diagnostics diag;
+ std::string resolvedPath = symlinkResolver.realPath(diag, possiblePath);
+ if (diag.hasError()) {
+ diag.verbose("MRM error: %s\n", diag.errorMessage().c_str());
+ diag.clearError();
+ return false;
+ }
+
+ // FIXME: Should we only return real paths of files which point to macho's? For now that is what we are doing
+ auto it = fileMap.find(resolvedPath);
+ if (it == fileMap.end())
+ return false;
+
+ memcpy(realPath, resolvedPath.c_str(), std::min((size_t)MAXPATHLEN, resolvedPath.size() + 1));
+ return true;
+}
+
+bool FileSystemMRM::loadFile(const char* path, dyld3::closure::LoadedFileInfo& info,
+ char realerPath[MAXPATHLEN], void (^error)(const char* format, ...)) const
+{
+ Diagnostics diag;
+ std::string resolvedPath = symlinkResolver.realPath(diag, path);
+ if (diag.hasError()) {
+ diag.verbose("MRM error: %s\n", diag.errorMessage().c_str());
+ diag.clearError();
+ return false;
+ }
+
+ auto it = fileMap.find(resolvedPath);
+ if (it == fileMap.end())
+ return false;
+
+ if (resolvedPath == path)
+ realerPath[0] = '\0';
+ else
+ memcpy(realerPath, resolvedPath.c_str(), std::min((size_t)MAXPATHLEN, resolvedPath.size() + 1));
+
+ // The file exists at this exact path. Lets use it!
+ const FileInfo& fileInfo = files[it->second];
+
+ info.fileContent = fileInfo.data;
+ info.fileContentLen = fileInfo.length;
+ info.sliceOffset = 0;
+ info.sliceLen = fileInfo.length;
+ info.isOSBinary = true;
+ info.inode = fileInfo.inode;
+ info.mtime = fileInfo.mtime;
+ info.unload = nullptr;
+ info.path = path;
+ return true;
+ }
+
+void FileSystemMRM::unloadFile(const dyld3::closure::LoadedFileInfo& info) const
+{
+ if (info.unload)
+ info.unload(info);
+}
+
+void FileSystemMRM::unloadPartialFile(dyld3::closure::LoadedFileInfo& info,
+ uint64_t keepStartOffset, uint64_t keepLength) const
+{
+ // Note we don't actually unload the data here, but we do want to update the offsets for other data structures to track where we are
+ info.fileContent = (const void*)((char*)info.fileContent + keepStartOffset);
+ info.fileContentLen = keepLength;
+}
+
+bool FileSystemMRM::fileExists(const char* path, uint64_t* inode, uint64_t* mtime,
+ bool* issetuid, bool* inodesMatchRuntime) const
+{
+ Diagnostics diag;
+ std::string resolvedPath = symlinkResolver.realPath(diag, path);
+ if (diag.hasError()) {
+ diag.verbose("MRM error: %s\n", diag.errorMessage().c_str());
+ diag.clearError();
+ return false;
+ }
+
+ auto it = fileMap.find(resolvedPath);
+ if (it == fileMap.end())
+ return false;
+
+ // The file exists at this exact path. Lets use it!
+ const FileInfo& fileInfo = files[it->second];
+ if (inode)
+ *inode = fileInfo.inode;
+ if (mtime)
+ *mtime = fileInfo.mtime;
+ if (issetuid)
+ *issetuid = false;
+ if (inodesMatchRuntime)
+ *inodesMatchRuntime = false;
+ return true;
+}
+
+bool FileSystemMRM::addFile(const char* path, uint8_t* data, uint64_t size, Diagnostics& diag,
+ FileFlags fileFlags, uint64_t inode, uint64_t modTime,
+ const char* projectName) {
+ auto iteratorAndInserted = fileMap.insert(std::make_pair(path, files.size()));
+ if (!iteratorAndInserted.second) {
+ diag.error("Already have content for path: '%s'", path);
+ return false;
+ }
+
+ symlinkResolver.addFile(diag, path);
+ if (diag.hasError())
+ return false;
+
+ if ( (inode == 0) && (modTime == 0) ) {
+ // on platforms where MRM builds the cache, inode is just a placeholder
+ // Note its safe to just use the index here as we only compare it during closure building
+ // and never record it in the closures
+ inode = files.size() + 1;
+ modTime = 0;
+ }
+
+ files.push_back((FileInfo){ path, data, size, fileFlags, modTime, inode, projectName });
+ return true;
+}
+
+bool FileSystemMRM::addSymlink(const char* fromPath, const char* toPath, Diagnostics& diag)
+{
+ symlinkResolver.addSymlink(diag, fromPath, toPath);
+ return !diag.hasError();
+}
+
+void FileSystemMRM::forEachFileInfo(std::function<void(const char* path, const void* buffer, size_t bufferSize,
+ FileFlags fileFlags, uint64_t inode, uint64_t modTime,
+ const char* projectName)> lambda)
+{
+ for (const FileInfo& fileInfo : files)
+ lambda(fileInfo.path.c_str(), fileInfo.data, fileInfo.length, fileInfo.flags,
+ fileInfo.inode, fileInfo.mtime, fileInfo.projectName.c_str());
+}
+
+size_t FileSystemMRM::fileCount() const
+{
+ return files.size();
+}
+
+std::vector<cache_builder::FileAlias> FileSystemMRM::getResolvedSymlinks(void (^callback)(const std::string& error)) const
+{
+ return symlinkResolver.getResolvedSymlinks(callback);
+}
+
+std::vector<cache_builder::FileAlias> FileSystemMRM::getIntermediateSymlinks() const
+{
+ return symlinkResolver.getIntermediateSymlinks();
+}
+