From c9a08540c3d64d1285928d1ce3d3d416a2547dd9 Mon Sep 17 00:00:00 2001 From: Daiderd Jordan Date: Thu, 30 Aug 2018 00:59:29 +0200 Subject: [PATCH 1/8] nix doctor: add command Inspired by the homebrew command, shows a combination of debugging information and warnings with potential issues with a nix installation. --- src/nix/doctor.cc | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/nix/doctor.cc diff --git a/src/nix/doctor.cc b/src/nix/doctor.cc new file mode 100644 index 000000000..fb4fc2a6e --- /dev/null +++ b/src/nix/doctor.cc @@ -0,0 +1,26 @@ +#include "command.hh" +#include "shared.hh" +#include "store-api.hh" + +using namespace nix; + +struct CmdDoctor : StoreCommand +{ + std::string name() override + { + return "doctor"; + } + + std::string description() override + { + return "check your system for potential problems"; + } + + void run(ref store) override + { + std::cout << "Store uri: " << store->getUri() << std::endl; + } +}; + +static RegisterCommand r1(make_ref()); + From 070823baa4c3c397c8a5eb0378944187e7f4903c Mon Sep 17 00:00:00 2001 From: Daiderd Jordan Date: Thu, 30 Aug 2018 23:28:47 +0200 Subject: [PATCH 2/8] Store: expose the protocol version used by a store --- src/libstore/legacy-ssh-store.cc | 6 ++++++ src/libstore/local-store.cc | 6 ++++++ src/libstore/local-store.hh | 2 ++ src/libstore/remote-store.cc | 7 +++++++ src/libstore/remote-store.hh | 2 ++ src/libstore/store-api.hh | 6 ++++++ 6 files changed, 29 insertions(+) diff --git a/src/libstore/legacy-ssh-store.cc b/src/libstore/legacy-ssh-store.cc index 88d2574e8..26e185198 100644 --- a/src/libstore/legacy-ssh-store.cc +++ b/src/libstore/legacy-ssh-store.cc @@ -303,6 +303,12 @@ struct LegacySSHStore : public Store { auto conn(connections->get()); } + + unsigned int getProtocol() override + { + auto conn(connections->get()); + return conn->remoteVersion; + } }; static RegisterStoreImplementation regStore([]( diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index c91dbf241..c8117c0c6 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -1332,6 +1332,12 @@ void LocalStore::verifyPath(const Path & path, const PathSet & store, } +unsigned int LocalStore::getProtocol() +{ + return PROTOCOL_VERSION; +} + + #if defined(FS_IOC_SETFLAGS) && defined(FS_IOC_GETFLAGS) && defined(FS_IMMUTABLE_FL) static void makeMutable(const Path & path) diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh index 746bdbeed..fce963433 100644 --- a/src/libstore/local-store.hh +++ b/src/libstore/local-store.hh @@ -209,6 +209,8 @@ public: void registerValidPaths(const ValidPathInfos & infos); + unsigned int getProtocol() override; + void vacuumDB(); /* Repair the contents of the given path by redownloading it using diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc index ea86ef052..eff5d2524 100644 --- a/src/libstore/remote-store.cc +++ b/src/libstore/remote-store.cc @@ -646,6 +646,13 @@ void RemoteStore::connect() } +unsigned int RemoteStore::getProtocol() +{ + auto conn(connections->get()); + return conn->daemonVersion; +} + + void RemoteStore::flushBadConnections() { connections->flushBad(); diff --git a/src/libstore/remote-store.hh b/src/libstore/remote-store.hh index b488e34ce..16daee8b6 100644 --- a/src/libstore/remote-store.hh +++ b/src/libstore/remote-store.hh @@ -97,6 +97,8 @@ public: void connect() override; + unsigned int getProtocol() override; + void flushBadConnections(); protected: diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh index 7c5b495a4..c2f964e11 100644 --- a/src/libstore/store-api.hh +++ b/src/libstore/store-api.hh @@ -598,6 +598,12 @@ public: a notion of connection. Otherwise this is a no-op. */ virtual void connect() { }; + /* Get the protocol version of this store or it's connection. */ + virtual unsigned int getProtocol() + { + return 0; + }; + /* Get the priority of the store, used to order substituters. In particular, binary caches can specify a priority field in their "nix-cache-info" file. Lower value means higher priority. */ From 7314dc7f07a90782dea5cc9d298c7f7148e3b7c3 Mon Sep 17 00:00:00 2001 From: Daiderd Jordan Date: Thu, 30 Aug 2018 23:42:28 +0200 Subject: [PATCH 3/8] nix doctor: add warning if client/daemon protocol mismatches A protocol mismatch can sometimes cause problems when using specific features with an older daemon. For example: Nix 2.0 changed the way files are compied to the store. The daemon is backwards compatible and can still handle older clients, however a 1.11 nix-daemon isn't forwards compatible. --- src/nix/doctor.cc | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/nix/doctor.cc b/src/nix/doctor.cc index fb4fc2a6e..6ef5eb9d3 100644 --- a/src/nix/doctor.cc +++ b/src/nix/doctor.cc @@ -1,9 +1,20 @@ #include "command.hh" #include "shared.hh" #include "store-api.hh" +#include "worker-protocol.hh" using namespace nix; +std::string formatProtocol(unsigned int proto) +{ + if (proto) { + auto major = GET_PROTOCOL_MAJOR(proto) >> 8; + auto minor = GET_PROTOCOL_MINOR(proto); + return (format("%1%.%2%") % major % minor).str(); + } + return "unknown"; +} + struct CmdDoctor : StoreCommand { std::string name() override @@ -19,8 +30,22 @@ struct CmdDoctor : StoreCommand void run(ref store) override { std::cout << "Store uri: " << store->getUri() << std::endl; + std::cout << std::endl; + + checkStoreProtocol(store->getProtocol()); + } + + void checkStoreProtocol(unsigned int proto) { + if (PROTOCOL_VERSION != proto) { + std::cout << "Warning: protocol version of this client does not match the store." << std::endl; + std::cout << "While this is not necessarily a problem it's recommended to keep the client in" << std::endl; + std::cout << "sync with the daemon." << std::endl; + std::cout << std::endl; + std::cout << "Client protocol: " << formatProtocol(PROTOCOL_VERSION) << std::endl; + std::cout << "Store protocol: " << formatProtocol(proto) << std::endl; + std::cout << std::endl; + } } }; static RegisterCommand r1(make_ref()); - From 246acf93f2b61b2915e2140d761b19c2e836a96e Mon Sep 17 00:00:00 2001 From: Daiderd Jordan Date: Fri, 31 Aug 2018 01:01:59 +0200 Subject: [PATCH 4/8] nix doctor: handle serve protocol The serve protocol used by LegacySSHStore has a different major and shouldn't be compared to PROTOCOL_VERSION. --- src/nix/doctor.cc | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/nix/doctor.cc b/src/nix/doctor.cc index 6ef5eb9d3..a31df595d 100644 --- a/src/nix/doctor.cc +++ b/src/nix/doctor.cc @@ -1,4 +1,5 @@ #include "command.hh" +#include "serve-protocol.hh" #include "shared.hh" #include "store-api.hh" #include "worker-protocol.hh" @@ -35,14 +36,18 @@ struct CmdDoctor : StoreCommand checkStoreProtocol(store->getProtocol()); } - void checkStoreProtocol(unsigned int proto) { - if (PROTOCOL_VERSION != proto) { + void checkStoreProtocol(unsigned int storeProto) { + auto clientProto = GET_PROTOCOL_MAJOR(SERVE_PROTOCOL_VERSION) == GET_PROTOCOL_MAJOR(storeProto) + ? SERVE_PROTOCOL_VERSION + : PROTOCOL_VERSION; + + if (clientProto != storeProto) { std::cout << "Warning: protocol version of this client does not match the store." << std::endl; std::cout << "While this is not necessarily a problem it's recommended to keep the client in" << std::endl; std::cout << "sync with the daemon." << std::endl; std::cout << std::endl; - std::cout << "Client protocol: " << formatProtocol(PROTOCOL_VERSION) << std::endl; - std::cout << "Store protocol: " << formatProtocol(proto) << std::endl; + std::cout << "Client protocol: " << formatProtocol(clientProto) << std::endl; + std::cout << "Store protocol: " << formatProtocol(storeProto) << std::endl; std::cout << std::endl; } } From 0f18dc54797a1850bc1b91673790ad73e8f4b82f Mon Sep 17 00:00:00 2001 From: Daiderd Jordan Date: Sun, 2 Sep 2018 01:01:23 +0200 Subject: [PATCH 5/8] nix doctor: add warning for multiple versions It's pretty easy to unintentionally install a second version of nix into the user profile when using a daemon install. In this case it looks like nix was upgraded while the nix-daemon is probably still unning an older version. --- src/nix/doctor.cc | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/nix/doctor.cc b/src/nix/doctor.cc index a31df595d..6265e9cfe 100644 --- a/src/nix/doctor.cc +++ b/src/nix/doctor.cc @@ -33,9 +33,26 @@ struct CmdDoctor : StoreCommand std::cout << "Store uri: " << store->getUri() << std::endl; std::cout << std::endl; + checkNixInPath(); checkStoreProtocol(store->getProtocol()); } + void checkNixInPath() { + PathSet dirs; + + for (auto & dir : tokenizeString(getEnv("PATH"), ":")) + if (pathExists(dir + "/nix-env")) + dirs.insert(dirOf(canonPath(dir + "/nix-env", true))); + + if (dirs.size() != 1) { + std::cout << "Warning: multiple versions of nix found in PATH." << std::endl; + std::cout << std::endl; + for (auto & dir : dirs) + std::cout << " " << dir << std::endl; + std::cout << std::endl; + } + } + void checkStoreProtocol(unsigned int storeProto) { auto clientProto = GET_PROTOCOL_MAJOR(SERVE_PROTOCOL_VERSION) == GET_PROTOCOL_MAJOR(storeProto) ? SERVE_PROTOCOL_VERSION From bfdca55868ac1cd336c5d73ff944098ce82d023d Mon Sep 17 00:00:00 2001 From: Daiderd Jordan Date: Sun, 2 Sep 2018 12:52:04 +0200 Subject: [PATCH 6/8] nix doctor: add check for profile roots In most cases profiles that are in PATH should have a gcroot. --- src/nix/doctor.cc | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/nix/doctor.cc b/src/nix/doctor.cc index 6265e9cfe..2a8af7780 100644 --- a/src/nix/doctor.cc +++ b/src/nix/doctor.cc @@ -34,6 +34,7 @@ struct CmdDoctor : StoreCommand std::cout << std::endl; checkNixInPath(); + checkProfileRoots(store); checkStoreProtocol(store->getProtocol()); } @@ -53,6 +54,40 @@ struct CmdDoctor : StoreCommand } } + void checkProfileRoots(ref store) { + PathSet dirs; + + Roots roots = store->findRoots(); + + for (auto & dir : tokenizeString(getEnv("PATH"), ":")) + try { + auto profileDir = canonPath(dirOf(dir), true); + if (hasSuffix(profileDir, "user-environment") && + store->isValidPath(profileDir)) { + PathSet referrers; + store->computeFSClosure({profileDir}, referrers, true, + settings.gcKeepOutputs, settings.gcKeepDerivations); + bool found = false; + for (auto & i : roots) + if (referrers.find(i.second) != referrers.end()) + found = true; + if (!found) + dirs.insert(dir); + + } + } catch (SysError &) {} + + if (!dirs.empty()) { + std::cout << "Warning: found profiles without a gcroot." << std::endl; + std::cout << "The generation this profile points to will be deleted with the next gc, resulting" << std::endl; + std::cout << "in broken symlinks. Make sure your profiles are in " << settings.nixStateDir << "/profiles." << std::endl; + std::cout << std::endl; + for (auto & dir : dirs) + std::cout << " " << dir << std::endl; + std::cout << std::endl; + } + } + void checkStoreProtocol(unsigned int storeProto) { auto clientProto = GET_PROTOCOL_MAJOR(SERVE_PROTOCOL_VERSION) == GET_PROTOCOL_MAJOR(storeProto) ? SERVE_PROTOCOL_VERSION From 80a4b44d3d1bd35ab13d6b534f1015d15fdb08fd Mon Sep 17 00:00:00 2001 From: Daiderd Jordan Date: Sun, 2 Sep 2018 13:14:39 +0200 Subject: [PATCH 7/8] nix doctor: only perform path/profile checks with a daemon/local store Not all store types LegacySSHStore support these operations and it doesn't really make sense to check those. --- src/nix/doctor.cc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/nix/doctor.cc b/src/nix/doctor.cc index 2a8af7780..6112b1f65 100644 --- a/src/nix/doctor.cc +++ b/src/nix/doctor.cc @@ -33,8 +33,12 @@ struct CmdDoctor : StoreCommand std::cout << "Store uri: " << store->getUri() << std::endl; std::cout << std::endl; - checkNixInPath(); - checkProfileRoots(store); + auto type = getStoreType(); + + if (type < tOther) { + checkNixInPath(); + checkProfileRoots(store); + } checkStoreProtocol(store->getProtocol()); } @@ -56,7 +60,6 @@ struct CmdDoctor : StoreCommand void checkProfileRoots(ref store) { PathSet dirs; - Roots roots = store->findRoots(); for (auto & dir : tokenizeString(getEnv("PATH"), ":")) From ed25753501a7a3f77c097df01db299bdb60ede96 Mon Sep 17 00:00:00 2001 From: Daiderd Jordan Date: Wed, 26 Sep 2018 22:59:41 +0200 Subject: [PATCH 8/8] nix doctor: reimplement profile warning without gcroot check Calculating roots seems significantly slower on darwin compared to linux. Checking for /profile/ links could show some false positives but should still catch most issues. --- src/nix/doctor.cc | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/nix/doctor.cc b/src/nix/doctor.cc index 6112b1f65..b608b9d59 100644 --- a/src/nix/doctor.cc +++ b/src/nix/doctor.cc @@ -42,7 +42,8 @@ struct CmdDoctor : StoreCommand checkStoreProtocol(store->getProtocol()); } - void checkNixInPath() { + void checkNixInPath() + { PathSet dirs; for (auto & dir : tokenizeString(getEnv("PATH"), ":")) @@ -58,32 +59,29 @@ struct CmdDoctor : StoreCommand } } - void checkProfileRoots(ref store) { + void checkProfileRoots(ref store) + { PathSet dirs; - Roots roots = store->findRoots(); - for (auto & dir : tokenizeString(getEnv("PATH"), ":")) + for (auto & dir : tokenizeString(getEnv("PATH"), ":")) { + Path profileDir = dirOf(dir); try { - auto profileDir = canonPath(dirOf(dir), true); - if (hasSuffix(profileDir, "user-environment") && - store->isValidPath(profileDir)) { - PathSet referrers; - store->computeFSClosure({profileDir}, referrers, true, - settings.gcKeepOutputs, settings.gcKeepDerivations); - bool found = false; - for (auto & i : roots) - if (referrers.find(i.second) != referrers.end()) - found = true; - if (!found) - dirs.insert(dir); + Path userEnv = canonPath(profileDir, true); + if (store->isStorePath(userEnv) && hasSuffix(userEnv, "user-environment")) { + while (profileDir.find("/profiles/") == std::string::npos && isLink(profileDir)) + profileDir = absPath(readLink(profileDir), dirOf(profileDir)); + + if (profileDir.find("/profiles/") == std::string::npos) + dirs.insert(dir); } } catch (SysError &) {} + } if (!dirs.empty()) { - std::cout << "Warning: found profiles without a gcroot." << std::endl; - std::cout << "The generation this profile points to will be deleted with the next gc, resulting" << std::endl; - std::cout << "in broken symlinks. Make sure your profiles are in " << settings.nixStateDir << "/profiles." << std::endl; + std::cout << "Warning: found profiles outside of " << settings.nixStateDir << "/profiles." << std::endl; + std::cout << "The generation this profile points to might not have a gcroot and could be" << std::endl; + std::cout << "garbage collected, resulting in broken symlinks." << std::endl; std::cout << std::endl; for (auto & dir : dirs) std::cout << " " << dir << std::endl; @@ -91,7 +89,8 @@ struct CmdDoctor : StoreCommand } } - void checkStoreProtocol(unsigned int storeProto) { + void checkStoreProtocol(unsigned int storeProto) + { auto clientProto = GET_PROTOCOL_MAJOR(SERVE_PROTOCOL_VERSION) == GET_PROTOCOL_MAJOR(storeProto) ? SERVE_PROTOCOL_VERSION : PROTOCOL_VERSION;