;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2016 John Darrington ;;; Copyright © 2017, 2018 Leo Famulari ;;; Copyright © 2018 Efraim Flashner ;;; Copyright © 2020 Ricardo Wurmus ;;; Copyright © 2020 Tobias Geerinckx-Rice ;;; Copyright © 2020 Lars-Dominik Braun ;;; ;;; This file is part of GNU Guix. ;;; ;;; GNU Guix is free software; you can redistribute it and/or modify it ;;; under the terms of the GNU General Public License as published by ;;; the Free Software Foundation; either version 3 of the License, or (at ;;; your option) any later version. ;;; ;;; GNU Guix is distributed in the hope that it will be useful, but ;;; WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;;; GNU General Public License for more details. ;;; ;;; You should have received a copy of the GNU
aboutsummaryrefslogtreecommitdiff
#include "config.h"
#include "local-store.hh"
#include "globals.hh"
#include "archive.hh"
#include "pathlocks.hh"
#include "worker-protocol.hh"
#include "derivations.hh"
#include "affinity.hh"

#include <iostream>
#include <algorithm>
#include <cstring>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <utime.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <time.h>
#include <grp.h>
#include <ctype.h>

#if HAVE_UNSHARE && HAVE_STATVFS && HAVE_SYS_MOUNT_H
#include <sched.h>
#include <sys/statvfs.h>
#include <sys/mount.h>
#endif

#include <sys/ioctl.h>
#include <errno.h>

#include <sqlite3.h>


namespace nix {


void checkStoreNotSymlink()
{
    if (getEnv("NIX_IGNORE_SYMLINK_STORE") == "1") return;
    Path path = settings.nixStore;
    struct stat st;
    while (path != "/") {
        if (lstat(path.c_str(), &st))
            throw SysError(format("getting status of `%1%'") % path);
        if (S_ISLNK(st.st_mode))
            throw Error(format(
                "the path `%1%' is a symlink; "
                "this is not allowed for the store and its parent directories")
                % path);
        path = dirOf(path);
    }
}


LocalStore::LocalStore(bool reserveSpace)
{
    schemaPath = settings.nixDBPath + "/schema";

    if (settings.readOnlyMode) {
        openDB(false);
        return;
    }

    /* Create missing state directories if they don't already exist. */
    createDirs(settings.nixStore);
    makeStoreWritable();
    createDirs(linksDir = settings.nixStore + "/.links");
    Path profilesDir = settings.nixStateDir + "/profiles";
    createDirs(profilesDir);
    createDirs(settings.nixStateDir + "/temproots");
    createDirs(settings.nixDBPath);
    Path gcRootsDir = settings.nixStateDir + "/gcroots";
    if (!pathExists(gcRootsDir)) {
        createDirs(gcRootsDir);
        createSymlink(profilesDir, gcRootsDir + "/profiles");
    }

    /* Optionally, create directories and set permissions for a
       multi-user install. */
    if (getuid() == 0 && settings.buildUsersGroup != "") {

        Path perUserDir = profilesDir + "/per-user";
        createDirs(perUserDir);
        if (chmod(perUserDir.c_str(), 0755) == -1)
            throw SysError(format("could not set permissions on '%1%' to 755")
                           % perUserDir);

        mode_t perm = 01775;

        struct group * gr = getgrnam(settings.buildUsersGroup.c_str());
        if (!gr)
            throw Error(format("the group `%1%' specified in `build-users-group' does not exist")
                % settings.buildUsersGroup);
        else {
            struct stat st;
            if (stat(settings.nixStore.c_str(), &st))
                throw SysError(format("getting attributes of path '%1%'") % settings.nixStore);

            if (st.st_uid != 0 || st.st_gid != gr->gr_gid || (st.st_mode & ~S_IFMT) != perm) {
                if (chown(settings.nixStore.c_str(), 0, gr->gr_gid) == -1)
                    throw SysError(format("changing ownership of path '%1%'") % settings.nixStore);
                if (chmod(settings.nixStore.c_str(), perm) == -1)
                    throw SysError(format("changing permissions on path '%1%'") % settings.nixStore);
            }
        }
    }

    checkStoreNotSymlink();

    /* We can't open a SQLite database if the disk is full.  Since
       this prevents the garbage collector from running when it's most
       needed, we reserve some dummy space that we can free just
       before doing a garbage collection. */
    try {
        Path reservedPath = settings.nixDBPath + "/reserved";
        if (reserveSpace) {
            struct stat st;
            if (stat(reservedPath.c_str(), &st) == -1 ||
                st.st_size != settings.reservedSize)
            {
                AutoCloseFD fd = open(reservedPath.c_str(), O_WRONLY | O_CREAT, 0600);
                int res = -1;
#if HAVE_POSIX_FALLOCATE
                res = posix_fallocate(fd, 0, settings.reservedSize);
#endif
                if (res == -1) {
                    writeFull(fd, string(settings.reservedSize, 'X'));
                    ftruncate(fd, settings.reservedSize);
                }
            }
        }
        else
            deletePath(reservedPath);
    } catch (SysError & e) { /* don't care about errors */
    }

    /* Acquire the big fat lock in shared mode to make sure that no
       schema upgrade is in progress. */
    try {
        Path globalLockPath = settings.nixDBPath + "/big-lock";
        globalLock = openLockFile(globalLockPath.c_str(), true);
    } catch (SysError & e) {
        if (e.errNo != EACCES) throw;
        settings.readOnlyMode = true;
        openDB(false);
        return;
    }

    if (!lockFile(globalLock, ltRead, false)) {
        printMsg(