From c036d75f9e15baea68056de601fb089e38d36c5c Mon Sep 17 00:00:00 2001 From: John Ericson Date: Tue, 23 Jan 2024 14:23:03 -0500 Subject: [PATCH] Factor out abstract syntax for Store URIs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Need to decouple parsing from actually opening a store for Machine configs. Co-authored-by: Théophane Hufschmitt <7226587+thufschmitt@users.noreply.github.com> Co-authored-by: Robert Hensing --- src/libstore/store-api.cc | 147 +++++++++++--------------------- src/libstore/store-api.hh | 45 +++------- src/libstore/store-reference.cc | 92 ++++++++++++++++++++ src/libstore/store-reference.hh | 84 ++++++++++++++++++ 4 files changed, 236 insertions(+), 132 deletions(-) create mode 100644 src/libstore/store-reference.cc create mode 100644 src/libstore/store-reference.hh diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc index 6eba3a77d..7c2b3815f 100644 --- a/src/libstore/store-api.cc +++ b/src/libstore/store-api.cc @@ -8,7 +8,6 @@ #include "util.hh" #include "nar-info-disk-cache.hh" #include "thread-pool.hh" -#include "url.hh" #include "references.hh" #include "archive.hh" #include "callback.hh" @@ -1267,109 +1266,63 @@ Derivation Store::readInvalidDerivation(const StorePath & drvPath) namespace nix { -/* Split URI into protocol+hierarchy part and its parameter set. */ -std::pair splitUriAndParams(const std::string & uri_) -{ - auto uri(uri_); - Store::Params params; - auto q = uri.find('?'); - if (q != std::string::npos) { - params = decodeQuery(uri.substr(q + 1)); - uri = uri_.substr(0, q); - } - return {uri, params}; -} - -static bool isNonUriPath(const std::string & spec) -{ - return - // is not a URL - spec.find("://") == std::string::npos - // Has at least one path separator, and so isn't a single word that - // might be special like "auto" - && spec.find("/") != std::string::npos; -} - -std::shared_ptr openFromNonUri(const std::string & uri, const Store::Params & params) -{ - // TODO reenable on Windows once we have `LocalStore` and - // `UDSRemoteStore`. - if (uri == "" || uri == "auto") { - auto stateDir = getOr(params, "state", settings.nixStateDir); - if (access(stateDir.c_str(), R_OK | W_OK) == 0) - return std::make_shared(params); - else if (pathExists(settings.nixDaemonSocketFile)) - return std::make_shared(params); - #if __linux__ - else if (!pathExists(stateDir) - && params.empty() - && !isRootUser() - && !getEnv("NIX_STORE_DIR").has_value() - && !getEnv("NIX_STATE_DIR").has_value()) - { - /* If /nix doesn't exist, there is no daemon socket, and - we're not root, then automatically set up a chroot - store in ~/.local/share/nix/root. */ - auto chrootStore = getDataDir() + "/nix/root"; - if (!pathExists(chrootStore)) { - try { - createDirs(chrootStore); - } catch (Error & e) { - return std::make_shared(params); - } - warn("'%s' does not exist, so Nix will use '%s' as a chroot store", stateDir, chrootStore); - } else - debug("'%s' does not exist, so Nix will use '%s' as a chroot store", stateDir, chrootStore); - return std::make_shared("local", chrootStore, params); - } - #endif - else - return std::make_shared(params); - } else if (uri == "daemon") { - return std::make_shared(params); - } else if (uri == "local") { - return std::make_shared(params); - } else if (isNonUriPath(uri)) { - return std::make_shared("local", absPath(uri), params); - } else { - return nullptr; - } -} - -ref openStore(const std::string & uri_, +ref openStore(const std::string & uri, const Store::Params & extraParams) { - auto params = extraParams; - try { - auto parsedUri = parseURL(uri_); - params.insert(parsedUri.query.begin(), parsedUri.query.end()); + return openStore(StoreReference::parse(uri, extraParams)); +} - auto baseURI = parsedUri.authority.value_or("") + parsedUri.path; +ref openStore(StoreReference && storeURI) +{ + auto & params = storeURI.params; - for (auto implem : *Implementations::registered) { - if (implem.uriSchemes.count(parsedUri.scheme)) { - auto store = implem.create(parsedUri.scheme, baseURI, params); - if (store) { - experimentalFeatureSettings.require(store->experimentalFeature()); - store->init(); - store->warnUnknownSettings(); - return ref(store); - } + auto store = std::visit(overloaded { + [&](const StoreReference::Auto &) -> std::shared_ptr { + auto stateDir = getOr(params, "state", settings.nixStateDir); + if (access(stateDir.c_str(), R_OK | W_OK) == 0) + return std::make_shared(params); + else if (pathExists(settings.nixDaemonSocketFile)) + return std::make_shared(params); + #if __linux__ + else if (!pathExists(stateDir) + && params.empty() + && !isRootUser() + && !getEnv("NIX_STORE_DIR").has_value() + && !getEnv("NIX_STATE_DIR").has_value()) + { + /* If /nix doesn't exist, there is no daemon socket, and + we're not root, then automatically set up a chroot + store in ~/.local/share/nix/root. */ + auto chrootStore = getDataDir() + "/nix/root"; + if (!pathExists(chrootStore)) { + try { + createDirs(chrootStore); + } catch (Error & e) { + return std::make_shared(params); + } + warn("'%s' does not exist, so Nix will use '%s' as a chroot store", stateDir, chrootStore); + } else + debug("'%s' does not exist, so Nix will use '%s' as a chroot store", stateDir, chrootStore); + return std::make_shared("local", chrootStore, params); } - } - } - catch (BadURL &) { - auto [uri, uriParams] = splitUriAndParams(uri_); - params.insert(uriParams.begin(), uriParams.end()); + #endif + else + return std::make_shared(params); + }, + [&](const StoreReference::Specified & g) { + for (auto implem : *Implementations::registered) + if (implem.uriSchemes.count(g.scheme)) + return implem.create(g.scheme, g.authority, params); - if (auto store = openFromNonUri(uri, params)) { - experimentalFeatureSettings.require(store->experimentalFeature()); - store->warnUnknownSettings(); - return ref(store); - } - } + throw Error("don't know how to open Nix store with scheme '%s'", g.scheme); + }, + }, storeURI.variant); - throw Error("don't know how to open Nix store '%s'", uri_); + experimentalFeatureSettings.require(store->experimentalFeature()); + store->warnUnknownSettings(); + store->init(); + + return ref { store }; } std::list> getDefaultSubstituters() diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh index a508e5a00..430d9a5ab 100644 --- a/src/libstore/store-api.hh +++ b/src/libstore/store-api.hh @@ -13,6 +13,7 @@ #include "path-info.hh" #include "repair-flag.hh" #include "store-dir-config.hh" +#include "store-reference.hh" #include "source-path.hh" #include @@ -65,7 +66,7 @@ MakeError(Unsupported, Error); MakeError(SubstituteGone, Error); MakeError(SubstituterDisabled, Error); -MakeError(InvalidStoreURI, Error); +MakeError(InvalidStoreReference, Error); struct Realisation; struct RealisedPath; @@ -102,7 +103,7 @@ typedef std::map> StorePathCAMap; struct StoreConfig : public StoreDirConfig { - typedef std::map Params; + using Params = StoreReference::Params; using StoreDirConfig::StoreDirConfig; @@ -859,34 +860,13 @@ OutputPathMap resolveDerivedPath(Store &, const DerivedPath::Built &, Store * ev /** * @return a Store object to access the Nix store denoted by * ‘uri’ (slight misnomer...). - * - * @param uri Supported values are: - * - * - ‘local’: The Nix store in /nix/store and database in - * /nix/var/nix/db, accessed directly. - * - * - ‘daemon’: The Nix store accessed via a Unix domain socket - * connection to nix-daemon. - * - * - ‘unix://’: The Nix store accessed via a Unix domain socket - * connection to nix-daemon, with the socket located at . - * - * - ‘auto’ or ‘’: Equivalent to ‘local’ or ‘daemon’ depending on - * whether the user has write access to the local Nix - * store/database. - * - * - ‘file://’: A binary cache stored in . - * - * - ‘https://’: A binary cache accessed via HTTP. - * - * - ‘s3://’: A writable binary cache stored on Amazon's Simple - * Storage Service. - * - * - ‘ssh://[user@]’: A remote Nix store accessed by running - * ‘nix-store --serve’ via SSH. - * - * You can pass parameters to the store type by appending - * ‘?key=value&key=value&...’ to the URI. + */ +ref openStore(StoreReference && storeURI); + + +/** + * Opens the store at `uri`, where `uri` is in the format expected by `StoreReference::parse` + */ ref openStore(const std::string & uri = settings.storeUri.get(), const Store::Params & extraParams = Store::Params()); @@ -957,11 +937,6 @@ std::optional decodeValidPathInfo( std::istream & str, std::optional hashGiven = std::nullopt); -/** - * Split URI into protocol+hierarchy part and its parameter set. - */ -std::pair splitUriAndParams(const std::string & uri); - const ContentAddress * getDerivationCA(const BasicDerivation & drv); std::map drvOutputReferences( diff --git a/src/libstore/store-reference.cc b/src/libstore/store-reference.cc new file mode 100644 index 000000000..9e5dafd99 --- /dev/null +++ b/src/libstore/store-reference.cc @@ -0,0 +1,92 @@ +#include + +#include "error.hh" +#include "url.hh" +#include "store-reference.hh" +#include "file-system.hh" + +namespace nix { + +static bool isNonUriPath(const std::string & spec) +{ + return + // is not a URL + spec.find("://") == std::string::npos + // Has at least one path separator, and so isn't a single word that + // might be special like "auto" + && spec.find("/") != std::string::npos; +} + +StoreReference StoreReference::parse(const std::string & uri, const StoreReference::Params & extraParams) +{ + auto params = extraParams; + try { + auto parsedUri = parseURL(uri); + params.insert(parsedUri.query.begin(), parsedUri.query.end()); + + auto baseURI = parsedUri.authority.value_or("") + parsedUri.path; + + return { + .variant = + Specified{ + .scheme = std::move(parsedUri.scheme), + .authority = std::move(baseURI), + }, + .params = std::move(params), + }; + } catch (BadURL &) { + auto [baseURI, uriParams] = splitUriAndParams(uri); + params.insert(uriParams.begin(), uriParams.end()); + + if (baseURI == "" || baseURI == "auto") { + return { + .variant = Auto{}, + .params = std::move(params), + }; + } else if (baseURI == "daemon") { + return { + .variant = + Specified{ + .scheme = "unix", + .authority = "", + }, + .params = std::move(params), + }; + } else if (baseURI == "local") { + return { + .variant = + Specified{ + .scheme = "local", + .authority = "", + }, + .params = std::move(params), + }; + } else if (isNonUriPath(baseURI)) { + return { + .variant = + Specified{ + .scheme = "local", + .authority = absPath(baseURI), + }, + .params = std::move(params), + }; + } + } + + throw UsageError("Cannot parse Nix store '%s'", uri); +} + +/* Split URI into protocol+hierarchy part and its parameter set. */ +std::pair splitUriAndParams(const std::string & uri_) +{ + auto uri(uri_); + StoreReference::Params params; + auto q = uri.find('?'); + if (q != std::string::npos) { + params = decodeQuery(uri.substr(q + 1)); + uri = uri_.substr(0, q); + } + return {uri, params}; +} + +} diff --git a/src/libstore/store-reference.hh b/src/libstore/store-reference.hh new file mode 100644 index 000000000..c94a87d7d --- /dev/null +++ b/src/libstore/store-reference.hh @@ -0,0 +1,84 @@ +#pragma once +///@file + +#include + +#include "types.hh" + +namespace nix { + +/** + * A parsed Store URI (URI is a slight misnomer...), parsed but not yet + * resolved to a specific instance and query parms validated. + * + * Supported values are: + * + * - ‘local’: The Nix store in /nix/store and database in + * /nix/var/nix/db, accessed directly. + * + * - ‘daemon’: The Nix store accessed via a Unix domain socket + * connection to nix-daemon. + * + * - ‘unix://’: The Nix store accessed via a Unix domain socket + * connection to nix-daemon, with the socket located at . + * + * - ‘auto’ or ‘’: Equivalent to ‘local’ or ‘daemon’ depending on + * whether the user has write access to the local Nix + * store/database. + * + * - ‘file://’: A binary cache stored in . + * + * - ‘https://’: A binary cache accessed via HTTP. + * + * - ‘s3://’: A writable binary cache stored on Amazon's Simple + * Storage Service. + * + * - ‘ssh://[user@]’: A remote Nix store accessed by running + * ‘nix-store --serve’ via SSH. + * + * You can pass parameters to the store type by appending + * ‘?key=value&key=value&...’ to the URI. + */ +struct StoreReference +{ + using Params = std::map; + + /** + * Special store reference `""` or `"auto"` + */ + struct Auto + { + inline bool operator==(const Auto & rhs) const = default; + inline auto operator<=>(const Auto & rhs) const = default; + }; + + /** + * General case, a regular `scheme://authority` URL. + */ + struct Specified + { + std::string scheme; + std::string authority; + + bool operator==(const Specified & rhs) const = default; + auto operator<=>(const Specified & rhs) const = default; + }; + + typedef std::variant Variant; + + Variant variant; + + Params params; + + bool operator==(const StoreReference & rhs) const = default; + auto operator<=>(const StoreReference & rhs) const = default; + + static StoreReference parse(const std::string & uri, const Params & extraParams = Params{}); +}; + +/** + * Split URI into protocol+hierarchy part and its parameter set. + */ +std::pair splitUriAndParams(const std::string & uri); + +}