1 #include "crypto.hh"
2 #include "globals.hh"
3 #include "store-api.hh"
4 #include "util.hh"
5 #include "nar-info-disk-cache.hh"
6 #include "thread-pool.hh"
7 #include "json.hh"
8 #include "derivations.hh"
9
10 #include <future>
11
12
13 namespace nix {
14
15
isInStore(const Path & path) const16 bool Store::isInStore(const Path & path) const
17 {
18 return isInDir(path, storeDir);
19 }
20
21
isStorePath(const Path & path) const22 bool Store::isStorePath(const Path & path) const
23 {
24 return isInStore(path)
25 && path.size() >= storeDir.size() + 1 + storePathHashLen
26 && path.find('/', storeDir.size() + 1) == Path::npos;
27 }
28
29
assertStorePath(const Path & path) const30 void Store::assertStorePath(const Path & path) const
31 {
32 if (!isStorePath(path))
33 throw Error(format("path '%1%' is not in the Nix store") % path);
34 }
35
36
toStorePath(const Path & path) const37 Path Store::toStorePath(const Path & path) const
38 {
39 if (!isInStore(path))
40 throw Error(format("path '%1%' is not in the Nix store") % path);
41 Path::size_type slash = path.find('/', storeDir.size() + 1);
42 if (slash == Path::npos)
43 return path;
44 else
45 return Path(path, 0, slash);
46 }
47
48
followLinksToStore(const Path & _path) const49 Path Store::followLinksToStore(const Path & _path) const
50 {
51 Path path = absPath(_path);
52 while (!isInStore(path)) {
53 if (!isLink(path)) break;
54 string target = readLink(path);
55 path = absPath(target, dirOf(path));
56 }
57 if (!isInStore(path))
58 throw Error(format("path '%1%' is not in the Nix store") % path);
59 return path;
60 }
61
62
followLinksToStorePath(const Path & path) const63 Path Store::followLinksToStorePath(const Path & path) const
64 {
65 return toStorePath(followLinksToStore(path));
66 }
67
68
storePathToName(const Path & path)69 string storePathToName(const Path & path)
70 {
71 auto base = baseNameOf(path);
72 assert(base.size() == storePathHashLen || (base.size() > storePathHashLen && base[storePathHashLen] == '-'));
73 return base.size() == storePathHashLen ? "" : string(base, storePathHashLen + 1);
74 }
75
76
storePathToHash(const Path & path)77 string storePathToHash(const Path & path)
78 {
79 auto base = baseNameOf(path);
80 assert(base.size() >= storePathHashLen);
81 return string(base, 0, storePathHashLen);
82 }
83
84
checkStoreName(const string & name)85 void checkStoreName(const string & name)
86 {
87 string validChars = "+-._?=";
88
89 auto baseError = format("The path name '%2%' is invalid: %3%. "
90 "Path names are alphanumeric and can include the symbols %1% "
91 "and must not begin with a period. "
92 "Note: If '%2%' is a source file and you cannot rename it on "
93 "disk, builtins.path { name = ... } can be used to give it an "
94 "alternative name.") % validChars % name;
95
96 /* Disallow names starting with a dot for possible security
97 reasons (e.g., "." and ".."). */
98 if (string(name, 0, 1) == ".")
99 throw Error(baseError % "it is illegal to start the name with a period");
100 /* Disallow names longer than 211 characters. ext4’s max is 256,
101 but we need extra space for the hash and .chroot extensions. */
102 if (name.length() > 211)
103 throw Error(baseError % "name must be less than 212 characters");
104 for (auto & i : name)
105 if (!((i >= 'A' && i <= 'Z') ||
106 (i >= 'a' && i <= 'z') ||
107 (i >= '0' && i <= '9') ||
108 validChars.find(i) != string::npos))
109 {
110 throw Error(baseError % (format("the '%1%' character is invalid") % i));
111 }
112 }
113
114
115 /* Store paths have the following form:
116
117 <store>/<h>-<name>
118
119 where
120
121 <store> = the location of the Nix store, usually /nix/store
122
123 <name> = a human readable name for the path, typically obtained
124 from the name attribute of the derivation, or the name of the
125 source file from which the store path is created. For derivation
126 outputs other than the default "out" output, the string "-<id>"
127 is suffixed to <name>.
128
129 <h> = base-32 representation of the first 160 bits of a SHA-256
130 hash of <s>; the hash part of the store name
131
132 <s> = the string "<type>:sha256:<h2>:<store>:<name>";
133 note that it includes the location of the store as well as the
134 name to make sure that changes to either of those are reflected
135 in the hash (e.g. you won't get /nix/store/<h>-name1 and
136 /nix/store/<h>-name2 with equal hash parts).
137
138 <type> = one of:
139 "text:<r1>:<r2>:...<rN>"
140 for plain text files written to the store using
141 addTextToStore(); <r1> ... <rN> are the references of the
142 path.
143 "source"
144 for paths copied to the store using addToStore() when recursive
145 = true and hashAlgo = "sha256"
146 "output:<id>"
147 for either the outputs created by derivations, OR paths copied
148 to the store using addToStore() with recursive != true or
149 hashAlgo != "sha256" (in that case "source" is used; it's
150 silly, but it's done that way for compatibility). <id> is the
151 name of the output (usually, "out").
152
153 <h2> = base-16 representation of a SHA-256 hash of:
154 if <type> = "text:...":
155 the string written to the resulting store path
156 if <type> = "source":
157 the serialisation of the path from which this store path is
158 copied, as returned by hashPath()
159 if <type> = "output:<id>":
160 for non-fixed derivation outputs:
161 the derivation (see hashDerivationModulo() in
162 primops.cc)
163 for paths copied by addToStore() or produced by fixed-output
164 derivations:
165 the string "fixed:out:<rec><algo>:<hash>:", where
166 <rec> = "r:" for recursive (path) hashes, or "" for flat
167 (file) hashes
168 <algo> = "md5", "sha1" or "sha256"
169 <hash> = base-16 representation of the path or flat hash of
170 the contents of the path (or expected contents of the
171 path for fixed-output derivations)
172
173 It would have been nicer to handle fixed-output derivations under
174 "source", e.g. have something like "source:<rec><algo>", but we're
175 stuck with this for now...
176
177 The main reason for this way of computing names is to prevent name
178 collisions (for security). For instance, it shouldn't be feasible
179 to come up with a derivation whose output path collides with the
180 path for a copied source. The former would have a <s> starting with
181 "output:out:", while the latter would have a <s> starting with
182 "source:".
183 */
184
185
makeStorePath(const string & type,const Hash & hash,const string & name) const186 Path Store::makeStorePath(const string & type,
187 const Hash & hash, const string & name) const
188 {
189 /* e.g., "source:sha256:1abc...:/nix/store:foo.tar.gz" */
190 string s = type + ":" + hash.to_string(Base16) + ":" + storeDir + ":" + name;
191
192 checkStoreName(name);
193
194 return storeDir + "/"
195 + compressHash(hashString(htSHA256, s), 20).to_string(Base32, false)
196 + "-" + name;
197 }
198
199
makeOutputPath(const string & id,const Hash & hash,const string & name) const200 Path Store::makeOutputPath(const string & id,
201 const Hash & hash, const string & name) const
202 {
203 return makeStorePath("output:" + id, hash,
204 name + (id == "out" ? "" : "-" + id));
205 }
206
207
makeFixedOutputPath(bool recursive,const Hash & hash,const string & name) const208 Path Store::makeFixedOutputPath(bool recursive,
209 const Hash & hash, const string & name) const
210 {
211 return hash.type == htSHA256 && recursive
212 ? makeStorePath("source", hash, name)
213 : makeStorePath("output:out", hashString(htSHA256,
214 "fixed:out:" + (recursive ? (string) "r:" : "") +
215 hash.to_string(Base16) + ":"),
216 name);
217 }
218
219
makeTextPath(const string & name,const Hash & hash,const PathSet & references) const220 Path Store::makeTextPath(const string & name, const Hash & hash,
221 const PathSet & references) const
222 {
223 assert(hash.type == htSHA256);
224 /* Stuff the references (if any) into the type. This is a bit
225 hacky, but we can't put them in `s' since that would be
226 ambiguous. */
227 string type = "text";
228 for (auto & i : references) {
229 type += ":";
230 type += i;
231 }
232 return makeStorePath(type, hash, name);
233 }
234
235
computeStorePathForPath(const string & name,const Path & srcPath,bool recursive,HashType hashAlgo,PathFilter & filter) const236 std::pair<Path, Hash> Store::computeStorePathForPath(const string & name,
237 const Path & srcPath, bool recursive, HashType hashAlgo, PathFilter & filter) const
238 {
239 Hash h = recursive ? hashPath(hashAlgo, srcPath, filter).first : hashFile(hashAlgo, srcPath);
240 Path dstPath = makeFixedOutputPath(recursive, h, name);
241 return std::pair<Path, Hash>(dstPath, h);
242 }
243
244
computeStorePathForText(const string & name,const string & s,const PathSet & references) const245 Path Store::computeStorePathForText(const string & name, const string & s,
246 const PathSet & references) const
247 {
248 return makeTextPath(name, hashString(htSHA256, s), references);
249 }
250
251
Store(const Params & params)252 Store::Store(const Params & params)
253 : Config(params)
254 , state({(size_t) pathInfoCacheSize})
255 {
256 }
257
258
getUri()259 std::string Store::getUri()
260 {
261 return "";
262 }
263
isKnownNow()264 bool Store::PathInfoCacheValue::isKnownNow()
265 {
266 std::chrono::duration ttl = didExist()
267 ? std::chrono::seconds(settings.ttlPositiveNarInfoCache)
268 : std::chrono::seconds(settings.ttlNegativeNarInfoCache);
269
270 return std::chrono::steady_clock::now() < time_point + ttl;
271 }
272
273
isValidPath(const Path & storePath)274 bool Store::isValidPath(const Path & storePath)
275 {
276 assertStorePath(storePath);
277
278 auto hashPart = storePathToHash(storePath);
279
280 {
281 auto state_(state.lock());
282 auto res = state_->pathInfoCache.get(hashPart);
283 if (res && res->isKnownNow()) {
284 stats.narInfoReadAverted++;
285 return res->didExist();
286 }
287 }
288
289 if (diskCache) {
290 auto res = diskCache->lookupNarInfo(getUri(), hashPart);
291 if (res.first != NarInfoDiskCache::oUnknown) {
292 stats.narInfoReadAverted++;
293 auto state_(state.lock());
294 state_->pathInfoCache.upsert(hashPart,
295 res.first == NarInfoDiskCache::oInvalid ? 0 : PathInfoCacheValue(res.second));
296 return res.first == NarInfoDiskCache::oValid;
297 }
298 }
299
300 bool valid = isValidPathUncached(storePath);
301
302 if (diskCache && !valid)
303 // FIXME: handle valid = true case.
304 diskCache->upsertNarInfo(getUri(), hashPart, 0);
305
306 return valid;
307 }
308
309
310 /* Default implementation for stores that only implement
311 queryPathInfoUncached(). */
isValidPathUncached(const Path & path)312 bool Store::isValidPathUncached(const Path & path)
313 {
314 try {
315 queryPathInfo(path);
316 return true;
317 } catch (InvalidPath &) {
318 return false;
319 }
320 }
321
322
queryPathInfo(const Path & storePath)323 ref<const ValidPathInfo> Store::queryPathInfo(const Path & storePath)
324 {
325 std::promise<ref<ValidPathInfo>> promise;
326
327 queryPathInfo(storePath,
328 {[&](std::future<ref<ValidPathInfo>> result) {
329 try {
330 promise.set_value(result.get());
331 } catch (...) {
332 promise.set_exception(std::current_exception());
333 }
334 }});
335
336 return promise.get_future().get();
337 }
338
339
queryPathInfo(const Path & storePath,Callback<ref<ValidPathInfo>> callback)340 void Store::queryPathInfo(const Path & storePath,
341 Callback<ref<ValidPathInfo>> callback) noexcept
342 {
343 std::string hashPart;
344
345 try {
346 assertStorePath(storePath);
347
348 hashPart = storePathToHash(storePath);
349
350 {
351 auto res = state.lock()->pathInfoCache.get(hashPart);
352 if (res && res->isKnownNow()) {
353 stats.narInfoReadAverted++;
354 if (!res->didExist())
355 throw InvalidPath(format("path '%s' is not valid") % storePath);
356 return callback(ref<ValidPathInfo>(res->value));
357 }
358 }
359
360 if (diskCache) {
361 auto res = diskCache->lookupNarInfo(getUri(), hashPart);
362 if (res.first != NarInfoDiskCache::oUnknown) {
363 stats.narInfoReadAverted++;
364 {
365 auto state_(state.lock());
366 state_->pathInfoCache.upsert(hashPart,
367 res.first == NarInfoDiskCache::oInvalid ? 0 : PathInfoCacheValue(res.second));
368 if (res.first == NarInfoDiskCache::oInvalid ||
369 (res.second->path != storePath && storePathToName(storePath) != ""))
370 throw InvalidPath(format("path '%s' is not valid") % storePath);
371 }
372 return callback(ref<ValidPathInfo>(res.second));
373 }
374 }
375
376 } catch (...) { return callback.rethrow(); }
377
378 auto callbackPtr = std::make_shared<decltype(callback)>(std::move(callback));
379
380 queryPathInfoUncached(storePath,
381 {[this, storePath, hashPart, callbackPtr](std::future<std::shared_ptr<ValidPathInfo>> fut) {
382
383 try {
384 auto info = fut.get();
385
386 if (diskCache)
387 diskCache->upsertNarInfo(getUri(), hashPart, info);
388
389 {
390 auto state_(state.lock());
391 state_->pathInfoCache.upsert(hashPart, info);
392 }
393
394 if (!info
395 || (info->path != storePath && storePathToName(storePath) != ""))
396 {
397 stats.narInfoMissing++;
398 throw InvalidPath("path '%s' is not valid", storePath);
399 }
400
401 (*callbackPtr)(ref<ValidPathInfo>(info));
402 } catch (...) { callbackPtr->rethrow(); }
403 }});
404 }
405
406
queryValidPaths(const PathSet & paths,SubstituteFlag maybeSubstitute)407 PathSet Store::queryValidPaths(const PathSet & paths, SubstituteFlag maybeSubstitute)
408 {
409 struct State
410 {
411 size_t left;
412 PathSet valid;
413 std::exception_ptr exc;
414 };
415
416 Sync<State> state_(State{paths.size(), PathSet()});
417
418 std::condition_variable wakeup;
419 ThreadPool pool;
420
421 auto doQuery = [&](const Path & path ) {
422 checkInterrupt();
423 queryPathInfo(path, {[path, &state_, &wakeup](std::future<ref<ValidPathInfo>> fut) {
424 auto state(state_.lock());
425 try {
426 auto info = fut.get();
427 state->valid.insert(path);
428 } catch (InvalidPath &) {
429 } catch (...) {
430 state->exc = std::current_exception();
431 }
432 assert(state->left);
433 if (!--state->left)
434 wakeup.notify_one();
435 }});
436 };
437
438 for (auto & path : paths)
439 pool.enqueue(std::bind(doQuery, path));
440
441 pool.process();
442
443 while (true) {
444 auto state(state_.lock());
445 if (!state->left) {
446 if (state->exc) std::rethrow_exception(state->exc);
447 return state->valid;
448 }
449 state.wait(wakeup);
450 }
451 }
452
453
454 /* Return a string accepted by decodeValidPathInfo() that
455 registers the specified paths as valid. Note: it's the
456 responsibility of the caller to provide a closure. */
makeValidityRegistration(const PathSet & paths,bool showDerivers,bool showHash)457 string Store::makeValidityRegistration(const PathSet & paths,
458 bool showDerivers, bool showHash)
459 {
460 string s = "";
461
462 for (auto & i : paths) {
463 s += i + "\n";
464
465 auto info = queryPathInfo(i);
466
467 if (showHash) {
468 s += info->narHash.to_string(Base16, false) + "\n";
469 s += (format("%1%\n") % info->narSize).str();
470 }
471
472 Path deriver = showDerivers ? info->deriver : "";
473 s += deriver + "\n";
474
475 s += (format("%1%\n") % info->references.size()).str();
476
477 for (auto & j : info->references)
478 s += j + "\n";
479 }
480
481 return s;
482 }
483
484
pathInfoToJSON(JSONPlaceholder & jsonOut,const PathSet & storePaths,bool includeImpureInfo,bool showClosureSize,AllowInvalidFlag allowInvalid)485 void Store::pathInfoToJSON(JSONPlaceholder & jsonOut, const PathSet & storePaths,
486 bool includeImpureInfo, bool showClosureSize, AllowInvalidFlag allowInvalid)
487 {
488 auto jsonList = jsonOut.list();
489
490 for (auto storePath : storePaths) {
491 auto jsonPath = jsonList.object();
492 jsonPath.attr("path", storePath);
493
494 try {
495 auto info = queryPathInfo(storePath);
496 storePath = info->path;
497
498 jsonPath
499 .attr("narHash", info->narHash.to_string())
500 .attr("narSize", info->narSize);
501
502 {
503 auto jsonRefs = jsonPath.list("references");
504 for (auto & ref : info->references)
505 jsonRefs.elem(ref);
506 }
507
508 if (info->ca != "")
509 jsonPath.attr("ca", info->ca);
510
511 std::pair<uint64_t, uint64_t> closureSizes;
512
513 if (showClosureSize) {
514 closureSizes = getClosureSize(storePath);
515 jsonPath.attr("closureSize", closureSizes.first);
516 }
517
518 if (includeImpureInfo) {
519
520 if (info->deriver != "")
521 jsonPath.attr("deriver", info->deriver);
522
523 if (info->registrationTime)
524 jsonPath.attr("registrationTime", info->registrationTime);
525
526 if (info->ultimate)
527 jsonPath.attr("ultimate", info->ultimate);
528
529 if (!info->sigs.empty()) {
530 auto jsonSigs = jsonPath.list("signatures");
531 for (auto & sig : info->sigs)
532 jsonSigs.elem(sig);
533 }
534
535 auto narInfo = std::dynamic_pointer_cast<const NarInfo>(
536 std::shared_ptr<const ValidPathInfo>(info));
537
538 if (narInfo) {
539 if (!narInfo->url.empty())
540 jsonPath.attr("url", narInfo->url);
541 if (narInfo->fileHash)
542 jsonPath.attr("downloadHash", narInfo->fileHash.to_string());
543 if (narInfo->fileSize)
544 jsonPath.attr("downloadSize", narInfo->fileSize);
545 if (showClosureSize)
546 jsonPath.attr("closureDownloadSize", closureSizes.second);
547 }
548 }
549
550 } catch (InvalidPath &) {
551 jsonPath.attr("valid", false);
552 }
553 }
554 }
555
556
getClosureSize(const Path & storePath)557 std::pair<uint64_t, uint64_t> Store::getClosureSize(const Path & storePath)
558 {
559 uint64_t totalNarSize = 0, totalDownloadSize = 0;
560 PathSet closure;
561 computeFSClosure(storePath, closure, false, false);
562 for (auto & p : closure) {
563 auto info = queryPathInfo(p);
564 totalNarSize += info->narSize;
565 auto narInfo = std::dynamic_pointer_cast<const NarInfo>(
566 std::shared_ptr<const ValidPathInfo>(info));
567 if (narInfo)
568 totalDownloadSize += narInfo->fileSize;
569 }
570 return {totalNarSize, totalDownloadSize};
571 }
572
573
getStats()574 const Store::Stats & Store::getStats()
575 {
576 {
577 auto state_(state.lock());
578 stats.pathInfoCacheSize = state_->pathInfoCache.size();
579 }
580 return stats;
581 }
582
583
buildPaths(const PathSet & paths,BuildMode buildMode)584 void Store::buildPaths(const PathSet & paths, BuildMode buildMode)
585 {
586 for (auto & path : paths)
587 if (isDerivation(path))
588 unsupported("buildPaths");
589
590 if (queryValidPaths(paths).size() != paths.size())
591 unsupported("buildPaths");
592 }
593
594
copyStorePath(ref<Store> srcStore,ref<Store> dstStore,const Path & storePath,RepairFlag repair,CheckSigsFlag checkSigs)595 void copyStorePath(ref<Store> srcStore, ref<Store> dstStore,
596 const Path & storePath, RepairFlag repair, CheckSigsFlag checkSigs)
597 {
598 auto srcUri = srcStore->getUri();
599 auto dstUri = dstStore->getUri();
600
601 Activity act(*logger, lvlInfo, actCopyPath,
602 srcUri == "local" || srcUri == "daemon"
603 ? fmt("copying path '%s' to '%s'", storePath, dstUri)
604 : dstUri == "local" || dstUri == "daemon"
605 ? fmt("copying path '%s' from '%s'", storePath, srcUri)
606 : fmt("copying path '%s' from '%s' to '%s'", storePath, srcUri, dstUri),
607 {storePath, srcUri, dstUri});
608 PushActivity pact(act.id);
609
610 auto info = srcStore->queryPathInfo(storePath);
611
612 uint64_t total = 0;
613
614 if (!info->narHash) {
615 StringSink sink;
616 srcStore->narFromPath({storePath}, sink);
617 auto info2 = make_ref<ValidPathInfo>(*info);
618 info2->narHash = hashString(htSHA256, *sink.s);
619 if (!info->narSize) info2->narSize = sink.s->size();
620 if (info->ultimate) info2->ultimate = false;
621 info = info2;
622
623 StringSource source(*sink.s);
624 dstStore->addToStore(*info, source, repair, checkSigs);
625 return;
626 }
627
628 if (info->ultimate) {
629 auto info2 = make_ref<ValidPathInfo>(*info);
630 info2->ultimate = false;
631 info = info2;
632 }
633
634 auto source = sinkToSource([&](Sink & sink) {
635 LambdaSink wrapperSink([&](const unsigned char * data, size_t len) {
636 sink(data, len);
637 total += len;
638 act.progress(total, info->narSize);
639 });
640 srcStore->narFromPath({storePath}, wrapperSink);
641 }, [&]() {
642 throw EndOfFile("NAR for '%s' fetched from '%s' is incomplete", storePath, srcStore->getUri());
643 });
644
645 dstStore->addToStore(*info, *source, repair, checkSigs);
646 }
647
648
copyPaths(ref<Store> srcStore,ref<Store> dstStore,const PathSet & storePaths,RepairFlag repair,CheckSigsFlag checkSigs,SubstituteFlag substitute)649 void copyPaths(ref<Store> srcStore, ref<Store> dstStore, const PathSet & storePaths,
650 RepairFlag repair, CheckSigsFlag checkSigs, SubstituteFlag substitute)
651 {
652 PathSet valid = dstStore->queryValidPaths(storePaths, substitute);
653
654 PathSet missing;
655 for (auto & path : storePaths)
656 if (!valid.count(path)) missing.insert(path);
657
658 if (missing.empty()) return;
659
660 Activity act(*logger, lvlInfo, actCopyPaths, fmt("copying %d paths", missing.size()));
661
662 std::atomic<size_t> nrDone{0};
663 std::atomic<size_t> nrFailed{0};
664 std::atomic<uint64_t> bytesExpected{0};
665 std::atomic<uint64_t> nrRunning{0};
666
667 auto showProgress = [&]() {
668 act.progress(nrDone, missing.size(), nrRunning, nrFailed);
669 };
670
671 ThreadPool pool;
672
673 processGraph<Path>(pool,
674 PathSet(missing.begin(), missing.end()),
675
676 [&](const Path & storePath) {
677 if (dstStore->isValidPath(storePath)) {
678 nrDone++;
679 showProgress();
680 return PathSet();
681 }
682
683 auto info = srcStore->queryPathInfo(storePath);
684
685 bytesExpected += info->narSize;
686 act.setExpected(actCopyPath, bytesExpected);
687
688 return info->references;
689 },
690
691 [&](const Path & storePath) {
692 checkInterrupt();
693
694 if (!dstStore->isValidPath(storePath)) {
695 MaintainCount<decltype(nrRunning)> mc(nrRunning);
696 showProgress();
697 try {
698 copyStorePath(srcStore, dstStore, storePath, repair, checkSigs);
699 } catch (Error &e) {
700 nrFailed++;
701 if (!settings.keepGoing)
702 throw e;
703 logger->log(lvlError, format("could not copy %s: %s") % storePath % e.what());
704 showProgress();
705 return;
706 }
707 }
708
709 nrDone++;
710 showProgress();
711 });
712 }
713
714
copyClosure(ref<Store> srcStore,ref<Store> dstStore,const PathSet & storePaths,RepairFlag repair,CheckSigsFlag checkSigs,SubstituteFlag substitute)715 void copyClosure(ref<Store> srcStore, ref<Store> dstStore,
716 const PathSet & storePaths, RepairFlag repair, CheckSigsFlag checkSigs,
717 SubstituteFlag substitute)
718 {
719 PathSet closure;
720 srcStore->computeFSClosure({storePaths}, closure);
721 copyPaths(srcStore, dstStore, closure, repair, checkSigs, substitute);
722 }
723
724
decodeValidPathInfo(std::istream & str,bool hashGiven)725 ValidPathInfo decodeValidPathInfo(std::istream & str, bool hashGiven)
726 {
727 ValidPathInfo info;
728 getline(str, info.path);
729 if (str.eof()) { info.path = ""; return info; }
730 if (hashGiven) {
731 string s;
732 getline(str, s);
733 info.narHash = Hash(s, htSHA256);
734 getline(str, s);
735 if (!string2Int(s, info.narSize)) throw Error("number expected");
736 }
737 getline(str, info.deriver);
738 string s; int n;
739 getline(str, s);
740 if (!string2Int(s, n)) throw Error("number expected");
741 while (n--) {
742 getline(str, s);
743 info.references.insert(s);
744 }
745 if (!str || str.eof()) throw Error("missing input");
746 return info;
747 }
748
749
showPaths(const PathSet & paths)750 string showPaths(const PathSet & paths)
751 {
752 string s;
753 for (auto & i : paths) {
754 if (s.size() != 0) s += ", ";
755 s += "'" + i + "'";
756 }
757 return s;
758 }
759
760
fingerprint() const761 std::string ValidPathInfo::fingerprint() const
762 {
763 if (narSize == 0 || !narHash)
764 throw Error(format("cannot calculate fingerprint of path '%s' because its size/hash is not known")
765 % path);
766 return
767 "1;" + path + ";"
768 + narHash.to_string(Base32) + ";"
769 + std::to_string(narSize) + ";"
770 + concatStringsSep(",", references);
771 }
772
773
sign(const SecretKey & secretKey)774 void ValidPathInfo::sign(const SecretKey & secretKey)
775 {
776 sigs.insert(secretKey.signDetached(fingerprint()));
777 }
778
779
isContentAddressed(const Store & store) const780 bool ValidPathInfo::isContentAddressed(const Store & store) const
781 {
782 auto warn = [&]() {
783 printError(format("warning: path '%s' claims to be content-addressed but isn't") % path);
784 };
785
786 if (hasPrefix(ca, "text:")) {
787 Hash hash(std::string(ca, 5));
788 if (store.makeTextPath(storePathToName(path), hash, references) == path)
789 return true;
790 else
791 warn();
792 }
793
794 else if (hasPrefix(ca, "fixed:")) {
795 bool recursive = ca.compare(6, 2, "r:") == 0;
796 Hash hash(std::string(ca, recursive ? 8 : 6));
797 if (references.empty() &&
798 store.makeFixedOutputPath(recursive, hash, storePathToName(path)) == path)
799 return true;
800 else
801 warn();
802 }
803
804 return false;
805 }
806
807
checkSignatures(const Store & store,const PublicKeys & publicKeys) const808 size_t ValidPathInfo::checkSignatures(const Store & store, const PublicKeys & publicKeys) const
809 {
810 if (isContentAddressed(store)) return maxSigs;
811
812 size_t good = 0;
813 for (auto & sig : sigs)
814 if (checkSignature(publicKeys, sig))
815 good++;
816 return good;
817 }
818
819
checkSignature(const PublicKeys & publicKeys,const std::string & sig) const820 bool ValidPathInfo::checkSignature(const PublicKeys & publicKeys, const std::string & sig) const
821 {
822 return verifyDetached(fingerprint(), sig, publicKeys);
823 }
824
825
shortRefs() const826 Strings ValidPathInfo::shortRefs() const
827 {
828 Strings refs;
829 for (auto & r : references)
830 refs.push_back(baseNameOf(r));
831 return refs;
832 }
833
834
makeFixedOutputCA(bool recursive,const Hash & hash)835 std::string makeFixedOutputCA(bool recursive, const Hash & hash)
836 {
837 return "fixed:" + (recursive ? (std::string) "r:" : "") + hash.to_string();
838 }
839
840
addToStore(const ValidPathInfo & info,Source & narSource,RepairFlag repair,CheckSigsFlag checkSigs,std::shared_ptr<FSAccessor> accessor)841 void Store::addToStore(const ValidPathInfo & info, Source & narSource,
842 RepairFlag repair, CheckSigsFlag checkSigs,
843 std::shared_ptr<FSAccessor> accessor)
844 {
845 addToStore(info, make_ref<std::string>(narSource.drain()), repair, checkSigs, accessor);
846 }
847
addToStore(const ValidPathInfo & info,const ref<std::string> & nar,RepairFlag repair,CheckSigsFlag checkSigs,std::shared_ptr<FSAccessor> accessor)848 void Store::addToStore(const ValidPathInfo & info, const ref<std::string> & nar,
849 RepairFlag repair, CheckSigsFlag checkSigs,
850 std::shared_ptr<FSAccessor> accessor)
851 {
852 StringSource source(*nar);
853 addToStore(info, source, repair, checkSigs, accessor);
854 }
855
856 }
857
858
859 #include "local-store.hh"
860 #include "remote-store.hh"
861
862
863 namespace nix {
864
865
866 RegisterStoreImplementation::Implementations * RegisterStoreImplementation::implementations = 0;
867
868 /* Split URI into protocol+hierarchy part and its parameter set. */
splitUriAndParams(const std::string & uri_)869 std::pair<std::string, Store::Params> splitUriAndParams(const std::string & uri_)
870 {
871 auto uri(uri_);
872 Store::Params params;
873 auto q = uri.find('?');
874 if (q != std::string::npos) {
875 for (auto s : tokenizeString<Strings>(uri.substr(q + 1), "&")) {
876 auto e = s.find('=');
877 if (e != std::string::npos) {
878 auto value = s.substr(e + 1);
879 std::string decoded;
880 for (size_t i = 0; i < value.size(); ) {
881 if (value[i] == '%') {
882 if (i + 2 >= value.size())
883 throw Error("invalid URI parameter '%s'", value);
884 try {
885 decoded += std::stoul(std::string(value, i + 1, 2), 0, 16);
886 i += 3;
887 } catch (...) {
888 throw Error("invalid URI parameter '%s'", value);
889 }
890 } else
891 decoded += value[i++];
892 }
893 params[s.substr(0, e)] = decoded;
894 }
895 }
896 uri = uri_.substr(0, q);
897 }
898 return {uri, params};
899 }
900
openStore(const std::string & uri_,const Store::Params & extraParams)901 ref<Store> openStore(const std::string & uri_,
902 const Store::Params & extraParams)
903 {
904 auto [uri, uriParams] = splitUriAndParams(uri_);
905 auto params = extraParams;
906 params.insert(uriParams.begin(), uriParams.end());
907
908 for (auto fun : *RegisterStoreImplementation::implementations) {
909 auto store = fun(uri, params);
910 if (store) {
911 store->warnUnknownSettings();
912 return ref<Store>(store);
913 }
914 }
915
916 throw Error("don't know how to open Nix store '%s'", uri);
917 }
918
919
getStoreType(const std::string & uri,const std::string & stateDir)920 StoreType getStoreType(const std::string & uri, const std::string & stateDir)
921 {
922 if (uri == "daemon") {
923 return tDaemon;
924 } else if (uri == "local" || hasPrefix(uri, "/")) {
925 return tLocal;
926 } else if (uri == "" || uri == "auto") {
927 if (access(stateDir.c_str(), R_OK | W_OK) == 0)
928 return tLocal;
929 else if (pathExists(settings.nixDaemonSocketFile))
930 return tDaemon;
931 else
932 return tLocal;
933 } else {
934 return tOther;
935 }
936 }
937
938
939 static RegisterStoreImplementation regStore([](
940 const std::string & uri, const Store::Params & params)
941 -> std::shared_ptr<Store>
__anon86f7a2e60c02( const std::string & uri, const Store::Params & params) 942 {
943 switch (getStoreType(uri, get(params, "state", settings.nixStateDir))) {
944 case tDaemon:
945 return std::shared_ptr<Store>(std::make_shared<UDSRemoteStore>(params));
946 case tLocal: {
947 Store::Params params2 = params;
948 if (hasPrefix(uri, "/"))
949 params2["root"] = uri;
950 return std::shared_ptr<Store>(std::make_shared<LocalStore>(params2));
951 }
952 default:
953 return nullptr;
954 }
955 });
956
957
getDefaultSubstituters()958 std::list<ref<Store>> getDefaultSubstituters()
959 {
960 static auto stores([]() {
961 std::list<ref<Store>> stores;
962
963 StringSet done;
964
965 auto addStore = [&](const std::string & uri) {
966 if (done.count(uri)) return;
967 done.insert(uri);
968 try {
969 stores.push_back(openStore(uri));
970 } catch (Error & e) {
971 printError("warning: %s", e.what());
972 }
973 };
974
975 for (auto uri : settings.substituters.get())
976 addStore(uri);
977
978 for (auto uri : settings.extraSubstituters.get())
979 addStore(uri);
980
981 stores.sort([](ref<Store> & a, ref<Store> & b) {
982 return a->getPriority() < b->getPriority();
983 });
984
985 return stores;
986 } ());
987
988 return stores;
989 }
990
991
992 }
993