2 #include "local-store.hh"
5 #include "pathlocks.hh"
6 #include "worker-protocol.hh"
7 #include "derivations.hh"
14 #include <sys/types.h>
26 #if HAVE_UNSHARE && HAVE_STATVFS && HAVE_SYS_MOUNT_H
28 #include <sys/statvfs.h>
29 #include <sys/mount.h>
32 #include <sys/ioctl.h>
41 void checkStoreNotSymlink()
43 if (getEnv("NIX_IGNORE_SYMLINK_STORE") == "1") return;
44 Path path
= settings
.nixStore
;
47 if (lstat(path
.c_str(), &st
))
48 throw SysError(format("getting status of `%1%'") % path
);
49 if (S_ISLNK(st
.st_mode
))
51 "the path `%1%' is a symlink; "
52 "this is not allowed for the store and its parent directories")
59 LocalStore::LocalStore(bool reserveSpace
)
61 schemaPath
= settings
.nixDBPath
+ "/schema";
63 if (settings
.readOnlyMode
) {
68 /* Create missing state directories if they don't already exist. */
69 createDirs(settings
.nixStore
);
71 createDirs(linksDir
= settings
.nixStore
+ "/.links");
72 Path profilesDir
= settings
.nixStateDir
+ "/profiles";
73 createDirs(profilesDir
);
74 createDirs(settings
.nixStateDir
+ "/temproots");
75 createDirs(settings
.nixDBPath
);
76 Path gcRootsDir
= settings
.nixStateDir
+ "/gcroots";
77 if (!pathExists(gcRootsDir
)) {
78 createDirs(gcRootsDir
);
79 createSymlink(profilesDir
, gcRootsDir
+ "/profiles");
82 /* Optionally, create directories and set permissions for a
83 multi-user install. */
84 if (getuid() == 0 && settings
.buildUsersGroup
!= "") {
86 Path perUserDir
= profilesDir
+ "/per-user";
87 createDirs(perUserDir
);
88 if (chmod(perUserDir
.c_str(), 0755) == -1)
89 throw SysError(format("could not set permissions on '%1%' to 755")
94 struct group
* gr
= getgrnam(settings
.buildUsersGroup
.c_str());
96 throw Error(format("the group `%1%' specified in `build-users-group' does not exist")
97 % settings
.buildUsersGroup
);
100 if (stat(settings
.nixStore
.c_str(), &st
))
101 throw SysError(format("getting attributes of path '%1%'") % settings
.nixStore
);
103 if (st
.st_uid
!= 0 || st
.st_gid
!= gr
->gr_gid
|| (st
.st_mode
& ~S_IFMT
) != perm
) {
104 if (chown(settings
.nixStore
.c_str(), 0, gr
->gr_gid
) == -1)
105 throw SysError(format("changing ownership of path '%1%'") % settings
.nixStore
);
106 if (chmod(settings
.nixStore
.c_str(), perm
) == -1)
107 throw SysError(format("changing permissions on path '%1%'") % settings
.nixStore
);
112 checkStoreNotSymlink();
114 /* We can't open a SQLite database if the disk is full. Since
115 this prevents the garbage collector from running when it's most
116 needed, we reserve some dummy space that we can free just
117 before doing a garbage collection. */
119 Path reservedPath
= settings
.nixDBPath
+ "/reserved";
122 if (stat(reservedPath
.c_str(), &st
) == -1 ||
123 st
.st_size
!= settings
.reservedSize
)
125 AutoCloseFD fd
= open(reservedPath
.c_str(), O_WRONLY
| O_CREAT
, 0600);
127 #if HAVE_POSIX_FALLOCATE
128 res
= posix_fallocate(fd
, 0, settings
.reservedSize
);
131 writeFull(fd
, string(settings
.reservedSize
, 'X'));
132 ftruncate(fd
, settings
.reservedSize
);
137 deletePath(reservedPath
);
138 } catch (SysError
& e
) { /* don't care about errors */
141 /* Acquire the big fat lock in shared mode to make sure that no
142 schema upgrade is in progress. */
144 Path globalLockPath
= settings
.nixDBPath
+ "/big-lock";
145 globalLock
= openLockFile(globalLockPath
.c_str(), true);
146 } catch (SysError
& e
) {
147 if (e
.errNo
!= EACCES
) throw;
148 settings
.readOnlyMode
= true;
153 if (!lockFile(globalLock
, ltRead
, false)) {
154 printMsg(lvlError
, "waiting for the big store lock...");
155 lockFile(globalLock
, ltRead
, true);
158 /* Check the current database schema and if necessary do an
160 int curSchema
= getSchema();
161 if (curSchema
> nixSchemaVersion
)
162 throw Error(format("current store schema is version %1%, but I only support %2%")
163 % curSchema
% nixSchemaVersion
);
165 else if (curSchema
== 0) { /* new store */
166 curSchema
= nixSchemaVersion
;
168 writeFile(schemaPath
, (format("%1%") % nixSchemaVersion
).str());
171 else if (curSchema
< nixSchemaVersion
) {
172 /* Guix always used version 7 of the schema. */
174 format("Your store database uses an implausibly old schema, version %1%.")
182 LocalStore::~LocalStore()
185 if (fdTempRoots
!= -1) {
187 unlink(fnTempRoots
.c_str());
195 int LocalStore::getSchema()
198 if (pathExists(schemaPath
)) {
199 string s
= readFile(schemaPath
);
200 if (!string2Int(s
, curSchema
))
201 throw Error(format("`%1%' is corrupt") % schemaPath
);
207 void LocalStore::openDB(bool create
)
209 if (access(settings
.nixDBPath
.c_str(), R_OK
| W_OK
))
210 throw SysError(format("store database directory `%1%' is not writable") % settings
.nixDBPath
);
212 /* Open the store database. */
213 string dbPath
= settings
.nixDBPath
+ "/db.sqlite";
214 if (sqlite3_open_v2(dbPath
.c_str(), &db
.db
,
215 SQLITE_OPEN_READWRITE
| (create
? SQLITE_OPEN_CREATE
: 0), 0) != SQLITE_OK
)
216 throw Error(format("cannot open store database `%1%'") % dbPath
);
218 if (sqlite3_busy_timeout(db
, 60 * 60 * 1000) != SQLITE_OK
)
219 throwSQLiteError(db
, "setting timeout");
221 if (sqlite3_exec(db
, "pragma foreign_keys = 1;", 0, 0, 0) != SQLITE_OK
)
222 throwSQLiteError(db
, "enabling foreign keys");
224 /* !!! check whether sqlite has been built with foreign key
227 /* Whether SQLite should fsync(). "Normal" synchronous mode
228 should be safe enough. If the user asks for it, don't sync at
229 all. This can cause database corruption if the system
231 string syncMode
= settings
.fsyncMetadata
? "normal" : "off";
232 if (sqlite3_exec(db
, ("pragma synchronous = " + syncMode
+ ";").c_str(), 0, 0, 0) != SQLITE_OK
)
233 throwSQLiteError(db
, "setting synchronous mode");
235 /* Set the SQLite journal mode. WAL mode is fastest, so it's the
237 string mode
= settings
.useSQLiteWAL
? "wal" : "truncate";
241 stmt
.create(db
, "pragma main.journal_mode;");
242 if (sqlite3_step(stmt
) != SQLITE_ROW
)
243 throwSQLiteError(db
, "querying journal mode");
244 prevMode
= string((const char *) sqlite3_column_text(stmt
, 0));
246 if (prevMode
!= mode
&&
247 sqlite3_exec(db
, ("pragma main.journal_mode = " + mode
+ ";").c_str(), 0, 0, 0) != SQLITE_OK
)
248 throwSQLiteError(db
, "setting journal mode");
250 /* Increase the auto-checkpoint interval to 40000 pages. This
251 seems enough to ensure that instantiating the NixOS system
252 derivation is done in a single fsync(). */
253 if (mode
== "wal" && sqlite3_exec(db
, "pragma wal_autocheckpoint = 40000;", 0, 0, 0) != SQLITE_OK
)
254 throwSQLiteError(db
, "setting autocheckpoint interval");
256 /* Initialise the database schema, if necessary. */
258 const char * schema
=
259 #include "schema.sql.hh"
261 if (sqlite3_exec(db
, (const char *) schema
, 0, 0, 0) != SQLITE_OK
)
262 throwSQLiteError(db
, "initialising database schema");
265 /* Prepare SQL statements. */
266 stmtRegisterValidPath
.create(db
,
267 "insert into ValidPaths (path, hash, registrationTime, deriver, narSize) values (?, ?, ?, ?, ?);");
268 stmtUpdatePathInfo
.create(db
,
269 "update ValidPaths set narSize = ?, hash = ? where path = ?;");
270 stmtAddReference
.create(db
,
271 "insert or replace into Refs (referrer, reference) values (?, ?);");
272 stmtQueryPathInfo
.create(db
,
273 "select id, hash, registrationTime, deriver, narSize from ValidPaths where path = ?;");
274 stmtQueryReferences
.create(db
,
275 "select path from Refs join ValidPaths on reference = id where referrer = ?;");
276 stmtQueryReferrers
.create(db
,
277 "select path from Refs join ValidPaths on referrer = id where reference = (select id from ValidPaths where path = ?);");
278 stmtInvalidatePath
.create(db
,
279 "delete from ValidPaths where path = ?;");
280 stmtRegisterFailedPath
.create(db
,
281 "insert or ignore into FailedPaths (path, time) values (?, ?);");
282 stmtHasPathFailed
.create(db
,
283 "select time from FailedPaths where path = ?;");
284 stmtQueryFailedPaths
.create(db
,
285 "select path from FailedPaths;");
286 // If the path is a derivation, then clear its outputs.
287 stmtClearFailedPath
.create(db
,
288 "delete from FailedPaths where ?1 = '*' or path = ?1 "
289 "or path in (select d.path from DerivationOutputs d join ValidPaths v on d.drv = v.id where v.path = ?1);");
290 stmtAddDerivationOutput
.create(db
,
291 "insert or replace into DerivationOutputs (drv, id, path) values (?, ?, ?);");
292 stmtQueryValidDerivers
.create(db
,
293 "select v.id, v.path from DerivationOutputs d join ValidPaths v on d.drv = v.id where d.path = ?;");
294 stmtQueryDerivationOutputs
.create(db
,
295 "select id, path from DerivationOutputs where drv = ?;");
296 // Use "path >= ?" with limit 1 rather than "path like '?%'" to
297 // ensure efficient lookup.
298 stmtQueryPathFromHashPart
.create(db
,
299 "select path from ValidPaths where path >= ? limit 1;");
300 stmtQueryValidPaths
.create(db
, "select path from ValidPaths");
304 /* To improve purity, users may want to make the store a read-only
305 bind mount. So make the store writable for this process. */
306 void LocalStore::makeStoreWritable()
308 #if HAVE_UNSHARE && HAVE_STATVFS && HAVE_SYS_MOUNT_H && defined(MS_BIND) && defined(MS_REMOUNT)
309 if (getuid() != 0) return;
310 /* Check if /nix/store is on a read-only mount. */
312 if (statvfs(settings
.nixStore
.c_str(), &stat
) != 0)
313 throw SysError("getting info about the store mount point");
315 if (stat
.f_flag
& ST_RDONLY
) {
316 if (unshare(CLONE_NEWNS
) == -1)
317 throw SysError("setting up a private mount namespace");
319 if (mount(0, settings
.nixStore
.c_str(), "none", MS_REMOUNT
| MS_BIND
, 0) == -1)
320 throw SysError(format("remounting %1% writable") % settings
.nixStore
);
326 const time_t mtimeStore
= 1; /* 1 second into the epoch */
329 static void canonicaliseTimestampAndPermissions(const Path
& path
, const struct stat
& st
)
331 if (!S_ISLNK(st
.st_mode
)) {
333 /* Mask out all type related bits. */
334 mode_t mode
= st
.st_mode
& ~S_IFMT
;
336 if (mode
!= 0444 && mode
!= 0555) {
337 mode
= (st
.st_mode
& S_IFMT
)
339 | (st
.st_mode
& S_IXUSR
? 0111 : 0);
340 if (chmod(path
.c_str(), mode
) == -1)
341 throw SysError(format("changing mode of `%1%' to %2$o") % path
% mode
);
346 if (st
.st_mtime
!= mtimeStore
) {
347 struct timeval times
[2];
348 times
[0].tv_sec
= st
.st_atime
;
349 times
[0].tv_usec
= 0;
350 times
[1].tv_sec
= mtimeStore
;
351 times
[1].tv_usec
= 0;
353 if (lutimes(path
.c_str(), times
) == -1)
354 if (errno
!= ENOSYS
||
355 (!S_ISLNK(st
.st_mode
) && utimes(path
.c_str(), times
) == -1))
357 if (!S_ISLNK(st
.st_mode
) && utimes(path
.c_str(), times
) == -1)
359 throw SysError(format("changing modification time of `%1%'") % path
);
364 void canonicaliseTimestampAndPermissions(const Path
& path
)
367 if (lstat(path
.c_str(), &st
))
368 throw SysError(format("getting attributes of path `%1%'") % path
);
369 canonicaliseTimestampAndPermissions(path
, st
);
373 static void canonicalisePathMetaData_(const Path
& path
, uid_t fromUid
, InodesSeen
& inodesSeen
)
378 if (lstat(path
.c_str(), &st
))
379 throw SysError(format("getting attributes of path `%1%'") % path
);
381 /* Really make sure that the path is of a supported type. */
382 if (!(S_ISREG(st
.st_mode
) || S_ISDIR(st
.st_mode
) || S_ISLNK(st
.st_mode
)))
383 throw Error(format("file ‘%1%’ has an unsupported type") % path
);
385 /* Fail if the file is not owned by the build user. This prevents
386 us from messing up the ownership/permissions of files
387 hard-linked into the output (e.g. "ln /etc/shadow $out/foo").
388 However, ignore files that we chown'ed ourselves previously to
389 ensure that we don't fail on hard links within the same build
390 (i.e. "touch $out/foo; ln $out/foo $out/bar"). */
391 if (fromUid
!= (uid_t
) -1 && st
.st_uid
!= fromUid
) {
392 assert(!S_ISDIR(st
.st_mode
));
393 if (inodesSeen
.find(Inode(st
.st_dev
, st
.st_ino
)) == inodesSeen
.end())
394 throw BuildError(format("invalid ownership on file `%1%'") % path
);
395 mode_t mode
= st
.st_mode
& ~S_IFMT
;
396 assert(S_ISLNK(st
.st_mode
) || (st
.st_uid
== geteuid() && (mode
== 0444 || mode
== 0555) && st
.st_mtime
== mtimeStore
));
400 inodesSeen
.insert(Inode(st
.st_dev
, st
.st_ino
));
402 canonicaliseTimestampAndPermissions(path
, st
);
404 /* Change ownership to the current uid. If it's a symlink, use
405 lchown if available, otherwise don't bother. Wrong ownership
406 of a symlink doesn't matter, since the owning user can't change
407 the symlink and can't delete it because the directory is not
408 writable. The only exception is top-level paths in the
409 store (since that directory is group-writable for the build
410 users group); we check for this case below. */
411 if (st
.st_uid
!= geteuid()) {
413 if (lchown(path
.c_str(), geteuid(), getegid()) == -1)
415 if (!S_ISLNK(st
.st_mode
) &&
416 chown(path
.c_str(), geteuid(), getegid()) == -1)
418 throw SysError(format("changing owner of `%1%' to %2%")
422 if (S_ISDIR(st
.st_mode
)) {
423 DirEntries entries
= readDirectory(path
);
424 for (auto & i
: entries
)
425 canonicalisePathMetaData_(path
+ "/" + i
.name
, fromUid
, inodesSeen
);
430 void canonicalisePathMetaData(const Path
& path
, uid_t fromUid
, InodesSeen
& inodesSeen
)
432 canonicalisePathMetaData_(path
, fromUid
, inodesSeen
);
434 /* On platforms that don't have lchown(), the top-level path can't
435 be a symlink, since we can't change its ownership. */
437 if (lstat(path
.c_str(), &st
))
438 throw SysError(format("getting attributes of path `%1%'") % path
);
440 if (st
.st_uid
!= geteuid()) {
441 assert(S_ISLNK(st
.st_mode
));
442 throw Error(format("wrong ownership of top-level store path `%1%'") % path
);
447 void canonicalisePathMetaData(const Path
& path
, uid_t fromUid
)
449 InodesSeen inodesSeen
;
450 canonicalisePathMetaData(path
, fromUid
, inodesSeen
);
454 void LocalStore::checkDerivationOutputs(const Path
& drvPath
, const Derivation
& drv
)
456 string drvName
= storePathToName(drvPath
);
457 assert(isDerivation(drvName
));
458 drvName
= string(drvName
, 0, drvName
.size() - drvExtension
.size());
460 if (isFixedOutputDrv(drv
)) {
461 DerivationOutputs::const_iterator out
= drv
.outputs
.find("out");
462 if (out
== drv
.outputs
.end())
463 throw Error(format("derivation `%1%' does not have an output named `out'") % drvPath
);
465 bool recursive
; HashType ht
; Hash h
;
466 out
->second
.parseHashInfo(recursive
, ht
, h
);
467 Path outPath
= makeFixedOutputPath(recursive
, ht
, h
, drvName
);
469 StringPairs::const_iterator j
= drv
.env
.find("out");
470 if (out
->second
.path
!= outPath
|| j
== drv
.env
.end() || j
->second
!= outPath
)
471 throw Error(format("derivation `%1%' has incorrect output `%2%', should be `%3%'")
472 % drvPath
% out
->second
.path
% outPath
);
476 Derivation
drvCopy(drv
);
477 foreach (DerivationOutputs::iterator
, i
, drvCopy
.outputs
) {
479 drvCopy
.env
[i
->first
] = "";
482 Hash h
= hashDerivationModulo(*this, drvCopy
);
484 foreach (DerivationOutputs::const_iterator
, i
, drv
.outputs
) {
485 Path outPath
= makeOutputPath(i
->first
, h
, drvName
);
486 StringPairs::const_iterator j
= drv
.env
.find(i
->first
);
487 if (i
->second
.path
!= outPath
|| j
== drv
.env
.end() || j
->second
!= outPath
)
488 throw Error(format("derivation `%1%' has incorrect output `%2%', should be `%3%'")
489 % drvPath
% i
->second
.path
% outPath
);
495 uint64_t LocalStore::addValidPath(const ValidPathInfo
& info
, bool checkOutputs
)
497 stmtRegisterValidPath
.use()
499 ("sha256:" + printHash(info
.hash
))
500 (info
.registrationTime
== 0 ? time(0) : info
.registrationTime
)
501 (info
.deriver
, info
.deriver
!= "")
502 (info
.narSize
, info
.narSize
!= 0)
504 uint64_t id
= sqlite3_last_insert_rowid(db
);
506 /* If this is a derivation, then store the derivation outputs in
507 the database. This is useful for the garbage collector: it can
508 efficiently query whether a path is an output of some
510 if (isDerivation(info
.path
)) {
511 Derivation drv
= readDerivation(info
.path
);
513 /* Verify that the output paths in the derivation are correct
514 (i.e., follow the scheme for computing output paths from
515 derivations). Note that if this throws an error, then the
516 DB transaction is rolled back, so the path validity
517 registration above is undone. */
518 if (checkOutputs
) checkDerivationOutputs(info
.path
, drv
);
520 for (auto & i
: drv
.outputs
) {
521 stmtAddDerivationOutput
.use()
533 void LocalStore::addReference(uint64_t referrer
, uint64_t reference
)
535 stmtAddReference
.use()(referrer
)(reference
).exec();
539 void LocalStore::registerFailedPath(const Path
& path
)
541 retrySQLite
<void>([&]() {
542 stmtRegisterFailedPath
.use()(path
)(time(0)).step();
547 bool LocalStore::hasPathFailed(const Path
& path
)
549 return retrySQLite
<bool>([&]() {
550 return stmtHasPathFailed
.use()(path
).next();
555 PathSet
LocalStore::queryFailedPaths()
557 return retrySQLite
<PathSet
>([&]() {
558 auto useQueryFailedPaths(stmtQueryFailedPaths
.use());
561 while (useQueryFailedPaths
.next())
562 res
.insert(useQueryFailedPaths
.getStr(0));
569 void LocalStore::clearFailedPaths(const PathSet
& paths
)
571 retrySQLite
<void>([&]() {
574 for (auto & path
: paths
)
575 stmtClearFailedPath
.use()(path
).exec();
582 Hash
parseHashField(const Path
& path
, const string
& s
)
584 string::size_type colon
= s
.find(':');
585 if (colon
== string::npos
)
586 throw Error(format("corrupt hash `%1%' in valid-path entry for `%2%'")
588 HashType ht
= parseHashType(string(s
, 0, colon
));
590 throw Error(format("unknown hash type `%1%' in valid-path entry for `%2%'")
591 % string(s
, 0, colon
) % path
);
592 return parseHash(ht
, string(s
, colon
+ 1));
596 ValidPathInfo
LocalStore::queryPathInfo(const Path
& path
)
601 assertStorePath(path
);
603 return retrySQLite
<ValidPathInfo
>([&]() {
605 /* Get the path info. */
606 auto useQueryPathInfo(stmtQueryPathInfo
.use()(path
));
608 if (!useQueryPathInfo
.next())
609 throw Error(format("path `%1%' is not valid") % path
);
611 info
.id
= useQueryPathInfo
.getInt(0);
613 info
.hash
= parseHashField(path
, useQueryPathInfo
.getStr(1));
615 info
.registrationTime
= useQueryPathInfo
.getInt(2);
617 auto s
= (const char *) sqlite3_column_text(stmtQueryPathInfo
, 3);
618 if (s
) info
.deriver
= s
;
620 /* Note that narSize = NULL yields 0. */
621 info
.narSize
= useQueryPathInfo
.getInt(4);
623 /* Get the references. */
624 auto useQueryReferences(stmtQueryReferences
.use()(info
.id
));
626 while (useQueryReferences
.next())
627 info
.references
.insert(useQueryReferences
.getStr(0));
634 /* Update path info in the database. Currently only updates the
636 void LocalStore::updatePathInfo(const ValidPathInfo
& info
)
638 stmtUpdatePathInfo
.use()
639 (info
.narSize
, info
.narSize
!= 0)
640 ("sha256:" + printHash(info
.hash
))
646 uint64_t LocalStore::queryValidPathId(const Path
& path
)
648 auto use(stmtQueryPathInfo
.use()(path
));
650 throw Error(format("path ‘%1%’ is not valid") % path
);
651 return use
.getInt(0);
655 bool LocalStore::isValidPath_(const Path
& path
)
657 return stmtQueryPathInfo
.use()(path
).next();
661 bool LocalStore::isValidPath(const Path
& path
)
663 return retrySQLite
<bool>([&]() {
664 return isValidPath_(path
);
669 PathSet
LocalStore::queryValidPaths(const PathSet
& paths
)
671 return retrySQLite
<PathSet
>([&]() {
673 foreach (PathSet::const_iterator
, i
, paths
)
674 if (isValidPath_(*i
)) res
.insert(*i
);
680 PathSet
LocalStore::queryAllValidPaths()
682 return retrySQLite
<PathSet
>([&]() {
683 auto use(stmtQueryValidPaths
.use());
685 while (use
.next()) res
.insert(use
.getStr(0));
691 void LocalStore::queryReferences(const Path
& path
,
692 PathSet
& references
)
694 ValidPathInfo info
= queryPathInfo(path
);
695 references
.insert(info
.references
.begin(), info
.references
.end());
699 void LocalStore::queryReferrers_(const Path
& path
, PathSet
& referrers
)
701 auto useQueryReferrers(stmtQueryReferrers
.use()(path
));
703 while (useQueryReferrers
.next())
704 referrers
.insert(useQueryReferrers
.getStr(0));
708 void LocalStore::queryReferrers(const Path
& path
, PathSet
& referrers
)
710 assertStorePath(path
);
711 return retrySQLite
<void>([&]() {
712 queryReferrers_(path
, referrers
);
717 Path
LocalStore::queryDeriver(const Path
& path
)
719 return queryPathInfo(path
).deriver
;
723 PathSet
LocalStore::queryValidDerivers(const Path
& path
)
725 assertStorePath(path
);
727 return retrySQLite
<PathSet
>([&]() {
728 auto useQueryValidDerivers(stmtQueryValidDerivers
.use()(path
));
731 while (useQueryValidDerivers
.next())
732 derivers
.insert(useQueryValidDerivers
.getStr(1));
739 PathSet
LocalStore::queryDerivationOutputs(const Path
& path
)
741 return retrySQLite
<PathSet
>([&]() {
742 auto useQueryDerivationOutputs(stmtQueryDerivationOutputs
.use()(queryValidPathId(path
)));
745 while (useQueryDerivationOutputs
.next())
746 outputs
.insert(useQueryDerivationOutputs
.getStr(1));
753 StringSet
LocalStore::queryDerivationOutputNames(const Path
& path
)
755 return retrySQLite
<StringSet
>([&]() {
756 auto useQueryDerivationOutputs(stmtQueryDerivationOutputs
.use()(queryValidPathId(path
)));
758 StringSet outputNames
;
759 while (useQueryDerivationOutputs
.next())
760 outputNames
.insert(useQueryDerivationOutputs
.getStr(0));
767 Path
LocalStore::queryPathFromHashPart(const string
& hashPart
)
769 if (hashPart
.size() != 32) throw Error("invalid hash part");
771 Path prefix
= settings
.nixStore
+ "/" + hashPart
;
773 return retrySQLite
<Path
>([&]() -> Path
{
774 auto useQueryPathFromHashPart(stmtQueryPathFromHashPart
.use()(prefix
));
776 if (!useQueryPathFromHashPart
.next()) return "";
778 const char * s
= (const char *) sqlite3_column_text(stmtQueryPathFromHashPart
, 0);
779 return s
&& prefix
.compare(0, prefix
.size(), s
, prefix
.size()) == 0 ? s
: "";
783 /* Read a line from the substituter's reply file descriptor, while also
784 processing its stderr. */
785 string
LocalStore::getLineFromSubstituter(Agent
& run
)
794 FD_SET(run
.fromAgent
.readSide
, &fds
);
795 FD_SET(run
.builderOut
.readSide
, &fds
);
797 /* Wait for data to appear on the substituter's stdout or
799 if (select(std::max(run
.fromAgent
.readSide
, run
.builderOut
.readSide
) + 1, &fds
, 0, 0, 0) == -1) {
800 if (errno
== EINTR
) continue;
801 throw SysError("waiting for input from the substituter");
804 /* Completely drain stderr before dealing with stdout. */
805 if (FD_ISSET(run
.fromAgent
.readSide
, &fds
)) {
807 ssize_t n
= read(run
.fromAgent
.readSide
, (unsigned char *) buf
, sizeof(buf
));
809 if (errno
== EINTR
) continue;
810 throw SysError("reading from substituter's stderr");
812 if (n
== 0) throw EndOfFile(format("`%1% substitute' died unexpectedly")
813 % settings
.guixProgram
);
816 while (((p
= err
.find('\n')) != string::npos
)
817 || ((p
= err
.find('\r')) != string::npos
)) {
818 string
thing(err
, 0, p
+ 1);
819 writeToStderr("substitute: " + thing
);
820 err
= string(err
, p
+ 1);
824 /* Read from stdout until we get a newline or the buffer is empty. */
825 else if (FD_ISSET(run
.builderOut
.readSide
, &fds
)) {
827 readFull(run
.builderOut
.readSide
, (unsigned char *) &c
, 1);
829 if (!err
.empty()) printMsg(lvlError
, "substitute: " + err
);
838 template<class T
> T
LocalStore::getIntLineFromSubstituter(Agent
& run
)
840 string s
= getLineFromSubstituter(run
);
842 if (!string2Int(s
, res
)) throw Error("integer expected from stream");
847 PathSet
LocalStore::querySubstitutablePaths(const PathSet
& paths
)
851 if (!settings
.useSubstitutes
|| paths
.empty()) return res
;
853 Agent
& run
= *substituter();
856 foreach (PathSet::const_iterator
, j
, paths
)
857 if (res
.find(*j
) == res
.end()) { s
+= *j
; s
+= " "; }
858 writeLine(run
.toAgent
.writeSide
, s
);
860 /* FIXME: we only read stderr when an error occurs, so
861 substituters should only write (short) messages to
862 stderr when they fail. I.e. they shouldn't write debug
864 Path path
= getLineFromSubstituter(run
);
865 if (path
== "") break;
873 std::shared_ptr
<Agent
> LocalStore::substituter()
875 if (!runningSubstituter
) {
876 const Strings args
= { "substitute", "--query" };
877 const std::map
<string
, string
> env
= { { "_NIX_OPTIONS", settings
.pack() } };
878 runningSubstituter
= std::make_shared
<Agent
>(settings
.guixProgram
, args
, env
);
881 return runningSubstituter
;
884 void LocalStore::querySubstitutablePathInfos(PathSet
& paths
, SubstitutablePathInfos
& infos
)
886 if (!settings
.useSubstitutes
) return;
888 Agent
& run
= *substituter();
891 foreach (PathSet::const_iterator
, i
, paths
)
892 if (infos
.find(*i
) == infos
.end()) { s
+= *i
; s
+= " "; }
893 writeLine(run
.toAgent
.writeSide
, s
);
896 Path path
= getLineFromSubstituter(run
);
897 if (path
== "") break;
898 if (paths
.find(path
) == paths
.end())
899 throw Error(format("got unexpected path `%1%' from substituter") % path
);
901 SubstitutablePathInfo
& info(infos
[path
]);
902 info
.deriver
= getLineFromSubstituter(run
);
903 if (info
.deriver
!= "") assertStorePath(info
.deriver
);
904 int nrRefs
= getIntLineFromSubstituter
<int>(run
);
906 Path p
= getLineFromSubstituter(run
);
908 info
.references
.insert(p
);
910 info
.downloadSize
= getIntLineFromSubstituter
<long long>(run
);
911 info
.narSize
= getIntLineFromSubstituter
<long long>(run
);
916 void LocalStore::querySubstitutablePathInfos(const PathSet
& paths
,
917 SubstitutablePathInfos
& infos
)
919 if (!paths
.empty()) {
920 PathSet todo
= paths
;
921 querySubstitutablePathInfos(todo
, infos
);
926 Hash
LocalStore::queryPathHash(const Path
& path
)
928 return queryPathInfo(path
).hash
;
932 void LocalStore::registerValidPath(const ValidPathInfo
& info
)
934 ValidPathInfos infos
;
935 infos
.push_back(info
);
936 registerValidPaths(infos
);
940 void LocalStore::registerValidPaths(const ValidPathInfos
& infos
)
942 /* SQLite will fsync by default, but the new valid paths may not be fsync-ed.
943 * So some may want to fsync them before registering the validity, at the
944 * expense of some speed of the path registering operation. */
945 if (settings
.syncBeforeRegistering
) sync();
947 return retrySQLite
<void>([&]() {
951 foreach (ValidPathInfos::const_iterator
, i
, infos
) {
952 assert(i
->hash
.type
== htSHA256
);
953 if (isValidPath_(i
->path
))
956 addValidPath(*i
, false);
957 paths
.insert(i
->path
);
960 for (auto & i
: infos
) {
961 auto referrer
= queryValidPathId(i
.path
);
962 for (auto & j
: i
.references
)
963 addReference(referrer
, queryValidPathId(j
));
966 /* Check that the derivation outputs are correct. We can't do
967 this in addValidPath() above, because the references might
969 foreach (ValidPathInfos::const_iterator
, i
, infos
)
970 if (isDerivation(i
->path
)) {
971 // FIXME: inefficient; we already loaded the
972 // derivation in addValidPath().
973 Derivation drv
= readDerivation(i
->path
);
974 checkDerivationOutputs(i
->path
, drv
);
977 /* Do a topological sort of the paths. This will throw an
978 error if a cycle is detected and roll back the
979 transaction. Cycles can only occur when a derivation
980 has multiple outputs. */
981 topoSortPaths(*this, paths
);
988 /* Invalidate a path. The caller is responsible for checking that
989 there are no referrers. */
990 void LocalStore::invalidatePath(const Path
& path
)
992 debug(format("invalidating path `%1%'") % path
);
994 drvHashes
.erase(path
);
996 stmtInvalidatePath
.use()(path
).exec();
998 /* Note that the foreign key constraints on the Refs table take
999 care of deleting the references entries for `path'. */
1003 Path
LocalStore::addToStoreFromDump(const string
& dump
, const string
& name
,
1004 bool recursive
, HashType hashAlgo
, bool repair
)
1006 Hash h
= hashString(hashAlgo
, dump
);
1008 Path dstPath
= makeFixedOutputPath(recursive
, hashAlgo
, h
, name
);
1010 addTempRoot(dstPath
);
1012 if (repair
|| !isValidPath(dstPath
)) {
1014 /* The first check above is an optimisation to prevent
1015 unnecessary lock acquisition. */
1017 PathLocks
outputLock(singleton
<PathSet
, Path
>(dstPath
));
1019 if (repair
|| !isValidPath(dstPath
)) {
1021 if (pathExists(dstPath
)) deletePath(dstPath
);
1024 StringSource
source(dump
);
1025 restorePath(dstPath
, source
);
1027 writeFile(dstPath
, dump
);
1029 canonicalisePathMetaData(dstPath
, -1);
1031 /* Register the SHA-256 hash of the NAR serialisation of
1032 the path in the database. We may just have computed it
1033 above (if called with recursive == true and hashAlgo ==
1034 sha256); otherwise, compute it here. */
1037 hash
.first
= hashAlgo
== htSHA256
? h
: hashString(htSHA256
, dump
);
1038 hash
.second
= dump
.size();
1040 hash
= hashPath(htSHA256
, dstPath
);
1042 optimisePath(dstPath
); // FIXME: combine with hashPath()
1045 info
.path
= dstPath
;
1046 info
.hash
= hash
.first
;
1047 info
.narSize
= hash
.second
;
1048 registerValidPath(info
);
1051 outputLock
.setDeletion(true);
1058 Path
LocalStore::addToStore(const string
& name
, const Path
& _srcPath
,
1059 bool recursive
, HashType hashAlgo
, PathFilter
& filter
, bool repair
)
1061 Path
srcPath(absPath(_srcPath
));
1062 debug(format("adding `%1%' to the store") % srcPath
);
1064 /* Read the whole path into memory. This is not a very scalable
1065 method for very large paths, but `copyPath' is mainly used for
1069 dumpPath(srcPath
, sink
, filter
);
1071 sink
.s
= readFile(srcPath
);
1073 return addToStoreFromDump(sink
.s
, name
, recursive
, hashAlgo
, repair
);
1077 Path
LocalStore::addTextToStore(const string
& name
, const string
& s
,
1078 const PathSet
& references
, bool repair
)
1080 Path dstPath
= computeStorePathForText(name
, s
, references
);
1082 addTempRoot(dstPath
);
1084 if (repair
|| !isValidPath(dstPath
)) {
1086 PathLocks
outputLock(singleton
<PathSet
, Path
>(dstPath
));
1088 if (repair
|| !isValidPath(dstPath
)) {
1090 if (pathExists(dstPath
)) deletePath(dstPath
);
1092 writeFile(dstPath
, s
);
1094 canonicalisePathMetaData(dstPath
, -1);
1096 HashResult hash
= hashPath(htSHA256
, dstPath
);
1098 optimisePath(dstPath
);
1101 info
.path
= dstPath
;
1102 info
.hash
= hash
.first
;
1103 info
.narSize
= hash
.second
;
1104 info
.references
= references
;
1105 registerValidPath(info
);
1108 outputLock
.setDeletion(true);
1115 struct HashAndWriteSink
: Sink
1119 HashAndWriteSink(Sink
& writeSink
) : writeSink(writeSink
), hashSink(htSHA256
)
1122 virtual void operator () (const unsigned char * data
, size_t len
)
1124 writeSink(data
, len
);
1125 hashSink(data
, len
);
1129 return hashSink
.currentHash().first
;
1134 #define EXPORT_MAGIC 0x4558494e
1137 static void checkSecrecy(const Path
& path
)
1140 if (stat(path
.c_str(), &st
))
1141 throw SysError(format("getting status of `%1%'") % path
);
1142 if ((st
.st_mode
& (S_IRWXG
| S_IRWXO
)) != 0)
1143 throw Error(format("file `%1%' should be secret (inaccessible to everybody else)!") % path
);
1147 /* Return the authentication agent, a "guix authenticate" process started
1149 static std::shared_ptr
<Agent
> authenticationAgent()
1151 static std::shared_ptr
<Agent
> agent
;
1154 Strings args
= { "authenticate" };
1155 agent
= std::make_shared
<Agent
>(settings
.guixProgram
, args
);
1161 /* Read an integer and the byte that immediately follows it from FD. Return
1163 static int readInteger(int fd
)
1169 ssize_t rd
= read(fd
, &ch
, 1);
1172 throw SysError("reading an integer");
1174 throw EndOfFile("unexpected EOF reading an integer");
1187 /* Read from FD a reply coming from 'guix authenticate'. The reply has the
1188 form "CODE LEN:STR". CODE is an integer, where zero indicates success.
1189 LEN specifies the length in bytes of the string that immediately
1191 static std::string
readAuthenticateReply(int fd
)
1193 int code
= readInteger(fd
);
1194 int len
= readInteger(fd
);
1198 readFull(fd
, (unsigned char *) &str
[0], len
);
1206 /* Sign HASH with the key stored in file SECRETKEY. Return the signature as a
1207 string, or raise an exception upon error. */
1208 static std::string
signHash(const string
&secretKey
, const Hash
&hash
)
1210 auto agent
= authenticationAgent();
1211 auto hexHash
= printHash(hash
);
1213 writeLine(agent
->toAgent
.writeSide
,
1214 (format("sign %1%:%2% %3%:%4%")
1215 % secretKey
.size() % secretKey
1216 % hexHash
.size() % hexHash
).str());
1218 return readAuthenticateReply(agent
->fromAgent
.readSide
);
1221 /* Verify SIGNATURE and return the base16-encoded hash over which it was
1223 static std::string
verifySignature(const string
&signature
)
1225 auto agent
= authenticationAgent();
1227 writeLine(agent
->toAgent
.writeSide
,
1228 (format("verify %1%:%2%")
1229 % signature
.size() % signature
).str());
1231 return readAuthenticateReply(agent
->fromAgent
.readSide
);
1234 void LocalStore::exportPath(const Path
& path
, bool sign
,
1237 assertStorePath(path
);
1239 printMsg(lvlInfo
, format("exporting path `%1%'") % path
);
1241 if (!isValidPath(path
))
1242 throw Error(format("path `%1%' is not valid") % path
);
1244 HashAndWriteSink
hashAndWriteSink(sink
);
1246 dumpPath(path
, hashAndWriteSink
);
1248 /* Refuse to export paths that have changed. This prevents
1249 filesystem corruption from spreading to other machines.
1250 Don't complain if the stored hash is zero (unknown). */
1251 Hash hash
= hashAndWriteSink
.currentHash();
1252 Hash storedHash
= queryPathHash(path
);
1253 if (hash
!= storedHash
&& storedHash
!= Hash(storedHash
.type
))
1254 throw Error(format("hash of path `%1%' has changed from `%2%' to `%3%'!") % path
1255 % printHash(storedHash
) % printHash(hash
));
1257 writeInt(EXPORT_MAGIC
, hashAndWriteSink
);
1259 writeString(path
, hashAndWriteSink
);
1262 queryReferences(path
, references
);
1263 writeStrings(references
, hashAndWriteSink
);
1265 Path deriver
= queryDeriver(path
);
1266 writeString(deriver
, hashAndWriteSink
);
1269 Hash hash
= hashAndWriteSink
.currentHash();
1271 writeInt(1, hashAndWriteSink
);
1273 Path secretKey
= settings
.nixConfDir
+ "/signing-key.sec";
1274 checkSecrecy(secretKey
);
1276 string signature
= signHash(secretKey
, hash
);
1278 writeString(signature
, hashAndWriteSink
);
1281 writeInt(0, hashAndWriteSink
);
1285 struct HashAndReadSource
: Source
1287 Source
& readSource
;
1290 HashAndReadSource(Source
& readSource
) : readSource(readSource
), hashSink(htSHA256
)
1294 size_t read(unsigned char * data
, size_t len
)
1296 size_t n
= readSource
.read(data
, len
);
1297 if (hashing
) hashSink(data
, n
);
1303 /* Create a temporary directory in the store that won't be
1304 garbage-collected. */
1305 Path
LocalStore::createTempDirInStore()
1309 /* There is a slight possibility that `tmpDir' gets deleted by
1310 the GC between createTempDir() and addTempRoot(), so repeat
1311 until `tmpDir' exists. */
1312 tmpDir
= createTempDir(settings
.nixStore
);
1313 addTempRoot(tmpDir
);
1314 } while (!pathExists(tmpDir
));
1319 Path
LocalStore::importPath(bool requireSignature
, Source
& source
)
1321 HashAndReadSource
hashAndReadSource(source
);
1323 /* We don't yet know what store path this archive contains (the
1324 store path follows the archive data proper), and besides, we
1325 don't know yet whether the signature is valid. */
1326 Path tmpDir
= createTempDirInStore();
1327 AutoDelete
delTmp(tmpDir
);
1328 Path unpacked
= tmpDir
+ "/unpacked";
1330 restorePath(unpacked
, hashAndReadSource
);
1332 unsigned int magic
= readInt(hashAndReadSource
);
1333 if (magic
!= EXPORT_MAGIC
)
1334 throw Error("normalized archive cannot be imported; wrong format");
1336 Path dstPath
= readStorePath(hashAndReadSource
);
1338 PathSet references
= readStorePaths
<PathSet
>(hashAndReadSource
);
1340 Path deriver
= readString(hashAndReadSource
);
1341 if (deriver
!= "") assertStorePath(deriver
);
1343 Hash hash
= hashAndReadSource
.hashSink
.finish().first
;
1344 hashAndReadSource
.hashing
= false;
1346 bool haveSignature
= readInt(hashAndReadSource
) == 1;
1348 if (requireSignature
&& !haveSignature
)
1349 throw Error(format("imported archive of `%1%' lacks a signature") % dstPath
);
1351 if (haveSignature
) {
1352 string signature
= readString(hashAndReadSource
);
1354 if (requireSignature
) {
1355 string hash2
= verifySignature(signature
);
1357 /* Note: runProgram() throws an exception if the signature
1360 if (printHash(hash
) != hash2
)
1362 "signed hash doesn't match actual contents of imported "
1363 "archive; archive could be corrupt, or someone is trying "
1364 "to import a Trojan horse");
1368 /* Do the actual import. */
1370 /* !!! way too much code duplication with addTextToStore() etc. */
1371 addTempRoot(dstPath
);
1373 if (!isValidPath(dstPath
)) {
1375 PathLocks outputLock
;
1377 /* Lock the output path. But don't lock if we're being called
1378 from a build hook (whose parent process already acquired a
1379 lock on this path). */
1380 Strings locksHeld
= tokenizeString
<Strings
>(getEnv("NIX_HELD_LOCKS"));
1381 if (find(locksHeld
.begin(), locksHeld
.end(), dstPath
) == locksHeld
.end())
1382 outputLock
.lockPaths(singleton
<PathSet
, Path
>(dstPath
));
1384 if (!isValidPath(dstPath
)) {
1386 if (pathExists(dstPath
)) deletePath(dstPath
);
1388 if (rename(unpacked
.c_str(), dstPath
.c_str()) == -1)
1389 throw SysError(format("cannot move `%1%' to `%2%'")
1390 % unpacked
% dstPath
);
1392 canonicalisePathMetaData(dstPath
, -1);
1394 /* !!! if we were clever, we could prevent the hashPath()
1396 HashResult hash
= hashPath(htSHA256
, dstPath
);
1398 optimisePath(dstPath
); // FIXME: combine with hashPath()
1401 info
.path
= dstPath
;
1402 info
.hash
= hash
.first
;
1403 info
.narSize
= hash
.second
;
1404 info
.references
= references
;
1405 info
.deriver
= deriver
!= "" && isValidPath(deriver
) ? deriver
: "";
1406 registerValidPath(info
);
1409 outputLock
.setDeletion(true);
1416 Paths
LocalStore::importPaths(bool requireSignature
, Source
& source
)
1420 unsigned long long n
= readLongLong(source
);
1422 if (n
!= 1) throw Error("input doesn't look like something created by `nix-store --export'");
1423 res
.push_back(importPath(requireSignature
, source
));
1429 void LocalStore::invalidatePathChecked(const Path
& path
)
1431 assertStorePath(path
);
1433 retrySQLite
<void>([&]() {
1436 if (isValidPath_(path
)) {
1437 PathSet referrers
; queryReferrers_(path
, referrers
);
1438 referrers
.erase(path
); /* ignore self-references */
1439 if (!referrers
.empty())
1440 throw PathInUse(format("cannot delete path `%1%' because it is in use by %2%")
1441 % path
% showPaths(referrers
));
1442 invalidatePath(path
);
1450 bool LocalStore::verifyStore(bool checkContents
, bool repair
)
1452 printMsg(lvlError
, format("reading the store..."));
1454 bool errors
= false;
1456 /* Acquire the global GC lock to prevent a garbage collection. */
1457 AutoCloseFD fdGCLock
= openGCLock(ltWrite
);
1460 for (auto & i
: readDirectory(settings
.nixStore
)) store
.insert(i
.name
);
1462 /* Check whether all valid paths actually exist. */
1463 printMsg(lvlInfo
, "checking path existence...");
1465 PathSet validPaths2
= queryAllValidPaths(), validPaths
, done
;
1467 foreach (PathSet::iterator
, i
, validPaths2
)
1468 verifyPath(*i
, store
, done
, validPaths
, repair
, errors
);
1470 /* Release the GC lock so that checking content hashes (which can
1471 take ages) doesn't block the GC or builds. */
1474 /* Optionally, check the content hashes (slow). */
1475 if (checkContents
) {
1476 printMsg(lvlInfo
, "checking hashes...");
1478 Hash
nullHash(htSHA256
);
1480 foreach (PathSet::iterator
, i
, validPaths
) {
1482 ValidPathInfo info
= queryPathInfo(*i
);
1484 /* Check the content hash (optionally - slow). */
1485 printMsg(lvlTalkative
, format("checking contents of `%1%'") % *i
);
1486 HashResult current
= hashPath(info
.hash
.type
, *i
);
1488 if (info
.hash
!= nullHash
&& info
.hash
!= current
.first
) {
1489 printMsg(lvlError
, format("path `%1%' was modified! "
1490 "expected hash `%2%', got `%3%'")
1491 % *i
% printHash(info
.hash
) % printHash(current
.first
));
1492 if (repair
) repairPath(*i
); else errors
= true;
1495 bool update
= false;
1497 /* Fill in missing hashes. */
1498 if (info
.hash
== nullHash
) {
1499 printMsg(lvlError
, format("fixing missing hash on `%1%'") % *i
);
1500 info
.hash
= current
.first
;
1504 /* Fill in missing narSize fields (from old stores). */
1505 if (info
.narSize
== 0) {
1506 printMsg(lvlError
, format("updating size field on `%1%' to %2%") % *i
% current
.second
);
1507 info
.narSize
= current
.second
;
1511 if (update
) updatePathInfo(info
);
1515 } catch (Error
& e
) {
1516 /* It's possible that the path got GC'ed, so ignore
1517 errors on invalid paths. */
1518 if (isValidPath(*i
))
1519 printMsg(lvlError
, format("error: %1%") % e
.msg());
1521 printMsg(lvlError
, format("warning: %1%") % e
.msg());
1531 void LocalStore::verifyPath(const Path
& path
, const PathSet
& store
,
1532 PathSet
& done
, PathSet
& validPaths
, bool repair
, bool & errors
)
1536 if (done
.find(path
) != done
.end()) return;
1539 if (!isStorePath(path
)) {
1540 printMsg(lvlError
, format("path `%1%' is not in the store") % path
);
1541 invalidatePath(path
);
1545 if (store
.find(baseNameOf(path
)) == store
.end()) {
1546 /* Check any referrers first. If we can invalidate them
1547 first, then we can invalidate this path as well. */
1548 bool canInvalidate
= true;
1549 PathSet referrers
; queryReferrers(path
, referrers
);
1550 foreach (PathSet::iterator
, i
, referrers
)
1552 verifyPath(*i
, store
, done
, validPaths
, repair
, errors
);
1553 if (validPaths
.find(*i
) != validPaths
.end())
1554 canInvalidate
= false;
1557 if (canInvalidate
) {
1558 printMsg(lvlError
, format("path `%1%' disappeared, removing from database...") % path
);
1559 invalidatePath(path
);
1561 printMsg(lvlError
, format("path `%1%' disappeared, but it still has valid referrers!") % path
);
1565 } catch (Error
& e
) {
1566 printMsg(lvlError
, format("warning: %1%") % e
.msg());
1575 validPaths
.insert(path
);
1579 bool LocalStore::pathContentsGood(const Path
& path
)
1581 std::map
<Path
, bool>::iterator i
= pathContentsGoodCache
.find(path
);
1582 if (i
!= pathContentsGoodCache
.end()) return i
->second
;
1583 printMsg(lvlInfo
, format("checking path `%1%'...") % path
);
1584 ValidPathInfo info
= queryPathInfo(path
);
1586 if (!pathExists(path
))
1589 HashResult current
= hashPath(info
.hash
.type
, path
);
1590 Hash
nullHash(htSHA256
);
1591 res
= info
.hash
== nullHash
|| info
.hash
== current
.first
;
1593 pathContentsGoodCache
[path
] = res
;
1594 if (!res
) printMsg(lvlError
, format("path `%1%' is corrupted or missing!") % path
);
1599 void LocalStore::markContentsGood(const Path
& path
)
1601 pathContentsGoodCache
[path
] = true;
1605 void LocalStore::vacuumDB()
1607 if (sqlite3_exec(db
, "vacuum;", 0, 0, 0) != SQLITE_OK
)
1608 throwSQLiteError(db
, "vacuuming SQLite database");
1612 void LocalStore::createUser(const std::string
& userName
, uid_t userId
)
1614 auto dir
= settings
.nixStateDir
+ "/profiles/per-user/" + userName
;
1617 if (chmod(dir
.c_str(), 0755) == -1)
1618 throw SysError(format("changing permissions of directory '%s'") % dir
);
1619 if (chown(dir
.c_str(), userId
, -1) == -1)
1620 throw SysError(format("changing owner of directory '%s'") % dir
);