me
/
guix
Archived
1
0
Fork 0

daemon: Use 'Agent' to spawn 'guix substitute --query'.

* nix/libstore/local-store.hh (RunningSubstituter): Remove.
(LocalStore)[runningSubstituter]: Change to unique_ptr<Agent>.
[setSubstituterEnv, didSetSubstituterEnv]: Remove.
[getLineFromSubstituter, getIntLineFromSubstituter]: Take an 'Agent'.
* nix/libstore/local-store.cc (LocalStore::~LocalStore): Remove
reference to 'runningSubstituter'.
(LocalStore::setSubstituterEnv, LocalStore::startSubstituter): Remove.
(LocalStore::getLineFromSubstituter): Adjust to 'run' being an 'Agent'.
(LocalStore::querySubstitutablePaths): Spawn substituter agent if
needed.  Adjust to 'Agent' interface.
(LocalStore::querySubstitutablePathInfos): Likewise.
* nix/libstore/build.cc (SubstitutionGoal::tryToRun): Remove call to
'setSubstituterEnv' and add 'setenv' call for "_NIX_OPTIONS" instead.
(SubstitutionGoal::finished): Remove 'readLine' call for 'dummy'.
* guix/scripts/substitute.scm (%allow-unauthenticated-substitutes?):
Remove second argument to 'make-parameter'.
(process-query): Call 'warn-about-missing-authentication'
when (%allow-unauthenticated-substitutes?) is #t.
(guix-substitute): Wrap body in 'parameterize'.  Set 'guix-warning-port'
too.  No longer exit when 'substitute-urls' returns the empty list.  No
longer print newline initially.
* tests/substitute.scm (test-quit): Parameterize 'current-error-port' to
account for the port changes in 'guix-substitute'.
master
Ludovic Courtès 2020-12-01 15:01:40 +01:00
parent 2e308238ad
commit 79c6614f58
No known key found for this signature in database
GPG Key ID: 090B11993D9AEBB5
5 changed files with 104 additions and 206 deletions

View File

@ -124,11 +124,7 @@ disabled!~%"))
;; purposes, and should be avoided otherwise. ;; purposes, and should be avoided otherwise.
(make-parameter (make-parameter
(and=> (getenv "GUIX_ALLOW_UNAUTHENTICATED_SUBSTITUTES") (and=> (getenv "GUIX_ALLOW_UNAUTHENTICATED_SUBSTITUTES")
(cut string-ci=? <> "yes")) (cut string-ci=? <> "yes"))))
(lambda (value)
(when value
(warn-about-missing-authentication))
value)))
(define %narinfo-ttl (define %narinfo-ttl
;; Number of seconds during which cached narinfo lookups are considered ;; Number of seconds during which cached narinfo lookups are considered
@ -893,6 +889,9 @@ authorized substitutes."
(define (valid? obj) (define (valid? obj)
(valid-narinfo? obj acl)) (valid-narinfo? obj acl))
(when (%allow-unauthenticated-substitutes?)
(warn-about-missing-authentication))
(match (string-tokenize command) (match (string-tokenize command)
(("have" paths ..1) (("have" paths ..1)
;; Return the subset of PATHS available in CACHE-URLS. ;; Return the subset of PATHS available in CACHE-URLS.
@ -1139,68 +1138,67 @@ default value."
((= string->number number) (> number 0)) ((= string->number number) (> number 0))
(_ #f))) (_ #f)))
(mkdir-p %narinfo-cache-directory) ;; The daemon's agent code opens file descriptor 4 for us and this is where
(maybe-remove-expired-cache-entries %narinfo-cache-directory ;; stderr should go.
cached-narinfo-files (parameterize ((current-error-port (match args
#:entry-expiration (("--query") (fdopen 4 "wl"))
cached-narinfo-expiration-time (_ (current-error-port)))))
#:cleanup-period ;; Redirect diagnostics to file descriptor 4 as well.
%narinfo-expired-cache-entry-removal-delay) (guix-warning-port (current-error-port))
(check-acl-initialized)
;; Starting from commit 22144afa in Nix, we are allowed to bail out directly (mkdir-p %narinfo-cache-directory)
;; when we know we cannot substitute, but we must emit a newline on stdout (maybe-remove-expired-cache-entries %narinfo-cache-directory
;; when everything is alright. cached-narinfo-files
(when (null? (substitute-urls)) #:entry-expiration
(exit 0)) cached-narinfo-expiration-time
#:cleanup-period
%narinfo-expired-cache-entry-removal-delay)
(check-acl-initialized)
;; Say hello (see above.) ;; Sanity-check SUBSTITUTE-URLS so we can provide a meaningful error
(newline) ;; message.
(force-output (current-output-port)) (for-each validate-uri (substitute-urls))
;; Sanity-check SUBSTITUTE-URLS so we can provide a meaningful error message. ;; Attempt to install the client's locale so that messages are suitably
(for-each validate-uri (substitute-urls)) ;; translated. LC_CTYPE must be a UTF-8 locale; it's the case by default
;; so don't change it.
(match (or (find-daemon-option "untrusted-locale")
(find-daemon-option "locale"))
(#f #f)
(locale (false-if-exception (setlocale LC_MESSAGES locale))))
;; Attempt to install the client's locale so that messages are suitably (catch 'system-error
;; translated. LC_CTYPE must be a UTF-8 locale; it's the case by default so (lambda ()
;; don't change it. (set-thread-name "guix substitute"))
(match (or (find-daemon-option "untrusted-locale") (const #t)) ;GNU/Hurd lacks 'prctl'
(find-daemon-option "locale"))
(#f #f)
(locale (false-if-exception (setlocale LC_MESSAGES locale))))
(catch 'system-error (with-networking
(lambda () (with-error-handling ; for signature errors
(set-thread-name "guix substitute")) (match args
(const #t)) ;GNU/Hurd lacks 'prctl' (("--query")
(let ((acl (current-acl)))
(with-networking (let loop ((command (read-line)))
(with-error-handling ; for signature errors (or (eof-object? command)
(match args (begin
(("--query") (process-query command
(let ((acl (current-acl))) #:cache-urls (substitute-urls)
(let loop ((command (read-line))) #:acl acl)
(or (eof-object? command) (loop (read-line)))))))
(begin (("--substitute" store-path destination)
(process-query command ;; Download STORE-PATH and store it as a Nar in file DESTINATION.
#:cache-urls (substitute-urls) ;; Specify the number of columns of the terminal so the progress
#:acl acl) ;; report displays nicely.
(loop (read-line))))))) (parameterize ((current-terminal-columns (client-terminal-columns)))
(("--substitute" store-path destination) (process-substitution store-path destination
;; Download STORE-PATH and add store it as a Nar in file DESTINATION. #:cache-urls (substitute-urls)
;; Specify the number of columns of the terminal so the progress #:acl (current-acl)
;; report displays nicely. #:print-build-trace? print-build-trace?)))
(parameterize ((current-terminal-columns (client-terminal-columns))) ((or ("-V") ("--version"))
(process-substitution store-path destination (show-version-and-exit "guix substitute"))
#:cache-urls (substitute-urls) (("--help")
#:acl (current-acl) (show-help))
#:print-build-trace? print-build-trace?))) (opts
((or ("-V") ("--version")) (leave (G_ "~a: unrecognized options~%") opts)))))))
(show-version-and-exit "guix substitute"))
(("--help")
(show-help))
(opts
(leave (G_ "~a: unrecognized options~%") opts))))))
;;; Local Variables: ;;; Local Variables:
;;; eval: (put 'with-timeout 'scheme-indent-function 1) ;;; eval: (put 'with-timeout 'scheme-indent-function 1)

View File

@ -2986,8 +2986,6 @@ void SubstitutionGoal::tryToRun()
if (pathExists(destPath)) if (pathExists(destPath))
deletePath(destPath); deletePath(destPath);
worker.store.setSubstituterEnv();
/* Fill in the arguments. */ /* Fill in the arguments. */
Strings args; Strings args;
args.push_back("guix"); args.push_back("guix");
@ -2999,6 +2997,9 @@ void SubstitutionGoal::tryToRun()
/* Fork the substitute program. */ /* Fork the substitute program. */
pid = startProcess([&]() { pid = startProcess([&]() {
/* Communicate substitute-urls & co. to 'guix substitute'. */
setenv("_NIX_OPTIONS", settings.pack().c_str(), 1);
commonChildInit(logPipe); commonChildInit(logPipe);
if (dup2(outPipe.writeSide, STDOUT_FILENO) == -1) if (dup2(outPipe.writeSide, STDOUT_FILENO) == -1)
@ -3041,7 +3042,6 @@ void SubstitutionGoal::finished()
logPipe.readSide.close(); logPipe.readSide.close();
/* Get the hash info from stdout. */ /* Get the hash info from stdout. */
string dummy = readLine(outPipe.readSide);
string expectedHashStr = statusOk(status) ? readLine(outPipe.readSide) : ""; string expectedHashStr = statusOk(status) ? readLine(outPipe.readSide) : "";
outPipe.readSide.close(); outPipe.readSide.close();

View File

@ -57,7 +57,6 @@ void checkStoreNotSymlink()
LocalStore::LocalStore(bool reserveSpace) LocalStore::LocalStore(bool reserveSpace)
: didSetSubstituterEnv(false)
{ {
schemaPath = settings.nixDBPath + "/schema"; schemaPath = settings.nixDBPath + "/schema";
@ -182,21 +181,6 @@ LocalStore::LocalStore(bool reserveSpace)
LocalStore::~LocalStore() LocalStore::~LocalStore()
{ {
try {
if (runningSubstituter) {
RunningSubstituter &i = *runningSubstituter;
if (!i.disabled) {
i.to.close();
i.from.close();
i.error.close();
if (i.pid != -1)
i.pid.wait(true);
}
}
} catch (...) {
ignoreException();
}
try { try {
if (fdTempRoots != -1) { if (fdTempRoots != -1) {
fdTempRoots.close(); fdTempRoots.close();
@ -796,96 +780,31 @@ 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(RunningSubstituter & run)
{
if (run.disabled || run.pid != -1) return;
debug(format("starting substituter program `%1% substitute'")
% settings.guixProgram);
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(settings.guixProgram.c_str(), "guix", "substitute", "--query", NULL);
throw SysError(format("executing `%1%'") % settings.guixProgram);
});
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 /* Read a line from the substituter's stdout, while also processing
its stderr. */ its stderr. */
string LocalStore::getLineFromSubstituter(RunningSubstituter & run) string LocalStore::getLineFromSubstituter(Agent & run)
{ {
string res, err; string res, err;
/* We might have stdout data left over from the last time. */
if (run.fromBuf.hasData()) goto haveData;
while (1) { while (1) {
checkInterrupt(); checkInterrupt();
fd_set fds; fd_set fds;
FD_ZERO(&fds); FD_ZERO(&fds);
FD_SET(run.from, &fds); FD_SET(run.fromAgent.readSide, &fds);
FD_SET(run.error, &fds); FD_SET(run.builderOut.readSide, &fds);
/* Wait for data to appear on the substituter's stdout or /* Wait for data to appear on the substituter's stdout or
stderr. */ 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; if (errno == EINTR) continue;
throw SysError("waiting for input from the substituter"); throw SysError("waiting for input from the substituter");
} }
/* Completely drain stderr before dealing with stdout. */ /* Completely drain stderr before dealing with stdout. */
if (FD_ISSET(run.error, &fds)) { if (FD_ISSET(run.builderOut.readSide, &fds)) {
char buf[4096]; char buf[4096];
ssize_t n = read(run.error, (unsigned char *) buf, sizeof(buf)); ssize_t n = read(run.builderOut.readSide, (unsigned char *) buf, sizeof(buf));
if (n == -1) { if (n == -1) {
if (errno == EINTR) continue; if (errno == EINTR) continue;
throw SysError("reading from substituter's stderr"); throw SysError("reading from substituter's stderr");
@ -903,23 +822,20 @@ string LocalStore::getLineFromSubstituter(RunningSubstituter & run)
} }
/* Read from stdout until we get a newline or the buffer is empty. */ /* Read from stdout until we get a newline or the buffer is empty. */
else if (run.fromBuf.hasData() || FD_ISSET(run.from, &fds)) { else if (FD_ISSET(run.fromAgent.readSide, &fds)) {
haveData: unsigned char c;
do { readFull(run.fromAgent.readSide, (unsigned char *) &c, 1);
unsigned char c; if (c == '\n') {
run.fromBuf(&c, 1); if (!err.empty()) printMsg(lvlError, "substitute: " + err);
if (c == '\n') { return res;
if (!err.empty()) printMsg(lvlError, "substitute: " + err); }
return res; res += c;
}
res += c;
} while (run.fromBuf.hasData());
} }
} }
} }
template<class T> T LocalStore::getIntLineFromSubstituter(RunningSubstituter & run) template<class T> T LocalStore::getIntLineFromSubstituter(Agent & run)
{ {
string s = getLineFromSubstituter(run); string s = getLineFromSubstituter(run);
T res; T res;
@ -935,27 +851,26 @@ PathSet LocalStore::querySubstitutablePaths(const PathSet & paths)
if (!settings.useSubstitutes || paths.empty()) return res; if (!settings.useSubstitutes || paths.empty()) return res;
if (!runningSubstituter) { if (!runningSubstituter) {
std::unique_ptr<RunningSubstituter>fresh(new RunningSubstituter); const Strings args = { "substitute", "--query" };
const std::map<string, string> env = { { "_NIX_OPTIONS", settings.pack() } };
std::unique_ptr<Agent> fresh(new Agent(settings.guixProgram, args, env));
runningSubstituter.swap(fresh); runningSubstituter.swap(fresh);
} }
RunningSubstituter & run = *runningSubstituter; Agent & run = *runningSubstituter;
startSubstituter(run);
if (!run.disabled) { string s = "have ";
string s = "have "; foreach (PathSet::const_iterator, j, paths)
foreach (PathSet::const_iterator, j, paths) if (res.find(*j) == res.end()) { s += *j; s += " "; }
if (res.find(*j) == res.end()) { s += *j; s += " "; } writeLine(run.toAgent.writeSide, s);
writeLine(run.to, s); while (true) {
while (true) { /* FIXME: we only read stderr when an error occurs, so
/* FIXME: we only read stderr when an error occurs, so substituters should only write (short) messages to
substituters should only write (short) messages to stderr when they fail. I.e. they shouldn't write debug
stderr when they fail. I.e. they shouldn't write debug output. */
output. */ Path path = getLineFromSubstituter(run);
Path path = getLineFromSubstituter(run); if (path == "") break;
if (path == "") break; res.insert(path);
res.insert(path);
}
} }
return res; return res;
@ -967,18 +882,18 @@ void LocalStore::querySubstitutablePathInfos(PathSet & paths, SubstitutablePathI
if (!settings.useSubstitutes) return; if (!settings.useSubstitutes) return;
if (!runningSubstituter) { if (!runningSubstituter) {
std::unique_ptr<RunningSubstituter>fresh(new RunningSubstituter); const Strings args = { "substitute", "--query" };
const std::map<string, string> env = { { "_NIX_OPTIONS", settings.pack() } };
std::unique_ptr<Agent> fresh(new Agent(settings.guixProgram, args, env));
runningSubstituter.swap(fresh); runningSubstituter.swap(fresh);
} }
RunningSubstituter & run = *runningSubstituter; Agent & run = *runningSubstituter;
startSubstituter(run);
if (run.disabled) return;
string s = "info "; string s = "info ";
foreach (PathSet::const_iterator, i, paths) foreach (PathSet::const_iterator, i, paths)
if (infos.find(*i) == infos.end()) { s += *i; s += " "; } if (infos.find(*i) == infos.end()) { s += *i; s += " "; }
writeLine(run.to, s); writeLine(run.toAgent.writeSide, s);
while (true) { while (true) {
Path path = getLineFromSubstituter(run); Path path = getLineFromSubstituter(run);

View File

@ -38,21 +38,11 @@ struct OptimiseStats
}; };
struct RunningSubstituter
{
Pid pid;
AutoCloseFD to, from, error;
FdSource fromBuf;
bool disabled;
RunningSubstituter() : disabled(false) { };
};
class LocalStore : public StoreAPI class LocalStore : public StoreAPI
{ {
private: private:
/* The currently running substituter or empty. */ /* The currently running substituter or empty. */
std::unique_ptr<RunningSubstituter> runningSubstituter; std::unique_ptr<Agent> runningSubstituter;
Path linksDir; Path linksDir;
@ -178,8 +168,6 @@ public:
void markContentsGood(const Path & path); void markContentsGood(const Path & path);
void setSubstituterEnv();
void createUser(const std::string & userName, uid_t userId); void createUser(const std::string & userName, uid_t userId);
private: private:
@ -213,8 +201,6 @@ private:
/* Cache for pathContentsGood(). */ /* Cache for pathContentsGood(). */
std::map<Path, bool> pathContentsGoodCache; std::map<Path, bool> pathContentsGoodCache;
bool didSetSubstituterEnv;
/* The file to which we write our temporary roots. */ /* The file to which we write our temporary roots. */
Path fnTempRoots; Path fnTempRoots;
AutoCloseFD fdTempRoots; AutoCloseFD fdTempRoots;
@ -262,11 +248,9 @@ private:
void removeUnusedLinks(const GCState & state); void removeUnusedLinks(const GCState & state);
void startSubstituter(RunningSubstituter & runningSubstituter); string getLineFromSubstituter(Agent & run);
string getLineFromSubstituter(RunningSubstituter & run); template<class T> T getIntLineFromSubstituter(Agent & run);
template<class T> T getIntLineFromSubstituter(RunningSubstituter & run);
Path createTempDirInStore(); Path createTempDirInStore();

View File

@ -47,7 +47,8 @@ it writes to GUIX-WARNING-PORT a messages that matches ERROR-RX."
(test-equal name (test-equal name
'(1 #t) '(1 #t)
(let ((error-output (open-output-string))) (let ((error-output (open-output-string)))
(parameterize ((guix-warning-port error-output)) (parameterize ((current-error-port error-output)
(guix-warning-port error-output))
(catch 'quit (catch 'quit
(lambda () (lambda ()
exp exp