From 09755e696a04e16d9f51f48a529e1b4881af368c Mon Sep 17 00:00:00 2001 From: Sergei Zimmerman Date: Thu, 20 Nov 2025 01:27:18 +0300 Subject: [PATCH] libutil: Add callback-based FileSystemObjectSink::createDirectory --- src/libutil/archive.cc | 78 ++++++++++++------------- src/libutil/fs-sink.cc | 11 ++-- src/libutil/include/nix/util/fs-sink.hh | 17 ++++++ 3 files changed, 62 insertions(+), 44 deletions(-) diff --git a/src/libutil/archive.cc b/src/libutil/archive.cc index 3b5b610db..0291d6827 100644 --- a/src/libutil/archive.cc +++ b/src/libutil/archive.cc @@ -200,54 +200,54 @@ static void parse(FileSystemObjectSink & sink, Source & source, const CanonPath } else if (type == "directory") { - sink.createDirectory(path); + sink.createDirectory(path, [&](FileSystemObjectSink & dirSink, const CanonPath & relDirPath) { + std::map names; - std::map names; + std::string prevName; - std::string prevName; + while (1) { + auto tag = getString(); - while (1) { - auto tag = getString(); + if (tag == ")") + break; - if (tag == ")") - break; + if (tag != "entry") + throw badArchive("expected tag 'entry' or ')', got '%s'", tag); - if (tag != "entry") - throw badArchive("expected tag 'entry' or ')', got '%s'", tag); + expectTag("("); - expectTag("("); + expectTag("name"); - expectTag("name"); + auto name = getString(); + if (name.empty() || name == "." || name == ".." || name.find('/') != std::string::npos + || name.find((char) 0) != std::string::npos) + throw badArchive("NAR contains invalid file name '%1%'", name); + if (name <= prevName) + throw badArchive("NAR directory is not sorted"); + prevName = name; + if (archiveSettings.useCaseHack) { + auto i = names.find(name); + if (i != names.end()) { + debug("case collision between '%1%' and '%2%'", i->first, name); + name += caseHackSuffix; + name += std::to_string(++i->second); + auto j = names.find(name); + if (j != names.end()) + throw badArchive( + "NAR contains file name '%s' that collides with case-hacked file name '%s'", + prevName, + j->first); + } else + names[name] = 0; + } - auto name = getString(); - if (name.empty() || name == "." || name == ".." || name.find('/') != std::string::npos - || name.find((char) 0) != std::string::npos) - throw badArchive("NAR contains invalid file name '%1%'", name); - if (name <= prevName) - throw badArchive("NAR directory is not sorted"); - prevName = name; - if (archiveSettings.useCaseHack) { - auto i = names.find(name); - if (i != names.end()) { - debug("case collision between '%1%' and '%2%'", i->first, name); - name += caseHackSuffix; - name += std::to_string(++i->second); - auto j = names.find(name); - if (j != names.end()) - throw badArchive( - "NAR contains file name '%s' that collides with case-hacked file name '%s'", - prevName, - j->first); - } else - names[name] = 0; + expectTag("node"); + + parse(dirSink, source, relDirPath / name); + + expectTag(")"); } - - expectTag("node"); - - parse(sink, source, path / name); - - expectTag(")"); - } + }); } else if (type == "symlink") { diff --git a/src/libutil/fs-sink.cc b/src/libutil/fs-sink.cc index 5b5f73067..9058d6e00 100644 --- a/src/libutil/fs-sink.cc +++ b/src/libutil/fs-sink.cc @@ -34,11 +34,12 @@ void copyRecursive(SourceAccessor & accessor, const CanonPath & from, FileSystem } case SourceAccessor::tDirectory: { - sink.createDirectory(to); - for (auto & [name, _] : accessor.readDirectory(from)) { - copyRecursive(accessor, from / name, sink, to / name); - break; - } + sink.createDirectory(to, [&](FileSystemObjectSink & dirSink, const CanonPath & relDirPath) { + for (auto & [name, _] : accessor.readDirectory(from)) { + copyRecursive(accessor, from / name, dirSink, relDirPath / name); + break; + } + }); break; } diff --git a/src/libutil/include/nix/util/fs-sink.hh b/src/libutil/include/nix/util/fs-sink.hh index 132658b5d..b2b009b83 100644 --- a/src/libutil/include/nix/util/fs-sink.hh +++ b/src/libutil/include/nix/util/fs-sink.hh @@ -36,6 +36,23 @@ struct FileSystemObjectSink virtual void createDirectory(const CanonPath & path) = 0; + using DirectoryCreatedCallback = std::function; + + /** + * Create a directory and invoke a callback with a pair of sink + CanonPath + * of the created subdirectory relative to dirSink. + * + * @note This allows for UNIX RestoreSink implementations to implement + * *at-style accessors that always keep an open file descriptor for the + * freshly created directory. Use this when it's important to disallow any + * intermediate path components from being symlinks. + */ + virtual void createDirectory(const CanonPath & path, DirectoryCreatedCallback callback) + { + createDirectory(path); + callback(*this, path); + } + /** * This function in general is no re-entrant. Only one file can be * written at a time.