X-Git-Url: http://git.hcoop.net/jackhill/guix/guix.git/blobdiff_plain/4f70db97a040b35f125484ce8885766ca5807dd4..69a81892acf71aae4ea2aa98c015bfaf1649caba:/nix/libstore/local-store.cc diff --git a/nix/libstore/local-store.cc b/nix/libstore/local-store.cc index 0aed59710f..675d1ba66f 100644 --- a/nix/libstore/local-store.cc +++ b/nix/libstore/local-store.cc @@ -21,6 +21,7 @@ #include #include #include +#include #if HAVE_UNSHARE && HAVE_STATVFS && HAVE_SYS_MOUNT_H #include @@ -28,11 +29,8 @@ #include #endif -#if HAVE_LINUX_FS_H -#include #include #include -#endif #include @@ -51,7 +49,7 @@ void checkStoreNotSymlink() if (S_ISLNK(st.st_mode)) throw Error(format( "the path `%1%' is a symlink; " - "this is not allowed for the Nix store and its parent directories") + "this is not allowed for the store and its parent directories") % path); path = dirOf(path); } @@ -59,7 +57,6 @@ void checkStoreNotSymlink() LocalStore::LocalStore(bool reserveSpace) - : didSetSubstituterEnv(false) { schemaPath = settings.nixDBPath + "/schema"; @@ -88,8 +85,9 @@ LocalStore::LocalStore(bool reserveSpace) Path perUserDir = profilesDir + "/per-user"; createDirs(perUserDir); - if (chmod(perUserDir.c_str(), 01777) == -1) - throw SysError(format("could not set permissions on '%1%' to 1777") % perUserDir); + if (chmod(perUserDir.c_str(), 0755) == -1) + throw SysError(format("could not set permissions on '%1%' to 755") + % perUserDir); mode_t perm = 01775; @@ -153,7 +151,7 @@ LocalStore::LocalStore(bool reserveSpace) } if (!lockFile(globalLock, ltRead, false)) { - printMsg(lvlError, "waiting for the big Nix store lock..."); + printMsg(lvlError, "waiting for the big store lock..."); lockFile(globalLock, ltRead, true); } @@ -161,7 +159,7 @@ LocalStore::LocalStore(bool reserveSpace) upgrade. */ int curSchema = getSchema(); if (curSchema > nixSchemaVersion) - throw Error(format("current Nix store schema is version %1%, but I only support %2%") + throw Error(format("current store schema is version %1%, but I only support %2%") % curSchema % nixSchemaVersion); else if (curSchema == 0) { /* new store */ @@ -183,19 +181,6 @@ LocalStore::LocalStore(bool reserveSpace) LocalStore::~LocalStore() { - try { - foreach (RunningSubstituters::iterator, i, runningSubstituters) { - if (i->second.disabled) continue; - i->second.to.close(); - i->second.from.close(); - i->second.error.close(); - if (i->second.pid != -1) - i->second.pid.wait(true); - } - } catch (...) { - ignoreException(); - } - try { if (fdTempRoots != -1) { fdTempRoots.close(); @@ -222,13 +207,13 @@ int LocalStore::getSchema() void LocalStore::openDB(bool create) { if (access(settings.nixDBPath.c_str(), R_OK | W_OK)) - throw SysError(format("Nix database directory `%1%' is not writable") % settings.nixDBPath); + throw SysError(format("store database directory `%1%' is not writable") % settings.nixDBPath); - /* Open the Nix database. */ + /* Open the store database. */ string dbPath = settings.nixDBPath + "/db.sqlite"; if (sqlite3_open_v2(dbPath.c_str(), &db.db, SQLITE_OPEN_READWRITE | (create ? SQLITE_OPEN_CREATE : 0), 0) != SQLITE_OK) - throw Error(format("cannot open Nix database `%1%'") % dbPath); + throw Error(format("cannot open store database `%1%'") % dbPath); if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK) throwSQLiteError(db, "setting timeout"); @@ -316,8 +301,8 @@ void LocalStore::openDB(bool create) } -/* To improve purity, users may want to make the Nix store a read-only - bind mount. So make the Nix store writable for this process. */ +/* To improve purity, users may want to make the store a read-only + bind mount. So make the store writable for this process. */ void LocalStore::makeStoreWritable() { #if HAVE_UNSHARE && HAVE_STATVFS && HAVE_SYS_MOUNT_H && defined(MS_BIND) && defined(MS_REMOUNT) @@ -325,7 +310,7 @@ void LocalStore::makeStoreWritable() /* Check if /nix/store is on a read-only mount. */ struct statvfs stat; if (statvfs(settings.nixStore.c_str(), &stat) != 0) - throw SysError("getting info about the Nix store mount point"); + throw SysError("getting info about the store mount point"); if (stat.f_flag & ST_RDONLY) { if (unshare(CLONE_NEWNS) == -1) @@ -420,8 +405,8 @@ static void canonicalisePathMetaData_(const Path & path, uid_t fromUid, InodesSe lchown if available, otherwise don't bother. Wrong ownership of a symlink doesn't matter, since the owning user can't change the symlink and can't delete it because the directory is not - writable. The only exception is top-level paths in the Nix - store (since that directory is group-writable for the Nix build + writable. The only exception is top-level paths in the + store (since that directory is group-writable for the build users group); we check for this case below. */ if (st.st_uid != geteuid()) { #if HAVE_LCHOWN @@ -795,129 +780,62 @@ Path LocalStore::queryPathFromHashPart(const string & hashPart) }); } - -void LocalStore::setSubstituterEnv() -{ - if (didSetSubstituterEnv) return; - - /* Pass configuration options (including those overridden with - --option) to substituters. */ - setenv("_NIX_OPTIONS", settings.pack().c_str(), 1); - - didSetSubstituterEnv = true; -} - - -void LocalStore::startSubstituter(const Path & substituter, RunningSubstituter & run) -{ - if (run.disabled || run.pid != -1) return; - - debug(format("starting substituter program `%1%'") % substituter); - - Pipe toPipe, fromPipe, errorPipe; - - toPipe.create(); - fromPipe.create(); - errorPipe.create(); - - setSubstituterEnv(); - - run.pid = startProcess([&]() { - if (dup2(toPipe.readSide, STDIN_FILENO) == -1) - throw SysError("dupping stdin"); - if (dup2(fromPipe.writeSide, STDOUT_FILENO) == -1) - throw SysError("dupping stdout"); - if (dup2(errorPipe.writeSide, STDERR_FILENO) == -1) - throw SysError("dupping stderr"); - execl(substituter.c_str(), substituter.c_str(), "--query", NULL); - throw SysError(format("executing `%1%'") % substituter); - }); - - run.program = baseNameOf(substituter); - run.to = toPipe.writeSide.borrow(); - run.from = run.fromBuf.fd = fromPipe.readSide.borrow(); - run.error = errorPipe.readSide.borrow(); - - toPipe.readSide.close(); - fromPipe.writeSide.close(); - errorPipe.writeSide.close(); - - /* The substituter may exit right away if it's disabled in any way - (e.g. copy-from-other-stores.pl will exit if no other stores - are configured). */ - try { - getLineFromSubstituter(run); - } catch (EndOfFile & e) { - run.to.close(); - run.from.close(); - run.error.close(); - run.disabled = true; - if (run.pid.wait(true) != 0) throw; - } -} - - -/* Read a line from the substituter's stdout, while also processing - its stderr. */ -string LocalStore::getLineFromSubstituter(RunningSubstituter & run) +/* Read a line from the substituter's reply file descriptor, while also + processing its stderr. */ +string LocalStore::getLineFromSubstituter(Agent & run) { string res, err; - /* We might have stdout data left over from the last time. */ - if (run.fromBuf.hasData()) goto haveData; - while (1) { checkInterrupt(); fd_set fds; FD_ZERO(&fds); - FD_SET(run.from, &fds); - FD_SET(run.error, &fds); + FD_SET(run.fromAgent.readSide, &fds); + FD_SET(run.builderOut.readSide, &fds); /* Wait for data to appear on the substituter's stdout or stderr. */ - if (select(run.from > run.error ? run.from + 1 : run.error + 1, &fds, 0, 0, 0) == -1) { + if (select(std::max(run.fromAgent.readSide, run.builderOut.readSide) + 1, &fds, 0, 0, 0) == -1) { if (errno == EINTR) continue; throw SysError("waiting for input from the substituter"); } /* Completely drain stderr before dealing with stdout. */ - if (FD_ISSET(run.error, &fds)) { + if (FD_ISSET(run.fromAgent.readSide, &fds)) { char buf[4096]; - ssize_t n = read(run.error, (unsigned char *) buf, sizeof(buf)); + ssize_t n = read(run.fromAgent.readSide, (unsigned char *) buf, sizeof(buf)); if (n == -1) { if (errno == EINTR) continue; throw SysError("reading from substituter's stderr"); } - if (n == 0) throw EndOfFile(format("substituter `%1%' died unexpectedly") % run.program); + if (n == 0) throw EndOfFile(format("`%1% substitute' died unexpectedly") + % settings.guixProgram); err.append(buf, n); string::size_type p; while (((p = err.find('\n')) != string::npos) || ((p = err.find('\r')) != string::npos)) { string thing(err, 0, p + 1); - writeToStderr(run.program + ": " + thing); + writeToStderr("substitute: " + thing); err = string(err, p + 1); } } /* Read from stdout until we get a newline or the buffer is empty. */ - else if (run.fromBuf.hasData() || FD_ISSET(run.from, &fds)) { - haveData: - do { - unsigned char c; - run.fromBuf(&c, 1); - if (c == '\n') { - if (!err.empty()) printMsg(lvlError, run.program + ": " + err); - return res; - } - res += c; - } while (run.fromBuf.hasData()); + else if (FD_ISSET(run.builderOut.readSide, &fds)) { + unsigned char c; + readFull(run.builderOut.readSide, (unsigned char *) &c, 1); + if (c == '\n') { + if (!err.empty()) printMsg(lvlError, "substitute: " + err); + return res; + } + res += c; } } } -template T LocalStore::getIntLineFromSubstituter(RunningSubstituter & run) +template T LocalStore::getIntLineFromSubstituter(Agent & run) { string s = getLineFromSubstituter(run); T res; @@ -930,44 +848,49 @@ PathSet LocalStore::querySubstitutablePaths(const PathSet & paths) { PathSet res; - if (!settings.useSubstitutes) return res; - - foreach (Paths::iterator, i, settings.substituters) { - if (res.size() == paths.size()) break; - RunningSubstituter & run(runningSubstituters[*i]); - startSubstituter(*i, run); - if (run.disabled) continue; - string s = "have "; - foreach (PathSet::const_iterator, j, paths) - if (res.find(*j) == res.end()) { s += *j; s += " "; } - writeLine(run.to, s); - while (true) { - /* FIXME: we only read stderr when an error occurs, so - substituters should only write (short) messages to - stderr when they fail. I.e. they shouldn't write debug - output. */ - Path path = getLineFromSubstituter(run); - if (path == "") break; - res.insert(path); - } + if (!settings.useSubstitutes || paths.empty()) return res; + + Agent & run = *substituter(); + + string s = "have "; + foreach (PathSet::const_iterator, j, paths) + if (res.find(*j) == res.end()) { s += *j; s += " "; } + writeLine(run.toAgent.writeSide, s); + while (true) { + /* FIXME: we only read stderr when an error occurs, so + substituters should only write (short) messages to + stderr when they fail. I.e. they shouldn't write debug + output. */ + Path path = getLineFromSubstituter(run); + if (path == "") break; + res.insert(path); } + return res; } -void LocalStore::querySubstitutablePathInfos(const Path & substituter, - PathSet & paths, SubstitutablePathInfos & infos) +std::shared_ptr LocalStore::substituter() +{ + if (!runningSubstituter) { + const Strings args = { "substitute", "--query" }; + const std::map env = { { "_NIX_OPTIONS", settings.pack() } }; + runningSubstituter = std::make_shared(settings.guixProgram, args, env); + } + + return runningSubstituter; +} + +void LocalStore::querySubstitutablePathInfos(PathSet & paths, SubstitutablePathInfos & infos) { if (!settings.useSubstitutes) return; - RunningSubstituter & run(runningSubstituters[substituter]); - startSubstituter(substituter, run); - if (run.disabled) return; + Agent & run = *substituter(); string s = "info "; foreach (PathSet::const_iterator, i, paths) if (infos.find(*i) == infos.end()) { s += *i; s += " "; } - writeLine(run.to, s); + writeLine(run.toAgent.writeSide, s); while (true) { Path path = getLineFromSubstituter(run); @@ -993,10 +916,9 @@ void LocalStore::querySubstitutablePathInfos(const Path & substituter, void LocalStore::querySubstitutablePathInfos(const PathSet & paths, SubstitutablePathInfos & infos) { - PathSet todo = paths; - foreach (Paths::iterator, i, settings.substituters) { - if (todo.empty()) break; - querySubstitutablePathInfos(*i, todo, infos); + if (!paths.empty()) { + PathSet todo = paths; + querySubstitutablePathInfos(todo, infos); } } @@ -1222,16 +1144,91 @@ static void checkSecrecy(const Path & path) } -static std::string runAuthenticationProgram(const Strings & args) +/* Return the authentication agent, a "guix authenticate" process started + lazily. */ +static std::shared_ptr authenticationAgent() { - /* Use the 'authenticate' script from 'LIBEXECDIR/guix' or just - 'LIBEXECDIR', depending on whether we're uninstalled or not. */ - const bool installed = getenv("GUIX_UNINSTALLED") == NULL; - const string program = settings.nixLibexecDir - + (installed ? "/guix" : "") - + "/authenticate"; + static std::shared_ptr agent; + + if (!agent) { + Strings args = { "authenticate" }; + agent = std::make_shared(settings.guixProgram, args); + } - return runProgram(program, false, args); + return agent; +} + +/* Read an integer and the byte that immediately follows it from FD. Return + the integer. */ +static int readInteger(int fd) +{ + string str; + + while (1) { + char ch; + ssize_t rd = read(fd, &ch, 1); + if (rd == -1) { + if (errno != EINTR) + throw SysError("reading an integer"); + } else if (rd == 0) + throw EndOfFile("unexpected EOF reading an integer"); + else { + if (isdigit(ch)) { + str += ch; + } else { + break; + } + } + } + + return stoi(str); +} + +/* Read from FD a reply coming from 'guix authenticate'. The reply has the + form "CODE LEN:STR". CODE is an integer, where zero indicates success. + LEN specifies the length in bytes of the string that immediately + follows. */ +static std::string readAuthenticateReply(int fd) +{ + int code = readInteger(fd); + int len = readInteger(fd); + + string str; + str.resize(len); + readFull(fd, (unsigned char *) &str[0], len); + + if (code == 0) + return str; + else + throw Error(str); +} + +/* Sign HASH with the key stored in file SECRETKEY. Return the signature as a + string, or raise an exception upon error. */ +static std::string signHash(const string &secretKey, const Hash &hash) +{ + auto agent = authenticationAgent(); + auto hexHash = printHash(hash); + + writeLine(agent->toAgent.writeSide, + (format("sign %1%:%2% %3%:%4%") + % secretKey.size() % secretKey + % hexHash.size() % hexHash).str()); + + return readAuthenticateReply(agent->fromAgent.readSide); +} + +/* Verify SIGNATURE and return the base16-encoded hash over which it was + computed. */ +static std::string verifySignature(const string &signature) +{ + auto agent = authenticationAgent(); + + writeLine(agent->toAgent.writeSide, + (format("verify %1%:%2%") + % signature.size() % signature).str()); + + return readAuthenticateReply(agent->fromAgent.readSide); } void LocalStore::exportPath(const Path & path, bool sign, @@ -1273,23 +1270,10 @@ void LocalStore::exportPath(const Path & path, bool sign, writeInt(1, hashAndWriteSink); - Path tmpDir = createTempDir(); - AutoDelete delTmp(tmpDir); - Path hashFile = tmpDir + "/hash"; - writeFile(hashFile, printHash(hash)); - Path secretKey = settings.nixConfDir + "/signing-key.sec"; checkSecrecy(secretKey); - Strings args; - args.push_back("rsautl"); - args.push_back("-sign"); - args.push_back("-inkey"); - args.push_back(secretKey); - args.push_back("-in"); - args.push_back(hashFile); - - string signature = runAuthenticationProgram(args); + string signature = signHash(secretKey, hash); writeString(signature, hashAndWriteSink); @@ -1347,7 +1331,7 @@ Path LocalStore::importPath(bool requireSignature, Source & source) unsigned int magic = readInt(hashAndReadSource); if (magic != EXPORT_MAGIC) - throw Error("Nix archive cannot be imported; wrong format"); + throw Error("normalized archive cannot be imported; wrong format"); Path dstPath = readStorePath(hashAndReadSource); @@ -1368,18 +1352,7 @@ Path LocalStore::importPath(bool requireSignature, Source & source) string signature = readString(hashAndReadSource); if (requireSignature) { - Path sigFile = tmpDir + "/sig"; - writeFile(sigFile, signature); - - Strings args; - args.push_back("rsautl"); - args.push_back("-verify"); - args.push_back("-inkey"); - args.push_back(settings.nixConfDir + "/signing-key.pub"); - args.push_back("-pubin"); - args.push_back("-in"); - args.push_back(sigFile); - string hash2 = runAuthenticationProgram(args); + string hash2 = verifySignature(signature); /* Note: runProgram() throws an exception if the signature is invalid. */ @@ -1476,7 +1449,7 @@ void LocalStore::invalidatePathChecked(const Path & path) bool LocalStore::verifyStore(bool checkContents, bool repair) { - printMsg(lvlError, format("reading the Nix store...")); + printMsg(lvlError, format("reading the store...")); bool errors = false; @@ -1564,7 +1537,7 @@ void LocalStore::verifyPath(const Path & path, const PathSet & store, done.insert(path); if (!isStorePath(path)) { - printMsg(lvlError, format("path `%1%' is not in the Nix store") % path); + printMsg(lvlError, format("path `%1%' is not in the store") % path); invalidatePath(path); return; } @@ -1636,4 +1609,16 @@ void LocalStore::vacuumDB() } +void LocalStore::createUser(const std::string & userName, uid_t userId) +{ + auto dir = settings.nixStateDir + "/profiles/per-user/" + userName; + + createDirs(dir); + if (chmod(dir.c_str(), 0755) == -1) + throw SysError(format("changing permissions of directory '%s'") % dir); + if (chown(dir.c_str(), userId, -1) == -1) + throw SysError(format("changing owner of directory '%s'") % dir); +} + + }