1 : #include "config.h"
2 : #include "local-store.hh"
3 : #include "globals.hh"
4 : #include "archive.hh"
5 : #include "pathlocks.hh"
6 : #include "aterm.hh"
7 : #include "derivations-ast.hh"
8 : #include "worker-protocol.hh"
9 :
10 : #include <iostream>
11 : #include <algorithm>
12 :
13 : #include <sys/types.h>
14 : #include <sys/stat.h>
15 : #include <unistd.h>
16 : #include <utime.h>
17 : #include <fcntl.h>
18 : #include <errno.h>
19 :
20 :
21 : namespace nix {
22 :
23 :
24 363 : void checkStoreNotSymlink()
25 : {
26 363 : if (getEnv("NIX_IGNORE_SYMLINK_STORE") == "1") return;
27 362 : Path path = nixStore;
28 : struct stat st;
29 362 : while (path != "/") {
30 2172 : if (lstat(path.c_str(), &st))
31 0 : throw SysError(format("getting status of `%1%'") % path);
32 2172 : if (S_ISLNK(st.st_mode))
33 : throw Error(format(
34 : "the path `%1%' is a symlink; "
35 : "this is not allowed for the Nix store and its parent directories")
36 0 : % path);
37 2172 : path = dirOf(path);
38 362 : }
39 : }
40 :
41 :
42 420 : LocalStore::LocalStore()
43 : {
44 420 : substitutablePathsLoaded = false;
45 :
46 420 : schemaPath = nixDBPath + "/schema";
47 :
48 420 : if (readOnlyMode) return;
49 :
50 363 : checkStoreNotSymlink();
51 :
52 : try {
53 363 : Path globalLockPath = nixDBPath + "/big-lock";
54 363 : globalLock = openLockFile(globalLockPath.c_str(), true);
55 0 : } catch (SysError & e) {
56 0 : if (e.errNo != EACCES) throw;
57 0 : readOnlyMode = true;
58 0 : return;
59 : }
60 :
61 726 : if (!lockFile(globalLock, ltRead, false)) {
62 0 : printMsg(lvlError, "waiting for the big Nix store lock...");
63 0 : lockFile(globalLock, ltRead, true);
64 : }
65 :
66 363 : createDirs(nixDBPath + "/info");
67 363 : createDirs(nixDBPath + "/referrer");
68 :
69 363 : int curSchema = getSchema();
70 363 : if (curSchema > nixSchemaVersion)
71 : throw Error(format("current Nix store schema is version %1%, but I only support %2%")
72 0 : % curSchema % nixSchemaVersion);
73 363 : if (curSchema == 0) { /* new store */
74 15 : curSchema = nixSchemaVersion;
75 15 : writeFile(schemaPath, (format("%1%") % nixSchemaVersion).str());
76 : }
77 363 : if (curSchema == 1) throw Error("your Nix store is no longer supported");
78 363 : if (curSchema < nixSchemaVersion) upgradeStore12();
79 0 : }
80 :
81 :
82 420 : LocalStore::~LocalStore()
83 : {
84 : try {
85 420 : flushDelayedUpdates();
86 :
87 557 : foreach (RunningSubstituters::iterator, i, runningSubstituters) {
88 137 : i->second.toBuf.reset();
89 137 : i->second.to.reset();
90 137 : i->second.pid.wait(true);
91 : }
92 :
93 0 : } catch (...) {
94 0 : ignoreException();
95 : }
96 420 : }
97 :
98 :
99 363 : int LocalStore::getSchema()
100 : {
101 363 : int curSchema = 0;
102 363 : if (pathExists(schemaPath)) {
103 348 : string s = readFile(schemaPath);
104 348 : if (!string2Int(s, curSchema))
105 0 : throw Error(format("`%1%' is corrupt") % schemaPath);
106 : }
107 363 : return curSchema;
108 : }
109 :
110 :
111 72 : void copyPath(const Path & src, const Path & dst, PathFilter & filter)
112 : {
113 72 : debug(format("copying `%1%' to `%2%'") % src % dst);
114 :
115 : /* Dump an archive of the path `src' into a string buffer, then
116 : restore the archive to `dst'. This is not a very good method
117 : for very large paths, but `copyPath' is mainly used for small
118 : files. */
119 :
120 72 : StringSink sink;
121 72 : dumpPath(src, sink, filter);
122 :
123 72 : StringSource source(sink.s);
124 72 : restorePath(dst, source);
125 72 : }
126 :
127 :
128 5593 : void canonicalisePathMetaData(const Path & path, bool recurse)
129 : {
130 5593 : checkInterrupt();
131 :
132 : struct stat st;
133 5593 : if (lstat(path.c_str(), &st))
134 0 : throw SysError(format("getting attributes of path `%1%'") % path);
135 :
136 : /* Change ownership to the current uid. If it's a symlink, use
137 : lchown if available, otherwise don't bother. Wrong ownership
138 : of a symlink doesn't matter, since the owning user can't change
139 : the symlink and can't delete it because the directory is not
140 : writable. The only exception is top-level paths in the Nix
141 : store (since that directory is group-writable for the Nix build
142 : users group); we check for this case below. */
143 5593 : if (st.st_uid != geteuid()) {
144 : #if HAVE_LCHOWN
145 0 : if (lchown(path.c_str(), geteuid(), (gid_t) -1) == -1)
146 : #else
147 : if (!S_ISLNK(st.st_mode) &&
148 : chown(path.c_str(), geteuid(), (gid_t) -1) == -1)
149 : #endif
150 : throw SysError(format("changing owner of `%1%' to %2%")
151 0 : % path % geteuid());
152 : }
153 :
154 5593 : if (!S_ISLNK(st.st_mode)) {
155 :
156 : /* Mask out all type related bits. */
157 5490 : mode_t mode = st.st_mode & ~S_IFMT;
158 :
159 5490 : if (mode != 0444 && mode != 0555) {
160 : mode = (st.st_mode & S_IFMT)
161 : | 0444
162 2990 : | (st.st_mode & S_IXUSR ? 0111 : 0);
163 2990 : if (chmod(path.c_str(), mode) == -1)
164 0 : throw SysError(format("changing mode of `%1%' to %2$o") % path % mode);
165 : }
166 :
167 5490 : if (st.st_mtime != 0) {
168 : struct utimbuf utimbuf;
169 2990 : utimbuf.actime = st.st_atime;
170 2990 : utimbuf.modtime = 0;
171 2990 : if (utime(path.c_str(), &utimbuf) == -1)
172 0 : throw SysError(format("changing modification time of `%1%'") % path);
173 : }
174 :
175 : }
176 :
177 5593 : if (recurse && S_ISDIR(st.st_mode)) {
178 138 : Strings names = readDirectory(path);
179 358 : for (Strings::iterator i = names.begin(); i != names.end(); ++i)
180 358 : canonicalisePathMetaData(path + "/" + *i, true);
181 : }
182 5593 : }
183 :
184 :
185 5373 : void canonicalisePathMetaData(const Path & path)
186 : {
187 5373 : canonicalisePathMetaData(path, true);
188 :
189 : /* On platforms that don't have lchown(), the top-level path can't
190 : be a symlink, since we can't change its ownership. */
191 : struct stat st;
192 5373 : if (lstat(path.c_str(), &st))
193 0 : throw SysError(format("getting attributes of path `%1%'") % path);
194 :
195 5373 : if (st.st_uid != geteuid()) {
196 0 : assert(S_ISLNK(st.st_mode));
197 0 : throw Error(format("wrong ownership of top-level store path `%1%'") % path);
198 : }
199 5373 : }
200 :
201 :
202 53529 : static Path infoFileFor(const Path & path)
203 : {
204 53529 : string baseName = baseNameOf(path);
205 53529 : return (format("%1%/info/%2%") % nixDBPath % baseName).str();
206 : }
207 :
208 :
209 26432 : static Path referrersFileFor(const Path & path)
210 : {
211 26432 : string baseName = baseNameOf(path);
212 26432 : return (format("%1%/referrer/%2%") % nixDBPath % baseName).str();
213 : }
214 :
215 :
216 5386 : static Path tmpFileForAtomicUpdate(const Path & path)
217 : {
218 5386 : return (format("%1%/.%2%.%3%") % dirOf(path) % getpid() % baseNameOf(path)).str();
219 : }
220 :
221 :
222 5352 : static void appendReferrer(const Path & from, const Path & to, bool lock)
223 : {
224 5352 : Path referrersFile = referrersFileFor(from);
225 :
226 5352 : PathLocks referrersLock;
227 5352 : if (lock) {
228 0 : referrersLock.lockPaths(singleton<PathSet, Path>(referrersFile));
229 0 : referrersLock.setDeletion(true);
230 : }
231 :
232 5352 : AutoCloseFD fd = open(referrersFile.c_str(), O_WRONLY | O_APPEND | O_CREAT, 0666);
233 5352 : if (fd == -1) throw SysError(format("opening file `%1%'") % referrersFile);
234 :
235 5352 : string s = " " + to;
236 5352 : writeFull(fd, (const unsigned char *) s.c_str(), s.size());
237 5352 : }
238 :
239 :
240 : /* Atomically update the referrers file. If `purge' is true, the set
241 : of referrers is set to `referrers'. Otherwise, the current set of
242 : referrers is purged of invalid paths. */
243 16 : void LocalStore::rewriteReferrers(const Path & path, bool purge, PathSet referrers)
244 : {
245 16 : Path referrersFile = referrersFileFor(path);
246 :
247 16 : PathLocks referrersLock(singleton<PathSet, Path>(referrersFile));
248 16 : referrersLock.setDeletion(true);
249 :
250 16 : if (purge)
251 : /* queryReferrers() purges invalid paths, so that's all we
252 : need. */
253 16 : queryReferrers(path, referrers);
254 :
255 16 : Path tmpFile = tmpFileForAtomicUpdate(referrersFile);
256 :
257 16 : AutoCloseFD fd = open(tmpFile.c_str(), O_WRONLY | O_TRUNC | O_CREAT, 0666);
258 16 : if (fd == -1) throw SysError(format("opening file `%1%'") % referrersFile);
259 :
260 16 : string s;
261 37 : foreach (PathSet::const_iterator, i, referrers) {
262 21 : s += " "; s += *i;
263 : }
264 :
265 16 : writeFull(fd, (const unsigned char *) s.c_str(), s.size());
266 :
267 16 : fd.close(); /* for Windows; can't rename open file */
268 :
269 16 : if (rename(tmpFile.c_str(), referrersFile.c_str()) == -1)
270 0 : throw SysError(format("cannot rename `%1%' to `%2%'") % tmpFile % referrersFile);
271 16 : }
272 :
273 :
274 420 : void LocalStore::flushDelayedUpdates()
275 : {
276 872 : foreach (PathSet::iterator, i, delayedUpdates) {
277 16 : rewriteReferrers(*i, true, PathSet());
278 : }
279 420 : delayedUpdates.clear();
280 420 : }
281 :
282 :
283 : void LocalStore::registerValidPath(const Path & path,
284 : const Hash & hash, const PathSet & references,
285 370 : const Path & deriver)
286 : {
287 370 : ValidPathInfo info;
288 370 : info.path = path;
289 370 : info.hash = hash;
290 370 : info.references = references;
291 370 : info.deriver = deriver;
292 371 : registerValidPath(info);
293 369 : }
294 :
295 :
296 5371 : void LocalStore::registerValidPath(const ValidPathInfo & info, bool ignoreValidity)
297 : {
298 5371 : Path infoFile = infoFileFor(info.path);
299 :
300 5371 : ValidPathInfo oldInfo;
301 5371 : if (pathExists(infoFile)) oldInfo = queryPathInfo(info.path);
302 :
303 : /* Note that it's possible for infoFile to already exist. */
304 :
305 : /* Acquire a lock on each referrer file. This prevents those
306 : paths from being invalidated. (It would be a violation of the
307 : store invariants if we registered info.path as valid while some
308 : of its references are invalid.) NB: there can be no deadlock
309 : here since we're acquiring the locks in sorted order. */
310 5371 : PathSet lockNames;
311 15724 : foreach (PathSet::const_iterator, i, info.references)
312 10353 : if (*i != info.path) lockNames.insert(referrersFileFor(*i));
313 5371 : PathLocks referrerLocks(lockNames);
314 5371 : referrerLocks.setDeletion(true);
315 :
316 5371 : string refs;
317 15722 : foreach (PathSet::const_iterator, i, info.references) {
318 10352 : if (!refs.empty()) refs += " ";
319 10352 : refs += *i;
320 :
321 10352 : if (!ignoreValidity && *i != info.path && !isValidPath(*i))
322 : throw Error(format("cannot register `%1%' as valid, because its reference `%2%' isn't valid")
323 1 : % info.path % *i);
324 :
325 : /* Update the referrer mapping for *i. This must be done
326 : before the info file is written to maintain the invariant
327 : that if `path' is a valid path, then all its references
328 : have referrer mappings back to `path'. A " " is prefixed
329 : to separate it from the previous entry. It's not suffixed
330 : to deal with interrupted partial writes to this file. */
331 10351 : if (oldInfo.references.find(*i) == oldInfo.references.end())
332 5352 : appendReferrer(*i, info.path, false);
333 : }
334 :
335 : string s = (format(
336 : "Hash: sha256:%1%\n"
337 : "References: %2%\n"
338 : "Deriver: %3%\n"
339 : "Registered-At: %4%\n")
340 : % printHash(info.hash) % refs % info.deriver %
341 5370 : (oldInfo.registrationTime ? oldInfo.registrationTime : time(0))).str();
342 :
343 : /* Atomically rewrite the info file. */
344 5370 : Path tmpFile = tmpFileForAtomicUpdate(infoFile);
345 5370 : writeFile(tmpFile, s);
346 5370 : if (rename(tmpFile.c_str(), infoFile.c_str()) == -1)
347 0 : throw SysError(format("cannot rename `%1%' to `%2%'") % tmpFile % infoFile);
348 :
349 5371 : pathInfoCache[info.path] = info;
350 5370 : }
351 :
352 :
353 5480 : Hash parseHashField(const Path & path, const string & s)
354 : {
355 5480 : string::size_type colon = s.find(':');
356 5480 : if (colon == string::npos)
357 : throw Error(format("corrupt hash `%1%' in valid-path entry for `%2%'")
358 0 : % s % path);
359 5480 : HashType ht = parseHashType(string(s, 0, colon));
360 5480 : if (ht == htUnknown)
361 : throw Error(format("unknown hash type `%1%' in valid-path entry for `%2%'")
362 0 : % string(s, 0, colon) % path);
363 5480 : return parseHash(ht, string(s, colon + 1));
364 : }
365 :
366 :
367 5635 : ValidPathInfo LocalStore::queryPathInfo(const Path & path)
368 : {
369 5635 : ValidPathInfo res;
370 5635 : res.path = path;
371 :
372 5635 : assertStorePath(path);
373 :
374 5635 : std::map<Path, ValidPathInfo>::iterator lookup = pathInfoCache.find(path);
375 5635 : if (lookup != pathInfoCache.end()) return lookup->second;
376 :
377 : /* Read the info file. */
378 5480 : Path infoFile = infoFileFor(path);
379 5480 : if (!pathExists(infoFile))
380 0 : throw Error(format("path `%1%' is not valid") % path);
381 5480 : string info = readFile(infoFile);
382 :
383 : /* Parse it. */
384 5480 : Strings lines = tokenizeString(info, "\n");
385 :
386 32880 : for (Strings::iterator i = lines.begin(); i != lines.end(); ++i) {
387 21920 : unsigned int p = i->find(':');
388 21920 : if (p == string::npos) continue; /* bad line */
389 21920 : string name(*i, 0, p);
390 21920 : string value(*i, p + 2);
391 21920 : if (name == "References") {
392 5480 : Strings refs = tokenizeString(value, " ");
393 10960 : res.references = PathSet(refs.begin(), refs.end());
394 16440 : } else if (name == "Deriver") {
395 5480 : res.deriver = value;
396 10960 : } else if (name == "Hash") {
397 5480 : res.hash = parseHashField(path, value);
398 5480 : } else if (name == "Registered-At") {
399 5480 : int n = 0;
400 5480 : string2Int(value, n);
401 5480 : res.registrationTime = n;
402 : }
403 : }
404 :
405 5480 : return pathInfoCache[path] = res;
406 : }
407 :
408 :
409 37332 : bool LocalStore::isValidPath(const Path & path)
410 : {
411 : /* Files in the info directory starting with a `.' are temporary
412 : files. */
413 37332 : if (baseNameOf(path).at(0) == '.') return false;
414 37332 : return pathExists(infoFileFor(path));
415 : }
416 :
417 :
418 1 : PathSet LocalStore::queryValidPaths()
419 : {
420 1 : PathSet paths;
421 1 : Strings entries = readDirectory(nixDBPath + "/info");
422 13 : for (Strings::iterator i = entries.begin(); i != entries.end(); ++i)
423 12 : if (i->at(0) != '.') paths.insert(nixStore + "/" + *i);
424 1 : return paths;
425 : }
426 :
427 :
428 : void LocalStore::queryReferences(const Path & path,
429 429 : PathSet & references)
430 : {
431 429 : ValidPathInfo info = queryPathInfo(path);
432 429 : references.insert(info.references.begin(), info.references.end());
433 429 : }
434 :
435 :
436 5378 : bool LocalStore::queryReferrersInternal(const Path & path, PathSet & referrers)
437 : {
438 5378 : bool allValid = true;
439 :
440 5378 : if (!isValidPath(path))
441 0 : throw Error(format("path `%1%' is not valid") % path);
442 :
443 : /* No locking is necessary here: updates are only done by
444 : appending or by atomically replacing the file. When appending,
445 : there is a possibility that we see a partial entry, but it will
446 : just be filtered out below (the partially written path will not
447 : be valid, so it will be ignored). */
448 :
449 5378 : Path referrersFile = referrersFileFor(path);
450 5378 : if (!pathExists(referrersFile)) return true;
451 :
452 5174 : AutoCloseFD fd = open(referrersFile.c_str(), O_RDONLY);
453 5174 : if (fd == -1) throw SysError(format("opening file `%1%'") % referrersFile);
454 :
455 5174 : Paths refs = tokenizeString(readFile(fd), " ");
456 :
457 18224 : for (Paths::iterator i = refs.begin(); i != refs.end(); ++i)
458 : /* Referrers can be invalid (see registerValidPath() for the
459 : invariant), so we only return one if it is valid. */
460 7876 : if (isStorePath(*i) && isValidPath(*i)) referrers.insert(*i); else allValid = false;
461 :
462 5174 : return allValid;
463 : }
464 :
465 :
466 5371 : void LocalStore::queryReferrers(const Path & path, PathSet & referrers)
467 : {
468 5371 : queryReferrersInternal(path, referrers);
469 5371 : }
470 :
471 :
472 20 : Path LocalStore::queryDeriver(const Path & path)
473 : {
474 20 : return queryPathInfo(path).deriver;
475 : }
476 :
477 :
478 505 : void LocalStore::startSubstituter(const Path & substituter, RunningSubstituter & run)
479 : {
480 505 : if (run.pid != -1) return;
481 :
482 137 : debug(format("starting substituter program `%1%'") % substituter);
483 :
484 137 : Pipe toPipe, fromPipe;
485 :
486 137 : toPipe.create();
487 137 : fromPipe.create();
488 :
489 137 : run.pid = fork();
490 :
491 274 : switch (run.pid) {
492 :
493 : case -1:
494 0 : throw SysError("unable to fork");
495 :
496 : case 0: /* child */
497 : try {
498 137 : fromPipe.readSide.close();
499 137 : toPipe.writeSide.close();
500 137 : if (dup2(toPipe.readSide, STDIN_FILENO) == -1)
501 0 : throw SysError("dupping stdin");
502 137 : if (dup2(fromPipe.writeSide, STDOUT_FILENO) == -1)
503 0 : throw SysError("dupping stdout");
504 137 : closeMostFDs(set<int>());
505 137 : execl(substituter.c_str(), substituter.c_str(), "--query", NULL);
506 0 : throw SysError(format("executing `%1%'") % substituter);
507 0 : } catch (std::exception & e) {
508 0 : std::cerr << "error: " << e.what() << std::endl;
509 : }
510 0 : quickExit(1);
511 : }
512 :
513 : /* Parent. */
514 :
515 137 : toPipe.readSide.close();
516 137 : fromPipe.writeSide.close();
517 :
518 137 : run.toBuf = boost::shared_ptr<stdio_filebuf>(new stdio_filebuf(toPipe.writeSide.borrow(), std::ios_base::out));
519 137 : run.to = boost::shared_ptr<std::ostream>(new std::ostream(&*run.toBuf));
520 :
521 137 : run.fromBuf = boost::shared_ptr<stdio_filebuf>(new stdio_filebuf(fromPipe.readSide.borrow(), std::ios_base::in));
522 137 : run.from = boost::shared_ptr<std::istream>(new std::istream(&*run.fromBuf));
523 : }
524 :
525 :
526 555 : template<class T> T getIntLine(std::istream & str)
527 : {
528 555 : string s;
529 : T res;
530 555 : getline(str, s);
531 555 : if (!str || !string2Int(s, res)) throw Error("integer expected from stream");
532 555 : return res;
533 : }
534 :
535 :
536 236 : bool LocalStore::hasSubstitutes(const Path & path)
537 : {
538 691 : foreach (Paths::iterator, i, substituters) {
539 464 : RunningSubstituter & run(runningSubstituters[*i]);
540 464 : startSubstituter(*i, run);
541 :
542 464 : *run.to << "have\n" << path << "\n" << std::flush;
543 :
544 464 : if (getIntLine<int>(*run.from)) return true;
545 : }
546 :
547 227 : return false;
548 : }
549 :
550 :
551 : bool LocalStore::querySubstitutablePathInfo(const Path & substituter,
552 41 : const Path & path, SubstitutablePathInfo & info)
553 : {
554 41 : RunningSubstituter & run(runningSubstituters[substituter]);
555 41 : startSubstituter(substituter, run);
556 :
557 41 : *run.to << "info\n" << path << "\n" << std::flush;
558 :
559 41 : if (!getIntLine<int>(*run.from)) return false;
560 :
561 25 : getline(*run.from, info.deriver);
562 25 : if (info.deriver != "") assertStorePath(info.deriver);
563 25 : int nrRefs = getIntLine<int>(*run.from);
564 40 : while (nrRefs--) {
565 15 : Path p; getline(*run.from, p);
566 15 : assertStorePath(p);
567 15 : info.references.insert(p);
568 : }
569 25 : info.downloadSize = getIntLine<long long>(*run.from);
570 :
571 25 : return true;
572 : }
573 :
574 :
575 : bool LocalStore::querySubstitutablePathInfo(const Path & path,
576 8 : SubstitutablePathInfo & info)
577 : {
578 12 : foreach (Paths::iterator, i, substituters)
579 12 : if (querySubstitutablePathInfo(*i, path, info)) return true;
580 0 : return false;
581 : }
582 :
583 :
584 1 : Hash LocalStore::queryPathHash(const Path & path)
585 : {
586 1 : return queryPathInfo(path).hash;
587 : }
588 :
589 :
590 : static void dfsVisit(std::map<Path, ValidPathInfo> & infos,
591 9999 : const Path & path, PathSet & visited, Paths & sorted)
592 : {
593 9999 : if (visited.find(path) != visited.end()) return;
594 5001 : visited.insert(path);
595 :
596 5001 : ValidPathInfo & info(infos[path]);
597 :
598 14999 : for (PathSet::iterator i = info.references.begin();
599 : i != info.references.end(); ++i)
600 9998 : if (infos.find(*i) != infos.end())
601 4998 : dfsVisit(infos, *i, visited, sorted);
602 :
603 5001 : sorted.push_back(path);
604 : }
605 :
606 :
607 3 : void LocalStore::registerValidPaths(const ValidPathInfos & infos)
608 : {
609 3 : std::map<Path, ValidPathInfo> infosMap;
610 :
611 : /* Sort the paths topologically under the references relation, so
612 : that if path A is referenced by B, then A is registered before
613 : B. */
614 5004 : for (ValidPathInfos::const_iterator i = infos.begin(); i != infos.end(); ++i)
615 5001 : infosMap[i->path] = *i;
616 :
617 3 : PathSet visited;
618 3 : Paths sorted;
619 5007 : for (ValidPathInfos::const_iterator i = infos.begin(); i != infos.end(); ++i)
620 5001 : dfsVisit(infosMap, i->path, visited, sorted);
621 :
622 5004 : for (Paths::iterator i = sorted.begin(); i != sorted.end(); ++i)
623 5004 : registerValidPath(infosMap[*i]);
624 3 : }
625 :
626 :
627 : /* Invalidate a path. The caller is responsible for checking that
628 : there are no referrers. */
629 2673 : void LocalStore::invalidatePath(const Path & path)
630 : {
631 2673 : debug(format("invalidating path `%1%'") % path);
632 :
633 2673 : ValidPathInfo info;
634 :
635 2673 : if (pathExists(infoFileFor(path))) {
636 2673 : info = queryPathInfo(path);
637 :
638 : /* Remove the info file. */
639 2673 : Path p = infoFileFor(path);
640 2673 : if (unlink(p.c_str()) == -1)
641 0 : throw SysError(format("unlinking `%1%'") % p);
642 : }
643 :
644 : /* Remove the referrers file for `path'. */
645 2673 : Path p = referrersFileFor(path);
646 2673 : if (pathExists(p) && unlink(p.c_str()) == -1)
647 0 : throw SysError(format("unlinking `%1%'") % p);
648 :
649 : /* Clear `path' from the info cache. */
650 2673 : pathInfoCache.erase(path);
651 2673 : delayedUpdates.erase(path);
652 :
653 : /* Cause the referrer files for each path referenced by this one
654 : to be updated. This has to happen after removing the info file
655 : to preserve the invariant (see registerValidPath()).
656 :
657 : The referrer files are updated lazily in flushDelayedUpdates()
658 : to prevent quadratic performance in the garbage collector
659 : (i.e., when N referrers to some path X are deleted, we have to
660 : rewrite the referrers file for X N times, causing O(N^2) I/O).
661 :
662 : What happens if we die before the referrer file can be updated?
663 : That's not a problem, because stale (invalid) entries in the
664 : referrer file are ignored by queryReferrers(). Thus a referrer
665 : file is allowed to have stale entries; removing them is just an
666 : optimisation. verifyStore() gets rid of them eventually.
667 : */
668 7850 : foreach (PathSet::iterator, i, info.references)
669 7850 : if (*i != path) delayedUpdates.insert(*i);
670 2673 : }
671 :
672 :
673 : Path LocalStore::addToStore(const Path & _srcPath, bool fixed,
674 163 : bool recursive, string hashAlgo, PathFilter & filter)
675 : {
676 163 : Path srcPath(absPath(_srcPath));
677 163 : debug(format("adding `%1%' to the store") % srcPath);
678 :
679 : std::pair<Path, Hash> pr =
680 163 : computeStorePathForPath(srcPath, fixed, recursive, hashAlgo, filter);
681 163 : Path & dstPath(pr.first);
682 163 : Hash & h(pr.second);
683 :
684 163 : addTempRoot(dstPath);
685 :
686 163 : if (!isValidPath(dstPath)) {
687 :
688 : /* The first check above is an optimisation to prevent
689 : unnecessary lock acquisition. */
690 :
691 72 : PathLocks outputLock(singleton<PathSet, Path>(dstPath));
692 :
693 144 : if (!isValidPath(dstPath)) {
694 :
695 72 : if (pathExists(dstPath)) deletePathWrapped(dstPath);
696 :
697 72 : copyPath(srcPath, dstPath, filter);
698 :
699 72 : Hash h2 = hashPath(htSHA256, dstPath, filter);
700 72 : if (h != h2)
701 : throw Error(format("contents of `%1%' changed while copying it to `%2%' (%3% -> %4%)")
702 0 : % srcPath % dstPath % printHash(h) % printHash(h2));
703 :
704 72 : canonicalisePathMetaData(dstPath);
705 :
706 72 : registerValidPath(dstPath, h, PathSet(), "");
707 : }
708 :
709 72 : outputLock.setDeletion(true);
710 : }
711 :
712 163 : return dstPath;
713 : }
714 :
715 :
716 : Path LocalStore::addTextToStore(const string & suffix, const string & s,
717 233 : const PathSet & references)
718 : {
719 233 : Path dstPath = computeStorePathForText(suffix, s, references);
720 :
721 233 : addTempRoot(dstPath);
722 :
723 233 : if (!isValidPath(dstPath)) {
724 :
725 172 : PathLocks outputLock(singleton<PathSet, Path>(dstPath));
726 :
727 344 : if (!isValidPath(dstPath)) {
728 :
729 172 : if (pathExists(dstPath)) deletePathWrapped(dstPath);
730 :
731 172 : writeStringToFile(dstPath, s);
732 :
733 172 : canonicalisePathMetaData(dstPath);
734 :
735 : registerValidPath(dstPath,
736 172 : hashPath(htSHA256, dstPath), references, "");
737 : }
738 :
739 172 : outputLock.setDeletion(true);
740 : }
741 :
742 0 : return dstPath;
743 : }
744 :
745 :
746 : struct HashAndWriteSink : Sink
747 5 : {
748 : Sink & writeSink;
749 : HashSink hashSink;
750 : bool hashing;
751 5 : HashAndWriteSink(Sink & writeSink) : writeSink(writeSink), hashSink(htSHA256)
752 : {
753 5 : hashing = true;
754 5 : }
755 : virtual void operator ()
756 527 : (const unsigned char * data, unsigned int len)
757 : {
758 527 : writeSink(data, len);
759 527 : if (hashing) hashSink(data, len);
760 527 : }
761 : };
762 :
763 :
764 : #define EXPORT_MAGIC 0x4558494e
765 :
766 :
767 0 : static void checkSecrecy(const Path & path)
768 : {
769 : struct stat st;
770 0 : if (stat(path.c_str(), &st))
771 0 : throw SysError(format("getting status of `%1%'") % path);
772 0 : if ((st.st_mode & (S_IRWXG | S_IRWXO)) != 0)
773 0 : throw Error(format("file `%1%' should be secret (inaccessible to everybody else)!") % path);
774 0 : }
775 :
776 :
777 : void LocalStore::exportPath(const Path & path, bool sign,
778 5 : Sink & sink)
779 : {
780 5 : assertStorePath(path);
781 :
782 5 : addTempRoot(path);
783 5 : if (!isValidPath(path))
784 0 : throw Error(format("path `%1%' is not valid") % path);
785 :
786 5 : HashAndWriteSink hashAndWriteSink(sink);
787 :
788 5 : dumpPath(path, hashAndWriteSink);
789 :
790 5 : writeInt(EXPORT_MAGIC, hashAndWriteSink);
791 :
792 5 : writeString(path, hashAndWriteSink);
793 :
794 5 : PathSet references;
795 5 : queryReferences(path, references);
796 5 : writeStringSet(references, hashAndWriteSink);
797 :
798 5 : Path deriver = queryDeriver(path);
799 5 : writeString(deriver, hashAndWriteSink);
800 :
801 5 : if (sign) {
802 0 : Hash hash = hashAndWriteSink.hashSink.finish();
803 0 : hashAndWriteSink.hashing = false;
804 :
805 0 : writeInt(1, hashAndWriteSink);
806 :
807 0 : Path tmpDir = createTempDir();
808 0 : AutoDelete delTmp(tmpDir);
809 0 : Path hashFile = tmpDir + "/hash";
810 0 : writeStringToFile(hashFile, printHash(hash));
811 :
812 0 : Path secretKey = nixConfDir + "/signing-key.sec";
813 0 : checkSecrecy(secretKey);
814 :
815 0 : Strings args;
816 0 : args.push_back("rsautl");
817 0 : args.push_back("-sign");
818 0 : args.push_back("-inkey");
819 0 : args.push_back(secretKey);
820 0 : args.push_back("-in");
821 0 : args.push_back(hashFile);
822 0 : string signature = runProgram(OPENSSL_PATH, true, args);
823 :
824 0 : writeString(signature, hashAndWriteSink);
825 :
826 : } else
827 5 : writeInt(0, hashAndWriteSink);
828 5 : }
829 :
830 :
831 : struct HashAndReadSource : Source
832 5 : {
833 : Source & readSource;
834 : HashSink hashSink;
835 : bool hashing;
836 5 : HashAndReadSource(Source & readSource) : readSource(readSource), hashSink(htSHA256)
837 : {
838 5 : hashing = true;
839 5 : }
840 : virtual void operator ()
841 527 : (unsigned char * data, unsigned int len)
842 : {
843 527 : readSource(data, len);
844 527 : if (hashing) hashSink(data, len);
845 527 : }
846 : };
847 :
848 :
849 5 : Path LocalStore::importPath(bool requireSignature, Source & source)
850 : {
851 5 : HashAndReadSource hashAndReadSource(source);
852 :
853 : /* We don't yet know what store path this archive contains (the
854 : store path follows the archive data proper), and besides, we
855 : don't know yet whether the signature is valid. */
856 5 : Path tmpDir = createTempDir(nixStore);
857 5 : AutoDelete delTmp(tmpDir);
858 5 : Path unpacked = tmpDir + "/unpacked";
859 :
860 5 : restorePath(unpacked, hashAndReadSource);
861 :
862 5 : unsigned int magic = readInt(hashAndReadSource);
863 5 : if (magic != EXPORT_MAGIC)
864 0 : throw Error("Nix archive cannot be imported; wrong format");
865 :
866 5 : Path dstPath = readStorePath(hashAndReadSource);
867 :
868 5 : PathSet references = readStorePaths(hashAndReadSource);
869 :
870 5 : Path deriver = readString(hashAndReadSource);
871 5 : if (deriver != "") assertStorePath(deriver);
872 :
873 5 : Hash hash = hashAndReadSource.hashSink.finish();
874 5 : hashAndReadSource.hashing = false;
875 :
876 5 : bool haveSignature = readInt(hashAndReadSource) == 1;
877 :
878 5 : if (requireSignature && !haveSignature)
879 0 : throw Error("imported archive lacks a signature");
880 :
881 5 : if (haveSignature) {
882 0 : string signature = readString(hashAndReadSource);
883 :
884 0 : if (requireSignature) {
885 0 : Path sigFile = tmpDir + "/sig";
886 0 : writeStringToFile(sigFile, signature);
887 :
888 0 : Strings args;
889 0 : args.push_back("rsautl");
890 0 : args.push_back("-verify");
891 0 : args.push_back("-inkey");
892 0 : args.push_back(nixConfDir + "/signing-key.pub");
893 0 : args.push_back("-pubin");
894 0 : args.push_back("-in");
895 0 : args.push_back(sigFile);
896 0 : string hash2 = runProgram(OPENSSL_PATH, true, args);
897 :
898 : /* Note: runProgram() throws an exception if the signature
899 : is invalid. */
900 :
901 0 : if (printHash(hash) != hash2)
902 : throw Error(
903 : "signed hash doesn't match actual contents of imported "
904 : "archive; archive could be corrupt, or someone is trying "
905 0 : "to import a Trojan horse");
906 0 : }
907 : }
908 :
909 : /* Do the actual import. */
910 :
911 : /* !!! way too much code duplication with addTextToStore() etc. */
912 5 : addTempRoot(dstPath);
913 :
914 5 : if (!isValidPath(dstPath)) {
915 :
916 5 : PathLocks outputLock(singleton<PathSet, Path>(dstPath));
917 :
918 10 : if (!isValidPath(dstPath)) {
919 :
920 5 : if (pathExists(dstPath)) deletePathWrapped(dstPath);
921 :
922 5 : if (rename(unpacked.c_str(), dstPath.c_str()) == -1)
923 : throw SysError(format("cannot move `%1%' to `%2%'")
924 0 : % unpacked % dstPath);
925 :
926 5 : canonicalisePathMetaData(dstPath);
927 :
928 : /* !!! if we were clever, we could prevent the hashPath()
929 : here. */
930 5 : if (deriver != "" && !isValidPath(deriver)) deriver = "";
931 : registerValidPath(dstPath,
932 5 : hashPath(htSHA256, dstPath), references, deriver);
933 : }
934 :
935 4 : outputLock.setDeletion(true);
936 : }
937 :
938 4 : return dstPath;
939 : }
940 :
941 :
942 : void LocalStore::deleteFromStore(const Path & path, unsigned long long & bytesFreed,
943 2675 : unsigned long long & blocksFreed)
944 : {
945 2675 : bytesFreed = 0;
946 :
947 2675 : assertStorePath(path);
948 :
949 2675 : if (isValidPath(path)) {
950 : /* Acquire a lock on the referrers file to prevent new
951 : referrers to this path from appearing while we're deleting
952 : it. */
953 2673 : PathLocks referrersLock(singleton<PathSet, Path>(referrersFileFor(path)));
954 2673 : referrersLock.setDeletion(true);
955 2673 : PathSet referrers; queryReferrers(path, referrers);
956 2673 : referrers.erase(path); /* ignore self-references */
957 2673 : if (!referrers.empty())
958 : throw PathInUse(format("cannot delete path `%1%' because it is in use by `%2%'")
959 0 : % path % showPaths(referrers));
960 2673 : invalidatePath(path);
961 : }
962 :
963 2675 : deletePathWrapped(path, bytesFreed, blocksFreed);
964 2675 : }
965 :
966 :
967 1 : void LocalStore::verifyStore(bool checkContents)
968 : {
969 : /* Check whether all valid paths actually exist. */
970 1 : printMsg(lvlInfo, "checking path existence");
971 :
972 1 : PathSet validPaths2 = queryValidPaths(), validPaths;
973 :
974 13 : for (PathSet::iterator i = validPaths2.begin(); i != validPaths2.end(); ++i) {
975 12 : checkInterrupt();
976 12 : if (!isStorePath(*i)) {
977 0 : printMsg(lvlError, format("path `%1%' is not in the Nix store") % *i);
978 0 : invalidatePath(*i);
979 12 : } else if (!pathExists(*i)) {
980 0 : printMsg(lvlError, format("path `%1%' disappeared") % *i);
981 0 : invalidatePath(*i);
982 : } else
983 12 : validPaths.insert(*i);
984 : }
985 :
986 :
987 : /* Check the store path meta-information. */
988 1 : printMsg(lvlInfo, "checking path meta-information");
989 :
990 1 : std::map<Path, PathSet> referrersCache;
991 :
992 13 : for (PathSet::iterator i = validPaths.begin(); i != validPaths.end(); ++i) {
993 12 : bool update = false;
994 12 : ValidPathInfo info = queryPathInfo(*i);
995 :
996 : /* Check the references: each reference should be valid, and
997 : it should have a matching referrer. */
998 21 : for (PathSet::iterator j = info.references.begin();
999 : j != info.references.end(); ++j)
1000 : {
1001 9 : if (referrersCache.find(*j) == referrersCache.end())
1002 7 : queryReferrers(*j, referrersCache[*j]);
1003 9 : if (referrersCache[*j].find(*i) == referrersCache[*j].end()) {
1004 0 : printMsg(lvlError, format("adding missing referrer mapping from `%1%' to `%2%'")
1005 : % *j % *i);
1006 0 : appendReferrer(*j, *i, true);
1007 : }
1008 9 : if (validPaths.find(*j) == validPaths.end()) {
1009 0 : printMsg(lvlError, format("incomplete closure: `%1%' needs missing `%2%'")
1010 : % *i % *j);
1011 : /* nothing we can do about it... */
1012 : }
1013 : }
1014 :
1015 : /* Check the deriver. (Note that the deriver doesn't have to
1016 : be a valid path.) */
1017 12 : if (!info.deriver.empty() && !isStorePath(info.deriver)) {
1018 0 : info.deriver = "";
1019 0 : update = true;
1020 : }
1021 :
1022 : /* Check the content hash (optionally - slow). */
1023 12 : if (checkContents) {
1024 0 : debug(format("checking contents of `%1%'") % *i);
1025 0 : Hash current = hashPath(info.hash.type, *i);
1026 0 : if (current != info.hash) {
1027 0 : printMsg(lvlError, format("path `%1%' was modified! "
1028 : "expected hash `%2%', got `%3%'")
1029 : % *i % printHash(info.hash) % printHash(current));
1030 : }
1031 : }
1032 :
1033 12 : if (update) registerValidPath(info);
1034 : }
1035 :
1036 1 : referrersCache.clear();
1037 :
1038 :
1039 : /* Check the referrers. */
1040 1 : printMsg(lvlInfo, "checking referrers");
1041 :
1042 1 : std::map<Path, PathSet> referencesCache;
1043 :
1044 1 : Strings entries = readDirectory(nixDBPath + "/referrer");
1045 15 : for (Strings::iterator i = entries.begin(); i != entries.end(); ++i) {
1046 7 : Path from = nixStore + "/" + *i;
1047 :
1048 7 : if (validPaths.find(from) == validPaths.end()) {
1049 0 : printMsg(lvlError, format("removing referrers file for invalid `%1%'") % from);
1050 0 : Path p = referrersFileFor(from);
1051 0 : if (unlink(p.c_str()) == -1)
1052 0 : throw SysError(format("unlinking `%1%'") % p);
1053 0 : continue;
1054 : }
1055 :
1056 7 : PathSet referrers;
1057 7 : bool allValid = queryReferrersInternal(from, referrers);
1058 7 : bool update = false;
1059 :
1060 7 : if (!allValid) {
1061 0 : printMsg(lvlError, format("removing some stale referrers for `%1%'") % from);
1062 0 : update = true;
1063 : }
1064 :
1065 : /* Each referrer should have a matching reference. */
1066 7 : PathSet referrersNew;
1067 16 : for (PathSet::iterator j = referrers.begin(); j != referrers.end(); ++j) {
1068 9 : if (referencesCache.find(*j) == referencesCache.end())
1069 5 : queryReferences(*j, referencesCache[*j]);
1070 9 : if (referencesCache[*j].find(from) == referencesCache[*j].end()) {
1071 0 : printMsg(lvlError, format("removing unexpected referrer mapping from `%1%' to `%2%'")
1072 : % from % *j);
1073 0 : update = true;
1074 9 : } else referrersNew.insert(*j);
1075 : }
1076 :
1077 7 : if (update) rewriteReferrers(from, false, referrersNew);
1078 1 : }
1079 1 : }
1080 :
1081 0 :
1082 1106 : }
|