nix-super/src/nix/doctor.cc
John Ericson ac89bb064a Split up util.{hh,cc}
All OS and IO operations should be moved out, leaving only some misc
portable pure functions.

This is useful to avoid copious CPP when doing things like Windows and
Emscripten ports.

Newly exposed functions to break cycles:

 - `restoreSignals`
 - `updateWindowSize`
2023-11-05 12:20:02 -05:00

155 lines
4.6 KiB
C++

#include <sstream>
#include "command.hh"
#include "logging.hh"
#include "serve-protocol.hh"
#include "shared.hh"
#include "store-api.hh"
#include "local-fs-store.hh"
#include "worker-protocol.hh"
using namespace nix;
namespace {
std::string formatProtocol(unsigned int proto)
{
if (proto) {
auto major = GET_PROTOCOL_MAJOR(proto) >> 8;
auto minor = GET_PROTOCOL_MINOR(proto);
return fmt("%1%.%2%", major, minor);
}
return "unknown";
}
bool checkPass(const std::string & msg) {
notice(ANSI_GREEN "[PASS] " ANSI_NORMAL + msg);
return true;
}
bool checkFail(const std::string & msg) {
notice(ANSI_RED "[FAIL] " ANSI_NORMAL + msg);
return false;
}
void checkInfo(const std::string & msg) {
notice(ANSI_BLUE "[INFO] " ANSI_NORMAL + msg);
}
}
struct CmdDoctor : StoreCommand
{
bool success = true;
/**
* This command is stable before the others
*/
std::optional<ExperimentalFeature> experimentalFeature() override
{
return std::nullopt;
}
std::string description() override
{
return "check your system for potential problems and print a PASS or FAIL for each check";
}
Category category() override { return catNixInstallation; }
void run(ref<Store> store) override
{
logger->log("Running checks against store uri: " + store->getUri());
if (store.dynamic_pointer_cast<LocalFSStore>()) {
success &= checkNixInPath();
success &= checkProfileRoots(store);
}
success &= checkStoreProtocol(store->getProtocol());
checkTrustedUser(store);
if (!success)
throw Exit(2);
}
bool checkNixInPath()
{
PathSet dirs;
for (auto & dir : tokenizeString<Strings>(getEnv("PATH").value_or(""), ":"))
if (pathExists(dir + "/nix-env"))
dirs.insert(dirOf(canonPath(dir + "/nix-env", true)));
if (dirs.size() != 1) {
std::stringstream ss;
ss << "Multiple versions of nix found in PATH:\n";
for (auto & dir : dirs)
ss << " " << dir << "\n";
return checkFail(ss.str());
}
return checkPass("PATH contains only one nix version.");
}
bool checkProfileRoots(ref<Store> store)
{
PathSet dirs;
for (auto & dir : tokenizeString<Strings>(getEnv("PATH").value_or(""), ":")) {
Path profileDir = dirOf(dir);
try {
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::stringstream ss;
ss << "Found profiles outside of " << settings.nixStateDir << "/profiles.\n"
<< "The generation this profile points to might not have a gcroot and could be\n"
<< "garbage collected, resulting in broken symlinks.\n\n";
for (auto & dir : dirs)
ss << " " << dir << "\n";
ss << "\n";
return checkFail(ss.str());
}
return checkPass("All profiles are gcroots.");
}
bool checkStoreProtocol(unsigned int storeProto)
{
unsigned int clientProto = GET_PROTOCOL_MAJOR(SERVE_PROTOCOL_VERSION) == GET_PROTOCOL_MAJOR(storeProto)
? SERVE_PROTOCOL_VERSION
: PROTOCOL_VERSION;
if (clientProto != storeProto) {
std::stringstream ss;
ss << "Warning: protocol version of this client does not match the store.\n"
<< "While this is not necessarily a problem it's recommended to keep the client in\n"
<< "sync with the daemon.\n\n"
<< "Client protocol: " << formatProtocol(clientProto) << "\n"
<< "Store protocol: " << formatProtocol(storeProto) << "\n\n";
return checkFail(ss.str());
}
return checkPass("Client protocol matches store protocol.");
}
void checkTrustedUser(ref<Store> store)
{
std::string_view trusted = store->isTrustedClient()
? "trusted"
: "not trusted";
checkInfo(fmt("You are %s by store uri: %s", trusted, store->getUri()));
}
};
static auto rCmdDoctor = registerCommand<CmdDoctor>("doctor");