diff options
Diffstat (limited to 'third_party/nix/src/libutil/archive.cc')
-rw-r--r-- | third_party/nix/src/libutil/archive.cc | 556 |
1 files changed, 267 insertions, 289 deletions
diff --git a/third_party/nix/src/libutil/archive.cc b/third_party/nix/src/libutil/archive.cc index 3aa120270970..9ae5c76db751 100644 --- a/third_party/nix/src/libutil/archive.cc +++ b/third_party/nix/src/libutil/archive.cc @@ -1,32 +1,31 @@ -#include <cerrno> -#include <algorithm> -#include <vector> -#include <map> - -#include <strings.h> // for strcasecmp - -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> +#include "archive.hh" #include <dirent.h> #include <fcntl.h> - -#include "archive.hh" -#include "util.hh" +#include <strings.h> // for strcasecmp +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <algorithm> +#include <cerrno> +#include <map> +#include <vector> #include "config.hh" +#include "util.hh" namespace nix { -struct ArchiveSettings : Config -{ - Setting<bool> useCaseHack{this, - #if __APPLE__ - true, - #else - false, - #endif +struct ArchiveSettings : Config { + Setting<bool> useCaseHack { + this, +#if __APPLE__ + true, +#else + false, +#endif "use-case-hack", - "Whether to enable a Darwin-specific hack for dealing with file name collisions."}; + "Whether to enable a Darwin-specific hack for dealing with file name " + "collisions." + }; }; static ArchiveSettings archiveSettings; @@ -37,105 +36,105 @@ const std::string narVersionMagic1 = "nix-archive-1"; static string caseHackSuffix = "~nix~case~hack~"; -PathFilter defaultPathFilter = [](const Path &) { return true; }; - +PathFilter defaultPathFilter = [](const Path&) { return true; }; -static void dumpContents(const Path & path, size_t size, - Sink & sink) -{ - sink << "contents" << size; +static void dumpContents(const Path& path, size_t size, Sink& sink) { + sink << "contents" << size; - AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC); - if (!fd) throw SysError(format("opening file '%1%'") % path); + AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC); + if (!fd) throw SysError(format("opening file '%1%'") % path); - std::vector<unsigned char> buf(65536); - size_t left = size; + std::vector<unsigned char> buf(65536); + size_t left = size; - while (left > 0) { - auto n = std::min(left, buf.size()); - readFull(fd.get(), buf.data(), n); - left -= n; - sink(buf.data(), n); - } + while (left > 0) { + auto n = std::min(left, buf.size()); + readFull(fd.get(), buf.data(), n); + left -= n; + sink(buf.data(), n); + } - writePadding(size, sink); + writePadding(size, sink); } - -static void dump(const Path & path, Sink & sink, PathFilter & filter) -{ - checkInterrupt(); - - struct stat st; - if (lstat(path.c_str(), &st)) - throw SysError(format("getting attributes of path '%1%'") % path); - - sink << "("; - - if (S_ISREG(st.st_mode)) { - sink << "type" << "regular"; - if (st.st_mode & S_IXUSR) - sink << "executable" << ""; - dumpContents(path, (size_t) st.st_size, sink); - } - - else if (S_ISDIR(st.st_mode)) { - sink << "type" << "directory"; - - /* If we're on a case-insensitive system like macOS, undo - the case hack applied by restorePath(). */ - std::map<string, string> unhacked; - for (auto & i : readDirectory(path)) - if (archiveSettings.useCaseHack) { - string name(i.name); - size_t pos = i.name.find(caseHackSuffix); - if (pos != string::npos) { - debug(format("removing case hack suffix from '%1%'") % (path + "/" + i.name)); - name.erase(pos); - } - if (unhacked.find(name) != unhacked.end()) - throw Error(format("file name collision in between '%1%' and '%2%'") - % (path + "/" + unhacked[name]) % (path + "/" + i.name)); - unhacked[name] = i.name; - } else - unhacked[i.name] = i.name; - - for (auto & i : unhacked) - if (filter(path + "/" + i.first)) { - sink << "entry" << "(" << "name" << i.first << "node"; - dump(path + "/" + i.second, sink, filter); - sink << ")"; - } - } - - else if (S_ISLNK(st.st_mode)) - sink << "type" << "symlink" << "target" << readLink(path); - - else throw Error(format("file '%1%' has an unsupported type") % path); - - sink << ")"; +static void dump(const Path& path, Sink& sink, PathFilter& filter) { + checkInterrupt(); + + struct stat st; + if (lstat(path.c_str(), &st)) + throw SysError(format("getting attributes of path '%1%'") % path); + + sink << "("; + + if (S_ISREG(st.st_mode)) { + sink << "type" + << "regular"; + if (st.st_mode & S_IXUSR) + sink << "executable" + << ""; + dumpContents(path, (size_t)st.st_size, sink); + } + + else if (S_ISDIR(st.st_mode)) { + sink << "type" + << "directory"; + + /* If we're on a case-insensitive system like macOS, undo + the case hack applied by restorePath(). */ + std::map<string, string> unhacked; + for (auto& i : readDirectory(path)) + if (archiveSettings.useCaseHack) { + string name(i.name); + size_t pos = i.name.find(caseHackSuffix); + if (pos != string::npos) { + debug(format("removing case hack suffix from '%1%'") % + (path + "/" + i.name)); + name.erase(pos); + } + if (unhacked.find(name) != unhacked.end()) + throw Error(format("file name collision in between '%1%' and '%2%'") % + (path + "/" + unhacked[name]) % (path + "/" + i.name)); + unhacked[name] = i.name; + } else + unhacked[i.name] = i.name; + + for (auto& i : unhacked) + if (filter(path + "/" + i.first)) { + sink << "entry" + << "(" + << "name" << i.first << "node"; + dump(path + "/" + i.second, sink, filter); + sink << ")"; + } + } + + else if (S_ISLNK(st.st_mode)) + sink << "type" + << "symlink" + << "target" << readLink(path); + + else + throw Error(format("file '%1%' has an unsupported type") % path); + + sink << ")"; } - -void dumpPath(const Path & path, Sink & sink, PathFilter & filter) -{ - sink << narVersionMagic1; - dump(path, sink, filter); +void dumpPath(const Path& path, Sink& sink, PathFilter& filter) { + sink << narVersionMagic1; + dump(path, sink, filter); } - -void dumpString(const std::string & s, Sink & sink) -{ - sink << narVersionMagic1 << "(" << "type" << "regular" << "contents" << s << ")"; +void dumpString(const std::string& s, Sink& sink) { + sink << narVersionMagic1 << "(" + << "type" + << "regular" + << "contents" << s << ")"; } - -static SerialisationError badArchive(string s) -{ - return SerialisationError("bad archive: " + s); +static SerialisationError badArchive(string s) { + return SerialisationError("bad archive: " + s); } - #if 0 static void skipGeneric(Source & source) { @@ -146,233 +145,212 @@ static void skipGeneric(Source & source) } #endif +static void parseContents(ParseSink& sink, Source& source, const Path& path) { + unsigned long long size = readLongLong(source); -static void parseContents(ParseSink & sink, Source & source, const Path & path) -{ - unsigned long long size = readLongLong(source); + sink.preallocateContents(size); - sink.preallocateContents(size); + unsigned long long left = size; + std::vector<unsigned char> buf(65536); - unsigned long long left = size; - std::vector<unsigned char> buf(65536); + while (left) { + checkInterrupt(); + auto n = buf.size(); + if ((unsigned long long)n > left) n = left; + source(buf.data(), n); + sink.receiveContents(buf.data(), n); + left -= n; + } + + readPadding(size, source); +} - while (left) { - checkInterrupt(); - auto n = buf.size(); - if ((unsigned long long)n > left) n = left; - source(buf.data(), n); - sink.receiveContents(buf.data(), n); - left -= n; - } +struct CaseInsensitiveCompare { + bool operator()(const string& a, const string& b) const { + return strcasecmp(a.c_str(), b.c_str()) < 0; + } +}; - readPadding(size, source); -} +static void parse(ParseSink& sink, Source& source, const Path& path) { + string s; + s = readString(source); + if (s != "(") throw badArchive("expected open tag"); -struct CaseInsensitiveCompare -{ - bool operator() (const string & a, const string & b) const - { - return strcasecmp(a.c_str(), b.c_str()) < 0; - } -}; + enum { tpUnknown, tpRegular, tpDirectory, tpSymlink } type = tpUnknown; + std::map<Path, int, CaseInsensitiveCompare> names; -static void parse(ParseSink & sink, Source & source, const Path & path) -{ - string s; + while (1) { + checkInterrupt(); s = readString(source); - if (s != "(") throw badArchive("expected open tag"); - enum { tpUnknown, tpRegular, tpDirectory, tpSymlink } type = tpUnknown; + if (s == ")") { + break; + } - std::map<Path, int, CaseInsensitiveCompare> names; + else if (s == "type") { + if (type != tpUnknown) throw badArchive("multiple type fields"); + string t = readString(source); - while (1) { - checkInterrupt(); + if (t == "regular") { + type = tpRegular; + sink.createRegularFile(path); + } - s = readString(source); + else if (t == "directory") { + sink.createDirectory(path); + type = tpDirectory; + } - if (s == ")") { - break; - } - - else if (s == "type") { - if (type != tpUnknown) - throw badArchive("multiple type fields"); - string t = readString(source); + else if (t == "symlink") { + type = tpSymlink; + } - if (t == "regular") { - type = tpRegular; - sink.createRegularFile(path); - } + else + throw badArchive("unknown file type " + t); - else if (t == "directory") { - sink.createDirectory(path); - type = tpDirectory; - } + } - else if (t == "symlink") { - type = tpSymlink; - } + else if (s == "contents" && type == tpRegular) { + parseContents(sink, source, path); + } - else throw badArchive("unknown file type " + t); + else if (s == "executable" && type == tpRegular) { + auto s = readString(source); + if (s != "") throw badArchive("executable marker has non-empty value"); + sink.isExecutable(); + } - } + else if (s == "entry" && type == tpDirectory) { + string name, prevName; - else if (s == "contents" && type == tpRegular) { - parseContents(sink, source, path); - } + s = readString(source); + if (s != "(") throw badArchive("expected open tag"); - else if (s == "executable" && type == tpRegular) { - auto s = readString(source); - if (s != "") throw badArchive("executable marker has non-empty value"); - sink.isExecutable(); - } + while (1) { + checkInterrupt(); - else if (s == "entry" && type == tpDirectory) { - string name, prevName; - - s = readString(source); - if (s != "(") throw badArchive("expected open tag"); - - while (1) { - checkInterrupt(); - - s = readString(source); - - if (s == ")") { - break; - } else if (s == "name") { - name = readString(source); - if (name.empty() || name == "." || name == ".." || name.find('/') != string::npos || name.find((char) 0) != string::npos) - throw Error(format("NAR contains invalid file name '%1%'") % name); - if (name <= prevName) - throw Error("NAR directory is not sorted"); - prevName = name; - if (archiveSettings.useCaseHack) { - auto i = names.find(name); - if (i != names.end()) { - debug(format("case collision between '%1%' and '%2%'") % i->first % name); - name += caseHackSuffix; - name += std::to_string(++i->second); - } else - names[name] = 0; - } - } else if (s == "node") { - if (s.empty()) throw badArchive("entry name missing"); - parse(sink, source, path + "/" + name); - } else - throw badArchive("unknown field " + s); - } - } + s = readString(source); - else if (s == "target" && type == tpSymlink) { - string target = readString(source); - sink.createSymlink(path, target); - } + if (s == ")") { + break; + } else if (s == "name") { + name = readString(source); + if (name.empty() || name == "." || name == ".." || + name.find('/') != string::npos || + name.find((char)0) != string::npos) + throw Error(format("NAR contains invalid file name '%1%'") % name); + if (name <= prevName) throw Error("NAR directory is not sorted"); + prevName = name; + if (archiveSettings.useCaseHack) { + auto i = names.find(name); + if (i != names.end()) { + debug(format("case collision between '%1%' and '%2%'") % + i->first % name); + name += caseHackSuffix; + name += std::to_string(++i->second); + } else + names[name] = 0; + } + } else if (s == "node") { + if (s.empty()) throw badArchive("entry name missing"); + parse(sink, source, path + "/" + name); + } else + throw badArchive("unknown field " + s); + } + } - else - throw badArchive("unknown field " + s); + else if (s == "target" && type == tpSymlink) { + string target = readString(source); + sink.createSymlink(path, target); } -} + else + throw badArchive("unknown field " + s); + } +} -void parseDump(ParseSink & sink, Source & source) -{ - string version; - try { - version = readString(source, narVersionMagic1.size()); - } catch (SerialisationError & e) { - /* This generally means the integer at the start couldn't be - decoded. Ignore and throw the exception below. */ - } - if (version != narVersionMagic1) - throw badArchive("input doesn't look like a Nix archive"); - parse(sink, source, ""); +void parseDump(ParseSink& sink, Source& source) { + string version; + try { + version = readString(source, narVersionMagic1.size()); + } catch (SerialisationError& e) { + /* This generally means the integer at the start couldn't be + decoded. Ignore and throw the exception below. */ + } + if (version != narVersionMagic1) + throw badArchive("input doesn't look like a Nix archive"); + parse(sink, source, ""); } +struct RestoreSink : ParseSink { + Path dstPath; + AutoCloseFD fd; -struct RestoreSink : ParseSink -{ - Path dstPath; - AutoCloseFD fd; - - void createDirectory(const Path & path) - { - Path p = dstPath + path; - if (mkdir(p.c_str(), 0777) == -1) - throw SysError(format("creating directory '%1%'") % p); - }; - - void createRegularFile(const Path & path) - { - Path p = dstPath + path; - fd = open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666); - if (!fd) throw SysError(format("creating file '%1%'") % p); - } + void createDirectory(const Path& path) { + Path p = dstPath + path; + if (mkdir(p.c_str(), 0777) == -1) + throw SysError(format("creating directory '%1%'") % p); + }; - void isExecutable() - { - struct stat st; - if (fstat(fd.get(), &st) == -1) - throw SysError("fstat"); - if (fchmod(fd.get(), st.st_mode | (S_IXUSR | S_IXGRP | S_IXOTH)) == -1) - throw SysError("fchmod"); - } + void createRegularFile(const Path& path) { + Path p = dstPath + path; + fd = open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666); + if (!fd) throw SysError(format("creating file '%1%'") % p); + } + + void isExecutable() { + struct stat st; + if (fstat(fd.get(), &st) == -1) throw SysError("fstat"); + if (fchmod(fd.get(), st.st_mode | (S_IXUSR | S_IXGRP | S_IXOTH)) == -1) + throw SysError("fchmod"); + } - void preallocateContents(unsigned long long len) - { + void preallocateContents(unsigned long long len) { #if HAVE_POSIX_FALLOCATE - if (len) { - errno = posix_fallocate(fd.get(), 0, len); - /* Note that EINVAL may indicate that the underlying - filesystem doesn't support preallocation (e.g. on - OpenSolaris). Since preallocation is just an - optimisation, ignore it. */ - if (errno && errno != EINVAL && errno != EOPNOTSUPP && errno != ENOSYS) - throw SysError(format("preallocating file of %1% bytes") % len); - } -#endif + if (len) { + errno = posix_fallocate(fd.get(), 0, len); + /* Note that EINVAL may indicate that the underlying + filesystem doesn't support preallocation (e.g. on + OpenSolaris). Since preallocation is just an + optimisation, ignore it. */ + if (errno && errno != EINVAL && errno != EOPNOTSUPP && errno != ENOSYS) + throw SysError(format("preallocating file of %1% bytes") % len); } +#endif + } - void receiveContents(unsigned char * data, unsigned int len) - { - writeFull(fd.get(), data, len); - } + void receiveContents(unsigned char* data, unsigned int len) { + writeFull(fd.get(), data, len); + } - void createSymlink(const Path & path, const string & target) - { - Path p = dstPath + path; - nix::createSymlink(target, p); - } + void createSymlink(const Path& path, const string& target) { + Path p = dstPath + path; + nix::createSymlink(target, p); + } }; - -void restorePath(const Path & path, Source & source) -{ - RestoreSink sink; - sink.dstPath = path; - parseDump(sink, source); +void restorePath(const Path& path, Source& source) { + RestoreSink sink; + sink.dstPath = path; + parseDump(sink, source); } +void copyNAR(Source& source, Sink& sink) { + // FIXME: if 'source' is the output of dumpPath() followed by EOF, + // we should just forward all data directly without parsing. -void copyNAR(Source & source, Sink & sink) -{ - // FIXME: if 'source' is the output of dumpPath() followed by EOF, - // we should just forward all data directly without parsing. - - ParseSink parseSink; /* null sink; just parse the NAR */ + ParseSink parseSink; /* null sink; just parse the NAR */ - LambdaSource wrapper([&](unsigned char * data, size_t len) { - auto n = source.read(data, len); - sink(data, n); - return n; - }); + LambdaSource wrapper([&](unsigned char* data, size_t len) { + auto n = source.read(data, len); + sink(data, n); + return n; + }); - parseDump(parseSink, wrapper); + parseDump(parseSink, wrapper); } - -} +} // namespace nix |