#include <iostream>
#include <absl/strings/match.h>
#include <fcntl.h>
#include <glog/logging.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "libexpr/attr-path.hh"
#include "libexpr/common-eval-args.hh"
#include "libexpr/eval-inline.hh"
#include "libexpr/eval.hh"
#include "libmain/shared.hh"
#include "libstore/download.hh"
#include "libstore/store-api.hh"
#include "libutil/finally.hh"
#include "libutil/hash.hh"
#include "nix/legacy.hh"
using namespace nix;
/* If ‘uri’ starts with ‘mirror://’, then resolve it using the list of
mirrors defined in Nixpkgs. */
std::string resolveMirrorUri(EvalState& state, std::string uri) {
if (std::string(uri, 0, 9) != "mirror://") {
return uri;
}
std::string s(uri, 9);
auto p = s.find('/');
if (p == std::string::npos) {
throw Error("invalid mirror URI");
}
std::string mirrorName(s, 0, p);
Value vMirrors{};
state.eval(
state.parseExprFromString(
"import <nixpkgs/pkgs/build-support/fetchurl/mirrors.nix>", "."),
vMirrors);
state.forceAttrs(vMirrors);
auto mirrorList = vMirrors.attrs->find(state.symbols.Create(mirrorName));
if (mirrorList == vMirrors.attrs->end()) {
throw Error(format("unknown mirror name '%1%'") % mirrorName);
}
state.forceList(*mirrorList->second.value);
if (mirrorList->second.value->listSize() < 1) {
throw Error(format("mirror URI '%1%' did not expand to anything") % uri);
}
std::string mirror = state.forceString(*(*mirrorList->second.value->list)[0]);
return mirror + (absl::EndsWith(mirror, "/") ? "" : "/") +
std::string(s, p + 1);
}
static int _main(int argc, char** argv) {
{
HashType ht = htSHA256;
std::vector<std::string> args;
bool printPath = !getEnv("PRINT_PATH").empty();
bool fromExpr = false;
std::string attrPath;
bool unpack = false;
std::string name;
struct MyArgs : LegacyArgs, MixEvalArgs {
using LegacyArgs::LegacyArgs;
};
MyArgs myArgs(baseNameOf(argv[0]),
[&](Strings::iterator& arg, const Strings::iterator& end) {
if (*arg == "--help") {
showManPage("nix-prefetch-url");
} else if (*arg == "--version") {
printVersion("nix-prefetch-url");
} else if (*arg == "--type") {
std::string s = getArg(*arg, arg, end);
ht = parseHashType(s);
if (ht == htUnknown) {
throw UsageError(format("unknown hash type '%1%'") % s);
}
} else if (*arg == "--print-path") {
printPath = true;
} else if (*arg == "--attr" || *arg == "-A") {
fromExpr = true;
attrPath = getArg(*arg, arg, end);
} else if (*arg == "--unpack") {
unpack = true;
} else if (*arg == "--name") {
name = getArg(*arg, arg, end);
} else if (*arg != "" && arg->at(0) == '-') {
return false;
} else {
args.push_back(*arg);
}
return true;
});
myArgs.parseCmdline(argvToStrings(argc, argv));
if (args.size() > 2) {
throw UsageError("too many arguments");
}
auto store = openStore();
auto state = std::make_unique<EvalState>(myArgs.searchPath, store);
Bindings& autoArgs = *myArgs.getAutoArgs(*state);
/* If -A is given, get the URI from the specified Nix
expression. */
std::string uri;
if (!fromExpr) {
if (args.empty()) {
throw UsageError("you must specify a URI");
}
uri = args[0];
} else {
Path path =
resolveExprPath(lookupFileArg(*state, args.empty() ? "." : args[0]));
Value vRoot{};
state->evalFile(path, vRoot);
Value& v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot));
state->forceAttrs(v);
/* Extract the URI. */
auto attr = v.attrs->find(state->symbols.Create("urls"));
if (attr == v.attrs->end()) {
throw Error("attribute set does not contain a 'urls' attribute");
}
state->forceList(*attr->second.value);
if (attr->second.value->listSize() < 1) {
throw Error("'urls' list is empty");
}
uri = state->forceString(*(*attr->second.value->list)[0]);
/* Extract the hash mode. */
attr = v.attrs->find(state->symbols.Create("outputHashMode"));
if (attr == v.attrs->end()) {
LOG(WARNING) << "this does not look like a fetchurl call";
} else {
unpack = state->forceString(*attr->second.value) == "recursive";
}
/* Extract the name. */
if (name.empty()) {
attr = v.attrs->find(state->symbols.Create("name"));
if (attr != v.attrs->end()) {
name = state->forceString(*attr->second.value);
}
}
}
/* Figure out a name in the Nix store. */
if (name.empty()) {
name = baseNameOf(uri);
}
if (name.empty()) {
throw Error(format("cannot figure out file name for '%1%'") % uri);
}
/* If an expected hash is given, the file may already exist in
the store. */
Hash hash;
Hash expectedHash(ht);
Path storePath;
if (args.size() == 2) {
auto expectedHash_ = Hash::deserialize(args[1], ht);
expectedHash = Hash::unwrap_throw(expectedHash);
storePath = store->makeFixedOutputPath(unpack, expectedHash, name);
if (store->isValidPath(storePath)) {
hash = expectedHash;
} else {
storePath.clear();
}
}
if (storePath.empty()) {
auto actualUri = resolveMirrorUri(*state, uri);
AutoDelete tmpDir(createTempDir(), true);
Path tmpFile = Path(tmpDir) + "/tmp";
/* Download the file. */
{
AutoCloseFD fd =
open(tmpFile.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0600);
if (!fd) {
throw SysError("creating temporary file '%s'", tmpFile);
}
FdSink sink(fd.get());
DownloadRequest req(actualUri);
req.decompress = false;
getDownloader()->download(std::move(req), sink);
}
/* Optionally unpack the file. */
if (unpack) {
LOG(INFO) << "unpacking...";
Path unpacked = Path(tmpDir) + "/unpacked";
createDirs(unpacked);
if (absl::EndsWith(baseNameOf(uri), ".zip")) {
runProgram("unzip", true, {"-qq", tmpFile, "-d", unpacked});
} else {
// FIXME: this requires GNU tar for decompression.
runProgram("tar", true, {"xf", tmpFile, "-C", unpacked});
}
/* If the archive unpacks to a single file/directory, then use
that as the top-level. */
auto entries = readDirectory(unpacked);
if (entries.size() == 1) {
tmpFile = unpacked + "/" + entries[0].name;
} else {
tmpFile = unpacked;
}
}
/* FIXME: inefficient; addToStore() will also hash
this. */
hash = unpack ? hashPath(ht, tmpFile).first : hashFile(ht, tmpFile);
if (expectedHash != Hash(ht) && expectedHash != hash) {
throw Error(format("hash mismatch for '%1%'") % uri);
}
/* Copy the file to the Nix store. FIXME: if RemoteStore
implemented addToStoreFromDump() and downloadFile()
supported a sink, we could stream the download directly
into the Nix store. */
storePath = store->addToStore(name, tmpFile, unpack, ht);
assert(storePath == store->makeFixedOutputPath(unpack, hash, name));
}
if (!printPath) {
LOG(INFO) << "path is '" << storePath << "'";
}
std::cout << printHash16or32(hash) << std::endl;
if (printPath) {
std::cout << storePath << std::endl;
}
return 0;
}
}
static RegisterLegacyCommand s1("nix-prefetch-url", _main);