aboutsummaryrefslogtreecommitdiff
path: root/src/libstore/lock.cc
blob: 1b1efb6b07e9af52c5d9da914f2cc79b27d4fd65 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
#include "lock.hh"
#include "globals.hh"
#include "pathlocks.hh"
#include "cgroup.hh"

#include <pwd.h>
#include <grp.h>

namespace nix {

struct SimpleUserLock : UserLock
{
    AutoCloseFD fdUserLock;
    uid_t uid;
    gid_t gid;
    std::vector<gid_t> supplementaryGIDs;

    void kill() override
    {
        killUser(uid);
    }

    std::pair<uid_t, uid_t> getUIDRange() override
    {
        assert(uid);
        return {uid, uid};
    }

    gid_t getGID() override { assert(gid); return gid; }

    std::vector<gid_t> getSupplementaryGIDs() override { return supplementaryGIDs; }

    static std::unique_ptr<UserLock> acquire()
    {
        assert(settings.buildUsersGroup != "");
        createDirs(settings.nixStateDir + "/userpool");

        /* Get the members of the build-users-group. */
        struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str());
        if (!gr)
            throw Error("the group '%s' specified in 'build-users-group' does not exist", settings.buildUsersGroup);

        /* Copy the result of getgrnam. */
        Strings users;
        for (char * * p = gr->gr_mem; *p; ++p) {
            debug("found build user '%s'", *p);
            users.push_back(*p);
        }

        if (users.empty())
            throw Error("the build users group '%s' has no members", settings.buildUsersGroup);

        /* Find a user account that isn't currently in use for another
           build. */
        for (auto & i : users) {
            debug("trying user '%s'", i);

            struct passwd * pw = getpwnam(i.c_str());
            if (!pw)
                throw Error("the user '%s' in the group '%s' does not exist", i, settings.buildUsersGroup);

            auto fnUserLock = fmt("%s/userpool/%s", settings.nixStateDir,pw->pw_uid);

            AutoCloseFD fd = open(fnUserLock.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600);
            if (!fd)
                throw SysError("opening user lock '%s'", fnUserLock);

            if (lockFile(fd.get(), ltWrite, false)) {
                auto lock = std::make_unique<SimpleUserLock>();

                lock->fdUserLock = std::move(fd);
                lock->uid = pw->pw_uid;
                lock->gid = gr->gr_gid;

                /* Sanity check... */
                if (lock->uid == getuid() || lock->uid == geteuid())
                    throw Error("the Nix user should not be a member of '%s'", settings.buildUsersGroup);

                #if __linux__
                /* Get the list of supplementary groups of this build
                   user.  This is usually either empty or contains a
                   group such as "kvm".  */
                lock->supplementaryGIDs.resize(10);
                int ngroups = lock->supplementaryGIDs.size();
                int err = getgrouplist(pw->pw_name, pw->pw_gid,
                    lock->supplementaryGIDs.data(), &ngroups);
                if (err == -1)
                    throw Error("failed to get list of supplementary groups for '%s'", pw->pw_name);

                lock->supplementaryGIDs.resize(ngroups);
                #endif

                return lock;
            }
        }

        return nullptr;
    }
};

#if __linux__
struct CgroupUserLock : UserLock
{
    AutoCloseFD fdUserLock;
    uid_t uid;

    void kill() override
    {
        if (cgroup) {
            destroyCgroup(*cgroup);
            cgroup.reset();
        }
    }

    std::pair<uid_t, uid_t> getUIDRange() override
    {
        assert(uid);
        return {uid, uid + settings.idsPerBuild - 1};
    }

    gid_t getGID() override
    {
        // We use the same GID ranges as for the UIDs.
        assert(uid);
        return uid;
    }

    std::vector<gid_t> getSupplementaryGIDs() override { return {}; }

    static std::unique_ptr<UserLock> acquire()
    {
        settings.requireExperimentalFeature("auto-allocate-uids");
        assert(settings.startId > 0);
        assert(settings.startId % settings.idsPerBuild == 0);
        assert(settings.uidCount % settings.idsPerBuild == 0);
        assert((uint64_t) settings.startId + (uint64_t) settings.uidCount <= std::numeric_limits<uid_t>::max());

        // FIXME: check whether the id range overlaps any known users

        createDirs(settings.nixStateDir + "/userpool2");

        size_t nrSlots = settings.uidCount / settings.idsPerBuild;

        for (size_t i = 0; i < nrSlots; i++) {
            debug("trying user slot '%d'", i);

            createDirs(settings.nixStateDir + "/userpool2");

            auto fnUserLock = fmt("%s/userpool2/slot-%d", settings.nixStateDir, i);

            AutoCloseFD fd = open(fnUserLock.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600);
            if (!fd)
                throw SysError("opening user lock '%s'", fnUserLock);

            if (lockFile(fd.get(), ltWrite, false)) {
                auto lock = std::make_unique<CgroupUserLock>();
                lock->fdUserLock = std::move(fd);
                lock->uid = settings.startId + i * settings.idsPerBuild;
                auto s = drainFD(lock->fdUserLock.get());
                if (s != "") lock->cgroup = s;
                return lock;
            }
        }

        return nullptr;
    }

    std::optional<Path> cgroup;

    std::optional<Path> getCgroup() override
    {
        if (!cgroup) {
            /* Create a systemd cgroup since that's the minimum
               required by systemd-nspawn. */
            auto ourCgroups = getCgroups("/proc/self/cgroup");
            auto systemdCgroup = ourCgroups["systemd"];
            if (systemdCgroup == "")
                throw Error("'systemd' cgroup does not exist");

            auto hostCgroup = canonPath("/sys/fs/cgroup/systemd/" + systemdCgroup);

            if (!pathExists(hostCgroup))
                throw Error("expected cgroup directory '%s'", hostCgroup);

            cgroup = fmt("%s/nix-%d", hostCgroup, uid);

            destroyCgroup(*cgroup);

            if (mkdir(cgroup->c_str(), 0755) == -1)
                throw SysError("creating cgroup '%s'", *cgroup);

            /* Record the cgroup in the lock file. This ensures that
               if we subsequently get executed under a different parent
               cgroup, we kill the previous cgroup first. */
            if (ftruncate(fdUserLock.get(), 0) == -1)
                throw Error("truncating user lock");
            writeFull(fdUserLock.get(), *cgroup);
        }

        return cgroup;
    };
};
#endif

std::unique_ptr<UserLock> acquireUserLock()
{
    #if __linux__
    if (settings.autoAllocateUids)
        return CgroupUserLock::acquire();
    else
    #endif
        return SimpleUserLock::acquire();
}

bool useBuildUsers()
{
    #if __linux__
    static bool b = (settings.buildUsersGroup != "" || settings.startId.get() != 0) && getuid() == 0;
    return b;
    #elif __APPLE__
    static bool b = settings.buildUsersGroup != "" && getuid() == 0;
    return b;
    #else
    return false;
    #endif
}

}