diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index fe5358962..f5fbd3fa6 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -769,8 +769,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * .nixCode = NixCode { .errPos = posDrvName } }); - HashType ht = outputHashAlgo.empty() ? htUnknown : parseHashType(outputHashAlgo); - + std::optional ht = parseHashTypeOpt(outputHashAlgo); Hash h = newHashAllowEmpty(*outputHash, ht); auto outPath = state.store->makeFixedOutputPath(ingestionMethod, h, drvName); @@ -1006,8 +1005,8 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va static void prim_hashFile(EvalState & state, const Pos & pos, Value * * args, Value & v) { string type = state.forceStringNoCtx(*args[0], pos); - HashType ht = parseHashType(type); - if (ht == htUnknown) + std::optional ht = parseHashType(type); + if (!ht) throw Error({ .hint = hintfmt("unknown hash type '%1%'", type), .nixCode = NixCode { .errPos = pos } @@ -1016,7 +1015,7 @@ static void prim_hashFile(EvalState & state, const Pos & pos, Value * * args, Va PathSet context; // discarded Path p = state.coerceToPath(pos, *args[1], context); - mkString(v, hashFile(ht, state.checkSourcePath(p)).to_string(Base16, false), context); + mkString(v, hashFile(*ht, state.checkSourcePath(p)).to_string(Base16, false), context); } /* Read a directory (without . or ..) */ @@ -1943,8 +1942,8 @@ static void prim_stringLength(EvalState & state, const Pos & pos, Value * * args static void prim_hashString(EvalState & state, const Pos & pos, Value * * args, Value & v) { string type = state.forceStringNoCtx(*args[0], pos); - HashType ht = parseHashType(type); - if (ht == htUnknown) + std::optional ht = parseHashType(type); + if (!ht) throw Error({ .hint = hintfmt("unknown hash type '%1%'", type), .nixCode = NixCode { .errPos = pos } @@ -1953,7 +1952,7 @@ static void prim_hashString(EvalState & state, const Pos & pos, Value * * args, PathSet context; // discarded string s = state.forceString(*args[1], context, pos); - mkString(v, hashString(ht, s).to_string(Base16, false), context); + mkString(v, hashString(*ht, s).to_string(Base16, false), context); } diff --git a/src/libfetchers/fetchers.cc b/src/libfetchers/fetchers.cc index 11cac4c55..9174c3de4 100644 --- a/src/libfetchers/fetchers.cc +++ b/src/libfetchers/fetchers.cc @@ -36,7 +36,7 @@ std::unique_ptr inputFromAttrs(const Attrs & attrs) if (res) { if (auto narHash = maybeGetStrAttr(attrs, "narHash")) // FIXME: require SRI hash. - res->narHash = newHashAllowEmpty(*narHash, htUnknown); + res->narHash = newHashAllowEmpty(*narHash, {}); return res; } } diff --git a/src/libfetchers/tarball.cc b/src/libfetchers/tarball.cc index 7966da314..ac83d52b9 100644 --- a/src/libfetchers/tarball.cc +++ b/src/libfetchers/tarball.cc @@ -264,7 +264,7 @@ struct TarballInputScheme : InputScheme auto input = std::make_unique(parseURL(getStrAttr(attrs, "url"))); if (auto hash = maybeGetStrAttr(attrs, "hash")) - input->hash = newHashAllowEmpty(*hash, htUnknown); + input->hash = newHashAllowEmpty(*hash, {}); return input; } diff --git a/src/libstore/build.cc b/src/libstore/build.cc index a33a9434a..82a2ab831 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -3730,8 +3730,8 @@ void DerivationGoal::registerOutputs() /* Check the hash. In hash mode, move the path produced by the derivation to its content-addressed location. */ Hash h2 = i.second.hash->method == FileIngestionMethod::Recursive - ? hashPath(i.second.hash->hash.type, actualPath).first - : hashFile(i.second.hash->hash.type, actualPath); + ? hashPath(*i.second.hash->hash.type, actualPath).first + : hashFile(*i.second.hash->hash.type, actualPath); auto dest = worker.store.makeFixedOutputPath(i.second.hash->method, h2, i.second.path.name()); @@ -4997,7 +4997,7 @@ bool Worker::pathContentsGood(const StorePath & path) if (!pathExists(store.printStorePath(path))) res = false; else { - HashResult current = hashPath(info->narHash.type, store.printStorePath(path)); + HashResult current = hashPath(*info->narHash.type, store.printStorePath(path)); Hash nullHash(htSHA256); res = info->narHash == nullHash || info->narHash == current.first; } diff --git a/src/libstore/builtins/fetchurl.cc b/src/libstore/builtins/fetchurl.cc index 2048f8f87..1cfe4a46a 100644 --- a/src/libstore/builtins/fetchurl.cc +++ b/src/libstore/builtins/fetchurl.cc @@ -63,9 +63,9 @@ void builtinFetchurl(const BasicDerivation & drv, const std::string & netrcData) for (auto hashedMirror : settings.hashedMirrors.get()) try { if (!hasSuffix(hashedMirror, "/")) hashedMirror += '/'; - auto ht = parseHashType(getAttr("outputHashAlgo")); + auto ht = parseHashTypeOpt(getAttr("outputHashAlgo")); auto h = Hash(getAttr("outputHash"), ht); - fetch(hashedMirror + printHashType(h.type) + "/" + h.to_string(Base16, false)); + fetch(hashedMirror + printHashType(*h.type) + "/" + h.to_string(Base16, false)); return; } catch (Error & e) { debug(e.what()); diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc index 528b7ccea..a79b78db6 100644 --- a/src/libstore/derivations.cc +++ b/src/libstore/derivations.cc @@ -9,7 +9,7 @@ namespace nix { std::string DerivationOutputHash::printMethodAlgo() const { - return makeFileIngestionPrefix(method) + printHashType(hash.type); + return makeFileIngestionPrefix(method) + printHashType(*hash.type); } @@ -121,8 +121,6 @@ static DerivationOutput parseDerivationOutput(const Store & store, istringstream hashAlgo = string(hashAlgo, 2); } const HashType hashType = parseHashType(hashAlgo); - if (hashType == htUnknown) - throw Error("unknown hash hashAlgorithm '%s'", hashAlgo); fsh = DerivationOutputHash { .method = std::move(method), .hash = Hash(hash, hashType), @@ -421,8 +419,6 @@ static DerivationOutput readDerivationOutput(Source & in, const Store & store) hashAlgo = string(hashAlgo, 2); } const HashType hashType = parseHashType(hashAlgo); - if (hashType == htUnknown) - throw Error("unknown hash hashAlgorithm '%s'", hashAlgo); fsh = DerivationOutputHash { .method = std::move(method), .hash = Hash(hash, hashType), diff --git a/src/libstore/export-import.cc b/src/libstore/export-import.cc index cb9da027d..57b7e9590 100644 --- a/src/libstore/export-import.cc +++ b/src/libstore/export-import.cc @@ -55,7 +55,7 @@ void Store::exportPath(const StorePath & path, Sink & sink) filesystem corruption from spreading to other machines. Don't complain if the stored hash is zero (unknown). */ Hash hash = hashAndWriteSink.currentHash(); - if (hash != info->narHash && info->narHash != Hash(info->narHash.type)) + if (hash != info->narHash && info->narHash != Hash(*info->narHash.type)) throw Error("hash of path '%s' has changed from '%s' to '%s'!", printStorePath(path), info->narHash.to_string(Base32, true), hash.to_string(Base32, true)); diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index a47ab5123..c6b55ff7c 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -1255,9 +1255,9 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair) std::unique_ptr hashSink; if (info->ca == "" || !info->references.count(info->path)) - hashSink = std::make_unique(info->narHash.type); + hashSink = std::make_unique(*info->narHash.type); else - hashSink = std::make_unique(info->narHash.type, std::string(info->path.hashPart())); + hashSink = std::make_unique(*info->narHash.type, std::string(info->path.hashPart())); dumpPath(Store::toRealPath(i), *hashSink); auto current = hashSink->finish(); diff --git a/src/libutil/args.cc b/src/libutil/args.cc index ce6580119..1f0e4f803 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -162,8 +162,18 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht) .labels = {"hash-algo"}, .handler = {[ht](std::string s) { *ht = parseHashType(s); - if (*ht == htUnknown) - throw UsageError("unknown hash type '%1%'", s); + }} + }; +} + +Args::Flag Args::Flag::mkHashTypeOptFlag(std::string && longName, std::optional * oht) +{ + return Flag { + .longName = std::move(longName), + .description = "hash algorithm ('md5', 'sha1', 'sha256', or 'sha512'). Optional as can also be gotten from SRI hash itself.", + .labels = {"hash-algo"}, + .handler = {[oht](std::string s) { + *oht = std::optional { parseHashType(s) }; }} }; } diff --git a/src/libutil/args.hh b/src/libutil/args.hh index 154d1e6aa..433d26bba 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -85,6 +85,7 @@ protected: Handler handler; static Flag mkHashTypeFlag(std::string && longName, HashType * ht); + static Flag mkHashTypeOptFlag(std::string && longName, std::optional * oht); }; std::map longFlags; diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc index 460d479a3..e49eb4569 100644 --- a/src/libutil/hash.cc +++ b/src/libutil/hash.cc @@ -4,6 +4,7 @@ #include #include +#include "args.hh" #include "hash.hh" #include "archive.hh" #include "util.hh" @@ -18,11 +19,13 @@ namespace nix { void Hash::init() { - if (type == htMD5) hashSize = md5HashSize; - else if (type == htSHA1) hashSize = sha1HashSize; - else if (type == htSHA256) hashSize = sha256HashSize; - else if (type == htSHA512) hashSize = sha512HashSize; - else abort(); + if (!type) abort(); + switch (*type) { + case htMD5: hashSize = md5HashSize; break; + case htSHA1: hashSize = sha1HashSize; break; + case htSHA256: hashSize = sha256HashSize; break; + case htSHA512: hashSize = sha512HashSize; break; + } assert(hashSize <= maxHashSize); memset(hash, 0, maxHashSize); } @@ -102,11 +105,18 @@ string printHash16or32(const Hash & hash) } +HashType assertInitHashType(const Hash & h) { + if (h.type) + return *h.type; + else + abort(); +} + std::string Hash::to_string(Base base, bool includeType) const { std::string s; if (base == SRI || includeType) { - s += printHashType(type); + s += printHashType(assertInitHashType(*this)); s += base == SRI ? '-' : ':'; } switch (base) { @@ -124,8 +134,10 @@ std::string Hash::to_string(Base base, bool includeType) const return s; } +Hash::Hash(std::string_view s, HashType type) : Hash(s, std::optional { type }) { } +Hash::Hash(std::string_view s) : Hash(s, std::optional{}) { } -Hash::Hash(std::string_view s, HashType type) +Hash::Hash(std::string_view s, std::optional type) : type(type) { size_t pos = 0; @@ -136,17 +148,17 @@ Hash::Hash(std::string_view s, HashType type) sep = s.find('-'); if (sep != string::npos) { isSRI = true; - } else if (type == htUnknown) + } else if (! type) throw BadHash("hash '%s' does not include a type", s); } if (sep != string::npos) { string hts = string(s, 0, sep); this->type = parseHashType(hts); - if (this->type == htUnknown) + if (!this->type) throw BadHash("unknown hash type '%s'", hts); - if (type != htUnknown && type != this->type) - throw BadHash("hash '%s' should have type '%s'", s, printHashType(type)); + if (type && type != this->type) + throw BadHash("hash '%s' should have type '%s'", s, printHashType(*type)); pos = sep + 1; } @@ -202,14 +214,16 @@ Hash::Hash(std::string_view s, HashType type) } else - throw BadHash("hash '%s' has wrong length for hash type '%s'", s, printHashType(type)); + throw BadHash("hash '%s' has wrong length for hash type '%s'", s, printHashType(*type)); } -Hash newHashAllowEmpty(std::string hashStr, HashType ht) +Hash newHashAllowEmpty(std::string hashStr, std::optional ht) { if (hashStr.empty()) { - Hash h(ht); - warn("found empty hash, assuming '%s'", h.to_string(SRI, true)); + if (!ht) + throw BadHash("empty hash requires explicit hash type"); + Hash h(*ht); + warn("found empty hash, assuming '%s'", h.to_string(Base::SRI, true)); return h; } else return Hash(hashStr, ht); @@ -328,24 +342,35 @@ Hash compressHash(const Hash & hash, unsigned int newSize) } -HashType parseHashType(const string & s) +std::optional parseHashTypeOpt(const string & s) { if (s == "md5") return htMD5; else if (s == "sha1") return htSHA1; else if (s == "sha256") return htSHA256; else if (s == "sha512") return htSHA512; - else return htUnknown; + else return std::optional {}; } +HashType parseHashType(const string & s) +{ + auto opt_h = parseHashTypeOpt(s); + if (opt_h) + return *opt_h; + else + throw UsageError("unknown hash algorithm '%1%'", s); +} string printHashType(HashType ht) { - if (ht == htMD5) return "md5"; - else if (ht == htSHA1) return "sha1"; - else if (ht == htSHA256) return "sha256"; - else if (ht == htSHA512) return "sha512"; - else abort(); + switch (ht) { + case htMD5: return "md5"; break; + case htSHA1: return "sha1"; break; + case htSHA256: return "sha256"; break; + case htSHA512: return "sha512"; break; + } + // illegal hash type enum value internally, as opposed to external input + // which should be validated with nice error message. + abort(); } - } diff --git a/src/libutil/hash.hh b/src/libutil/hash.hh index 180fb7633..0d9916508 100644 --- a/src/libutil/hash.hh +++ b/src/libutil/hash.hh @@ -10,7 +10,7 @@ namespace nix { MakeError(BadHash, Error); -enum HashType : char { htUnknown, htMD5, htSHA1, htSHA256, htSHA512 }; +enum HashType : char { htMD5, htSHA1, htSHA256, htSHA512 }; const int md5HashSize = 16; @@ -29,7 +29,7 @@ struct Hash unsigned int hashSize = 0; unsigned char hash[maxHashSize] = {}; - HashType type = htUnknown; + std::optional type = {}; /* Create an unset hash object. */ Hash() { }; @@ -40,14 +40,18 @@ struct Hash /* Initialize the hash from a string representation, in the format "[:]" or "-" (a Subresource Integrity hash expression). If the 'type' argument - is htUnknown, then the hash type must be specified in the + is not present, then the hash type must be specified in the string. */ - Hash(std::string_view s, HashType type = htUnknown); + Hash(std::string_view s, std::optional type); + // type must be provided + Hash(std::string_view s, HashType type); + // hash type must be part of string + Hash(std::string_view s); void init(); /* Check whether a hash is set. */ - operator bool () const { return type != htUnknown; } + operator bool () const { return (bool) type; } /* Check whether two hash are equal. */ bool operator == (const Hash & h2) const; @@ -95,7 +99,7 @@ struct Hash }; /* Helper that defaults empty hashes to the 0 hash. */ -Hash newHashAllowEmpty(std::string hashStr, HashType ht); +Hash newHashAllowEmpty(std::string hashStr, std::optional ht); /* Print a hash in base-16 if it's MD5, or base-32 otherwise. */ string printHash16or32(const Hash & hash); @@ -118,6 +122,8 @@ Hash compressHash(const Hash & hash, unsigned int newSize); /* Parse a string representing a hash type. */ HashType parseHashType(const string & s); +/* Will return nothing on parse error */ +std::optional parseHashTypeOpt(const string & s); /* And the reverse. */ string printHashType(HashType ht); diff --git a/src/libutil/tests/hash.cc b/src/libutil/tests/hash.cc index 5334b046e..412c03030 100644 --- a/src/libutil/tests/hash.cc +++ b/src/libutil/tests/hash.cc @@ -72,9 +72,4 @@ namespace nix { "7299aeadb6889018501d289e4900f7e4331b99dec4b5433a" "c7d329eeb6dd26545e96e55b874be909"); } - - TEST(hashString, hashingWithUnknownAlgoExits) { - auto s = "unknown"; - ASSERT_DEATH(hashString(HashType::htUnknown, s), ""); - } } diff --git a/src/nix-prefetch-url/nix-prefetch-url.cc b/src/nix-prefetch-url/nix-prefetch-url.cc index 55b72bda6..40b05a2f3 100644 --- a/src/nix-prefetch-url/nix-prefetch-url.cc +++ b/src/nix-prefetch-url/nix-prefetch-url.cc @@ -72,8 +72,6 @@ static int _main(int argc, char * * argv) else if (*arg == "--type") { string s = getArg(*arg, arg, end); ht = parseHashType(s); - if (ht == htUnknown) - throw UsageError("unknown hash type '%1%'", s); } else if (*arg == "--print-path") printPath = true; diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc index 5c5afd5ec..4e02aa2bf 100644 --- a/src/nix-store/nix-store.cc +++ b/src/nix-store/nix-store.cc @@ -725,7 +725,7 @@ static void opVerifyPath(Strings opFlags, Strings opArgs) auto path = store->followLinksToStorePath(i); printMsg(lvlTalkative, "checking path '%s'...", store->printStorePath(path)); auto info = store->queryPathInfo(path); - HashSink sink(info->narHash.type); + HashSink sink(*info->narHash.type); store->narFromPath(path, sink); auto current = sink.finish(); if (current.first != info->narHash) { diff --git a/src/nix/hash.cc b/src/nix/hash.cc index 0dd4998d9..f435192fc 100644 --- a/src/nix/hash.cc +++ b/src/nix/hash.cc @@ -79,12 +79,12 @@ static RegisterCommand r2("hash-path", [](){ return make_ref(FileIngest struct CmdToBase : Command { Base base; - HashType ht = htUnknown; + std::optional ht; std::vector args; CmdToBase(Base base) : base(base) { - addFlag(Flag::mkHashTypeFlag("type", &ht)); + addFlag(Flag::mkHashTypeOptFlag("type", &ht)); expectArgs("strings", &args); } @@ -132,8 +132,6 @@ static int compatNixHash(int argc, char * * argv) else if (*arg == "--type") { string s = getArg(*arg, arg, end); ht = parseHashType(s); - if (ht == htUnknown) - throw UsageError("unknown hash type '%1%'", s); } else if (*arg == "--to-base16") op = opTo16; else if (*arg == "--to-base32") op = opTo32; diff --git a/src/nix/verify.cc b/src/nix/verify.cc index ab83637dc..d1aba08e3 100644 --- a/src/nix/verify.cc +++ b/src/nix/verify.cc @@ -88,9 +88,9 @@ struct CmdVerify : StorePathsCommand std::unique_ptr hashSink; if (info->ca == "") - hashSink = std::make_unique(info->narHash.type); + hashSink = std::make_unique(*info->narHash.type); else - hashSink = std::make_unique(info->narHash.type, std::string(info->path.hashPart())); + hashSink = std::make_unique(*info->narHash.type, std::string(info->path.hashPart())); store->narFromPath(info->path, *hashSink);