-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathcreate.cpp
365 lines (319 loc) · 14.9 KB
/
create.cpp
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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
// Copyright (C) 2019 by Yuri Victorovich. All rights reserved.
#include "args.h"
#include "spec.h"
#include "locs.h"
#include "cmd.h"
#include "mount.h"
#include "scripts.h"
#include "util.h"
#include "err.h"
#include "commands.h"
#include <rang.hpp>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <set>
#include <functional>
#define ERR(msg...) ERR2("creating a crate", msg)
#define LOG(msg...) \
{ \
if (args.logProgress) \
std::cerr << rang::fg::gray << Util::tmSecMs() << ": " << msg << rang::style::reset << std::endl; \
}
// uid/gid
static uid_t myuid = ::getuid();
static gid_t mygid = ::getgid();
//
// helpers
//
static std::string guessCrateName(const Spec &spec) {
if (!spec.runCmdExecutable.empty())
return spec.runCmdExecutable.substr(spec.runCmdExecutable.rfind('/') + 1);
else {
std::ostringstream ss;
ss << *spec.runServices.rbegin();
for (auto it = spec.runServices.rbegin() + 1; it != spec.runServices.rend(); it++)
ss << '+' << *it;
return ss.str();
}
}
static void notifyUserOfLongProcess(bool begin, const std::string &processName, const std::string &doingWhat) {
std::cout << rang::fg::blue;
std::cout << "==" << std::endl;
if (begin)
std::cout << "== Running " << processName << " in order to " << doingWhat << std::endl;
else
std::cout << "== " << processName << " has finished to " << doingWhat << std::endl;
std::cout << "==" << rang::fg::reset << std::endl;
}
static void runChrootCommand(const std::string &jailPath, const std::string &cmd, const char *descr) {
Util::runCommand(STR(Cmd::chroot(jailPath) << cmd), descr);
}
static void installAndAddPackagesInJail(const std::string &jailPath,
const std::vector<std::string> &pkgsInstall,
const std::vector<std::string> &pkgsAdd,
const std::vector<std::pair<std::string, std::string>> &pkgLocalOverride,
const std::vector<std::string> &pkgNuke) {
// local helpers
auto J = [&jailPath](auto subdir) {
return STR(jailPath << subdir);
};
// notify
notifyUserOfLongProcess(true, "pkg", STR("install the required packages: " << (pkgsInstall+pkgsAdd)));
// install
if (!pkgsInstall.empty())
runChrootCommand(jailPath, STR("pkg install " << pkgsInstall), "install the requested packages into the jail");
if (!pkgsAdd.empty()) {
for (auto &p : pkgsAdd) {
Util::Fs::copyFile(p, STR(J("/tmp/") << Util::filePathToFileName(p)));
runChrootCommand(jailPath, STR("pkg add /tmp/" << Util::filePathToFileName(p)), "remove the added package files from jail");
}
}
// override packages with locally avaukable packages
for (auto lo : pkgLocalOverride) {
if (!Util::Fs::fileExists(lo.second))
ERR("package override: failed to find the package file '" << lo.second << "'")
runChrootCommand(jailPath, STR("pkg delete " << lo.first), CSTR("remove the package '" << lo.first << "' for local override in jail"));
Util::Fs::copyFile(lo.second, STR(J("/tmp/") << Util::filePathToFileName(lo.second)));
runChrootCommand(jailPath, STR("pkg add /tmp/" << Util::filePathToFileName(lo.second)), CSTR("add the local override package '" << lo.second << "' in jail"));
Util::Fs::unlink(J(STR("/tmp/" << Util::filePathToFileName(lo.second))));
}
// nuke packages when requested
for (auto &n : pkgNuke)
runChrootCommand(jailPath, STR("/usr/local/sbin/pkg-static delete -y -f " << n), "nuke the package in the jail");
// write the +CRATE.PKGS file
runChrootCommand(jailPath, STR("pkg info > " << J("/+CRATE.PKGS")), "write +CRATE.PKGS file");
// cleanup: delete the pkg package: it will not be needed any more, and delete the added package files
runChrootCommand(jailPath, "pkg delete -f pkg", "remove the 'pkg' package from jail");
if (!pkgsAdd.empty())
Util::runCommand(STR("rm " << jailPath << "/tmp/*"), "remove the added package files from jail");
// notify
notifyUserOfLongProcess(false, "pkg", STR("install the required packages: " << (pkgsInstall+pkgsAdd)));
}
static std::set<std::string> getElfDependencies(const std::string &elfPath, const std::string &jailPath,
std::function<bool(const std::string&)> filter = [](const std::string &path) {return true;})
{
std::set<std::string> dset;
// It is possible to use elf(3) to read shared library dependences from the elf file,
// but it's hard to then find the disk location, ld-elf.so does this through some WooDoo magic.
// Instead, we just use ldd(1) to read this information.
std::istringstream is(Util::runCommandGetOutput(
STR(Cmd::chroot(jailPath) << "/bin/sh -c \"ldd " << elfPath << " | grep '=>' | sed -e 's|.* => ||; s| .*||'\""), "get elf dependencies"));
std::string s;
while (std::getline(is, s, '\n'))
if (!s.empty() && filter(s))
dset.insert(s);
return dset;
}
static void removeRedundantJailParts(const std::string &jailPath, const Spec &spec) {
namespace Fs = Util::Fs;
const char *prefix = "/usr/local";
const char *prefixSlash = "/usr/local/";
auto prefixSlashSz = ::strlen(prefixSlash);
auto jailPathSz = jailPath.size();
// local helpers
auto J = [&jailPath](auto subdir) {
return STR(jailPath << subdir);
};
auto toJailPath = [J](const std::set<std::string> &in, std::set<std::string> &out) {
for (auto &p : in)
out.insert(J(p));
};
auto fromJailPath = [&jailPath,jailPathSz](const std::string &file) {
auto fileCstr = file.c_str();
assert(::strncmp(jailPath.c_str(), fileCstr, jailPathSz) == 0); // really begins with jailPath
return fileCstr + jailPathSz;
};
auto isBasePath = [prefixSlash,prefixSlashSz](const std::string &path) {
return ::strncmp(path.c_str(), prefixSlash, prefixSlashSz) != 0;
};
// form the 'except' set: it should only contain files in basem and they should begin with jailPath
std::set<std::string> except;
auto keepFile = [&except,&jailPath,J,toJailPath](auto &file) { // any file, not just ELF
except.insert(J(file));
if (Fs::isElfFileOrDir(J(file)) == 'E')
toJailPath(getElfDependencies(file, jailPath), except);
};
if (!spec.runCmdExecutable.empty()) {
if (isBasePath(spec.runCmdExecutable))
except.insert(J(spec.runCmdExecutable));
if (Fs::isElfFileOrDir(J(spec.runCmdExecutable)) == 'E')
toJailPath(getElfDependencies(spec.runCmdExecutable, jailPath), except);
}
for (auto &file : spec.baseKeep)
keepFile(file);
for (auto &fileWildcard : spec.baseKeepWildcard)
for (auto &file : Util::Fs::expandWildcards(fileWildcard, Cmd::chroot(jailPath)))
keepFile(file);
if (!spec.runServices.empty()) {
keepFile("/usr/sbin/service"); // needed to run a service
keepFile("/bin/cat"); // based on ktrace of 'service {name} start'
keepFile("/bin/chmod"); // --"--
keepFile("/usr/bin/env"); // --"--
keepFile("/bin/kenv"); // --"--
keepFile("/bin/mkdir"); // --"--
keepFile("/usr/bin/touch"); // --"--
keepFile("/usr/bin/procstat"); // --"--
keepFile("/usr/bin/grep"); // ?? needed?
keepFile("/sbin/sysctl"); // ??
keepFile("/usr/bin/limits"); // ??
keepFile("/usr/bin/sed"); // needed for /etc/rc.d/netif restart
keepFile("/bin/kenv"); // needed for /etc/rc.d/netif restart
keepFile("/usr/sbin/daemon"); // services are often run with daemon(8)
if (spec.runCmdExecutable.empty())
keepFile("/bin/sleep"); // our idle script runs
}
//keepFile("/sbin/rcorder"); // needed for /etc/rc
//keepFile("/usr/sbin/ip6addrctl"); // needed for /etc/rc
//keepFile("/usr/sbin/syslogd"); // needed for /etc/rc
//keepFile("/usr/bin/mktemp"); // needed for /etc/rc
//keepFile("/sbin/mdmfs"); // needed for /etc/rc
//keepFile("/bin/chmod"); // needed for /etc/rc
//keepFile("/usr/bin/find"); // needed for /etc/rc
//keepFile("/bin/mkdir"); // needed for /etc/rc
//keepFile("/usr/sbin/utx"); // needed for /etc/rc
//keepFile("/usr/bin/uname"); // needed for /etc/rc
//keepFile("/usr/bin/cmp"); // needed for /etc/rc
//keepFile("/bin/cp"); // needed for /etc/rc
//keepFile("/bin/chmod"); // needed for /etc/rc
//keepFile("/bin/rm"); // needed for /etc/rc
//keepFile("/bin/rmdir"); // needed for /etc/rc
keepFile("/sbin/sysctl"); // needed for /etc/rc.shutdown
if (spec.optionExists("net")) {
keepFile("/sbin/ifconfig"); // needed to set up interfaces
keepFile("/sbin/route"); // needed to set the default route
keepFile("/sbin/ipfw"); // needed to set firewall rules
keepFile("/usr/sbin/service"); // ??? needed for "net"-enabled crates to start the service ???
keepFile("/sbin/kldstat"); // ??? needed for "net"-enabled crates to start the service ???
keepFile("/sbin/kldload"); // ??? needed for "net"-enabled crates to start the service ???
}
keepFile("/bin/sleep"); // needed for /etc/rc.shutdown
keepFile("/bin/date"); // needed for /etc/rc.shutdown
keepFile("/bin/sh"); // (1) allow to create a user in jail, the user has to have the default shell (2) needed to run scrips when they are specified
keepFile("/usr/bin/env"); // allow to pass environment to jail
keepFile("/usr/sbin/pw"); // allow to add users in jail
keepFile("/usr/sbin/pwd_mkdb"); // allow to add users in jail
keepFile("/usr/libexec/ld-elf.so.1"); // needed to run elf executables
if (!spec.pkgInstall.empty() || !spec.pkgAdd.empty())
for (auto &e : Fs::findElfFiles(J(prefix)))
toJailPath(getElfDependencies(fromJailPath(e), jailPath, [isBasePath](const std::string &path) {return isBasePath(path);}), except);
// remove items
Fs::rmdirFlatExcept(J("/bin"), except);
Fs::rmdirHier(J("/boot"));
Fs::rmdirHier(J("/etc/periodic"));
Fs::unlink(J("/usr/lib/include"));
Fs::rmdirHierExcept(J("/lib"), except);
Fs::rmdirHierExcept(J("/usr/lib"), except);
Fs::rmdirHier(J("/usr/lib32"));
Fs::rmdirHier(J("/usr/include"));
Fs::rmdirHierExcept(J("/sbin"), except);
Fs::rmdirHierExcept(J("/usr/bin"), except);
Fs::rmdirHierExcept(J("/usr/sbin"), except);
Fs::rmdirHierExcept(J("/usr/libexec"), except);
Fs::rmdirHier(J("/usr/share/dtrace"));
Fs::rmdirHier(J("/usr/share/doc"));
Fs::rmdirHier(J("/usr/share/examples"));
Fs::rmdirHier(J("/usr/share/bsdconfig"));
Fs::rmdirHier(J("/usr/share/games"));
Fs::rmdirHier(J("/usr/share/i18n"));
Fs::rmdirHier(J("/usr/share/man"));
Fs::rmdirHier(J("/usr/share/misc"));
Fs::rmdirHier(J("/usr/share/pc-sysinstall"));
Fs::rmdirHier(J("/usr/share/openssl"));
Fs::rmdirHier(J("/usr/tests"));
Fs::rmdir (J("/usr/src"));
Fs::rmdir (J("/usr/obj"));
Fs::rmdirHier(J("/var/db/etcupdate"));
Fs::rmdirFlat(J("/rescue"));
if (!spec.pkgInstall.empty() || !spec.pkgAdd.empty()) {
Fs::rmdirFlat(J("/var/cache/pkg"));
Fs::rmdirFlat(J("/var/db/pkg"));
}
// remove static libs if not requested to keep them
if (!spec.optionExists("no-rm-static-libs"))
Util::runCommand(STR("find " << jailPath << " -name '*.a' | xargs rm"), "remove static libs");
}
//
// interface
//
bool createCrate(const Args &args, const Spec &spec) {
int res;
LOG("'create' command is invoked")
// output crate file name
auto crateFileName = !args.createOutput.empty() ? args.createOutput : STR(guessCrateName(spec) << ".crate");
// download the base archive if not yet
if (!Util::Fs::fileExists(Locations::baseArchive)) {
std::cout << "downloading base.txz from " << Locations::baseArchiveUrl << " ..." << std::endl;
Util::runCommand(STR("fetch -o " << Locations::baseArchive << " " << Locations::baseArchiveUrl), "download base.txz");
std::cout << "base.txz has finished downloading" << std::endl;
}
// create the jail directory
auto jailPath = STR(Locations::jailDirectoryPath << "/chroot-create-" << Util::filePathToBareName(crateFileName) << "-pid" << ::getpid());
res = mkdir(jailPath.c_str(), S_IRUSR|S_IWUSR|S_IXUSR);
if (res == -1)
ERR("failed to create the jail directory '" << jailPath << "': " << strerror(errno))
// helper
auto runScript = [&jailPath,&spec](const char *section) {
Scripts::section(section, spec.scripts, [&jailPath,section](const std::string &cmd) {
runChrootCommand(jailPath, cmd, CSTR("run script#" << section));
});
};
RunAtEnd destroyJailDir([&jailPath,&args]() {
// remove the (future) jail directory
LOG("removing the the jail directory")
Util::Fs::rmdirHier(jailPath);
});
// unpack the base archive
LOG("unpacking the base archive")
Util::runCommand(STR("cat " << Locations::baseArchive
<< " | " << Cmd::xz << " --decompress | tar -xf - --uname \"\" --gname \"\" -C " << jailPath),
"unpack the system base into the jail directory");
runScript("create:start");
// copy /etc/resolv.conf into the jail directory such that pkg would be able to resolve addresses
Util::Fs::copyFile("/etc/resolv.conf", STR(jailPath << "/etc/resolv.conf"));
// mount devfs
LOG("mounting devfs in jail")
Mount mountDevfs("devfs", STR(jailPath << "/dev"), "");
mountDevfs.mount();
// mount the pkg cache
LOG("mounting pkg cache and as nullfs in jail")
Util::Fs::mkdir(STR(jailPath << "/var/cache/pkg"), 0755);
Mount mountPkgCache("nullfs", STR(jailPath << "/var/cache/pkg"), "/var/cache/pkg");
mountPkgCache.mount();
// install packages into the jail, if needed
if (!spec.pkgInstall.empty() || !spec.pkgAdd.empty()) {
LOG("installing packages ...")
installAndAddPackagesInJail(jailPath, spec.pkgInstall, spec.pkgAdd, spec.pkgLocalOverride, spec.pkgNuke);
LOG("done installing packages")
}
// unmount
LOG("unmounting devfs in jail")
mountDevfs.unmount();
LOG("unmounting pkg cache in jail")
mountPkgCache.unmount();
// remove parts that aren't needed
LOG("removing unnecessary parts")
removeRedundantJailParts(jailPath, spec);
// remove /etc/resolv.conf in the jail directory
Util::Fs::unlink(STR(jailPath << "/etc/resolv.conf"));
// write the +CRATE-SPEC file
LOG("write the +CRATE.SPEC file")
Util::Fs::copyFile(args.createSpec, STR(jailPath << "/+CRATE.SPEC"));
// scripts: end-create
runScript("create:end");
// pack the jail into a .crate file
LOG("creating the crate file " << crateFileName)
Util::runCommand(STR("tar cf - -C " << jailPath << " . | " << Cmd::xz << " --extreme > " << crateFileName), "compress the jail directory into the crate file");
Util::Fs::chown(crateFileName, myuid, mygid);
// remove the create directory
destroyJailDir.doNow();
// finished
std::cout << "the crate file '" << crateFileName << "' has been created" << std::endl;
LOG("'create' command has succeeded")
return true;
}