From a5e125330cea51947610f0ba08cb43f944c95360 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 7 Jul 2017 16:45:45 +0200 Subject: [PATCH] generate-programs-index: Check whether symlink targets exist Fixes https://github.com/NixOS/nixpkgs/issues/26661 --- default.nix | 4 +-- file-cache.hh | 27 ++++++++++++----- generate-programs-index.cc | 61 +++++++++++++++++++++++++++++++++----- 3 files changed, 75 insertions(+), 17 deletions(-) diff --git a/default.nix b/default.nix index 68ad559..c93f05a 100644 --- a/default.nix +++ b/default.nix @@ -22,14 +22,14 @@ stdenv.mkDerivation { cp ${./file-cache.hh} file-cache.hh - g++ -g ${./generate-programs-index.cc} -Wall -std=c++14 -o $out/bin/generate-programs-index -I . \ + g++ -Os -g ${./generate-programs-index.cc} -Wall -std=c++14 -o $out/bin/generate-programs-index -I . \ $(pkg-config --cflags nix-main) \ $(pkg-config --libs nix-main) \ $(pkg-config --libs nix-expr) \ $(pkg-config --libs nix-store) \ -lsqlite3 -lgc - g++ -g ${./index-debuginfo.cc} -Wall -std=c++14 -o $out/bin/index-debuginfo -I . \ + g++ -Os -g ${./index-debuginfo.cc} -Wall -std=c++14 -o $out/bin/index-debuginfo -I . \ $(pkg-config --cflags nix-main) \ $(pkg-config --libs nix-main) \ $(pkg-config --libs nix-store) \ diff --git a/file-cache.hh b/file-cache.hh index 8439196..e589d88 100644 --- a/file-cache.hh +++ b/file-cache.hh @@ -23,6 +23,11 @@ class FileCache Sync state_; + struct Stat : FSAccessor::Stat + { + std::string target; + }; + public: FileCache(const Path & path) @@ -42,6 +47,7 @@ public: type integer not null, fileSize integer, isExecutable integer, + target text, primary key (storePath, subPath), foreign key (storePath) references StorePaths(id) on delete cascade ); @@ -60,17 +66,17 @@ public: state->insertPath.create(state->db, "insert or ignore into StorePaths(path) values (?)"); state->queryFiles.create(state->db, - "select subPath, type, fileSize, isExecutable from StorePathContents where storePath = ?"); + "select subPath, type, fileSize, isExecutable, target from StorePathContents where storePath = ?"); state->insertFile.create(state->db, - "insert into StorePathContents(storePath, subPath, type, fileSize, isExecutable) values (?, ?, ?, ?, ?)"); + "insert into StorePathContents(storePath, subPath, type, fileSize, isExecutable, target) values (?, ?, ?, ?, ?, ?)"); } /* Return the files in a store path, using a SQLite database to cache the results. */ - std::map + std::map getFiles(ref binaryCache, const Path & storePath) { - std::map files; + std::map files; /* Look up the path in the SQLite cache. */ { @@ -80,8 +86,13 @@ public: auto id = useQueryPath.getInt(0); auto useQueryFiles(state->queryFiles.use()(id)); while (useQueryFiles.next()) { - files[useQueryFiles.getStr(0)] = FSAccessor::Stat{ - (FSAccessor::Type) useQueryFiles.getInt(1), (uint64_t) useQueryFiles.getInt(2), useQueryFiles.getInt(3) != 0}; + Stat st; + st.type = (FSAccessor::Type) useQueryFiles.getInt(1); + st.fileSize = (uint64_t) useQueryFiles.getInt(2); + st.isExecutable = useQueryFiles.getInt(3) != 0; + if (!useQueryFiles.isNull(4)) + st.target = useQueryFiles.getStr(4); + files.emplace(useQueryFiles.getStr(0), st); } return files; } @@ -92,7 +103,7 @@ public: std::function recurse; recurse = [&](const std::string & relPath, json & v) { - FSAccessor::Stat st; + Stat st; std::string type = v["type"]; @@ -108,6 +119,7 @@ public: st.isExecutable = v.value("executable", false); } else if (type == "symlink") { st.type = FSAccessor::Type::tSymlink; + st.target = v.value("target", ""); } else return; files[relPath] = st; @@ -152,6 +164,7 @@ public: (x.second.type) (x.second.fileSize, x.second.type == FSAccessor::Type::tRegular) (x.second.isExecutable, x.second.type == FSAccessor::Type::tRegular) + (x.second.target, x.second.type == FSAccessor::Type::tSymlink) .exec(); } diff --git a/generate-programs-index.cc b/generate-programs-index.cc index 46c21b0..0498eb0 100644 --- a/generate-programs-index.cc +++ b/generate-programs-index.cc @@ -137,15 +137,60 @@ void mainWrapped(int argc, char * * argv) std::set programs; - for (auto & file : files) { - // FIXME: we assume that symlinks point to - // programs. Should check that. - if (file.second.type == FSAccessor::Type::tDirectory || - (file.second.type == FSAccessor::Type::tRegular && !file.second.isExecutable)) - continue; + for (auto file : files) { + std::smatch match; - if (std::regex_match(file.first, match, isProgram)) - programs.insert(match[1]); + if (!std::regex_match(file.first, match, isProgram)) continue; + + auto curPath = file.first; + auto stat = file.second; + + while (stat.type == FSAccessor::Type::tSymlink) { + + auto target = canonPath( + hasPrefix(stat.target, "/") + ? stat.target + : dirOf(storePath + "/" + curPath) + "/" + stat.target); + // FIXME: resolve symlinks in components of stat.target. + + if (!hasPrefix(target, "/nix/store/")) break; + + /* Assume that symlinks to other store paths point + to executables. But check symlinks within the + same store path. */ + if (target.compare(0, storePath.size(), storePath) != 0) { + stat.type = FSAccessor::Type::tRegular; + stat.isExecutable = true; + break; + } + + std::string sub(target, storePath.size() + 1); + + auto file2 = files.find(sub); + if (file2 == files.end()) { + printError("symlink ‘%s’ has non-existent target ‘%s’", + storePath + "/" + file.first, stat.target); + break; + } + + if (file2->second.type != FSAccessor::Type::tRegular + || !file2->second.isExecutable) + { + printError("symlink ‘%s’ points to non-executable ‘%s’", + storePath + "/" + file.first, stat.target); + break; + } + + curPath = sub; + stat = file2->second; + } + + if (stat.type == FSAccessor::Type::tDirectory + || stat.type == FSAccessor::Type::tSymlink + || (stat.type == FSAccessor::Type::tRegular && !stat.isExecutable)) + continue; + + programs.insert(match[1]); } if (programs.empty()) return;