projects/guix·blob javascript-team · refs

1552 lines · 39.5 KiB

1#include "config.h"
2
3#include "util.hh"
4#include "affinity.hh"
5
6#include <iostream>
7#include <cerrno>
8#include <cstdio>
9#include <cstdlib>
10#include <cstring>
11#include <cassert>
12#include <format>
13
14#include <sys/wait.h>
15#include <unistd.h>
16#include <fcntl.h>
17#include <limits.h>
18#include <sys/socket.h>
19
20#ifdef __APPLE__
21#include <sys/syscall.h>
22#endif
23
24#ifdef __linux__
25#include <sys/prctl.h>
26#endif
27
28#ifdef HAVE_LINUX_CLOSE_RANGE_H
29# include <linux/close_range.h>
30#endif
31
32
33extern char * * environ;
34
35
36namespace nix {
37
38
39BaseError::BaseError(std::string_view fs, unsigned int status)
40 : status(status)
41{
42 err = fs;
43}
44
45
46BaseError & BaseError::addPrefix(std::string fs)
47{
48 prefix_ = fs + prefix_;
49 return *this;
50}
51
52
53SysError::SysError(std::string_view fs)
54 : Error(std::format("{}: {}", fs, strerror(errno)))
55 , errNo(errno)
56{
57}
58
59
60string getEnv(const string & key, const string & def)
61{
62 char * value = getenv(key.c_str());
63 return value ? string(value) : def;
64}
65
66
67string findProgram(const string & program)
68{
69 if(program.empty()) return "";
70
71 if(program[0] == '/') return pathExists(program) ? program : "";
72
73 char *path_ = getenv("PATH");
74 if(path_ == NULL) return "";
75 string path = path_;
76
77 Strings dirs = tokenizeString<Strings>(path, ":");
78 for (const auto& i : dirs) {
79 if(i == "") continue;
80 string f = i + "/" + program;
81 if(pathExists(f)) return f;
82 }
83
84 return "";
85}
86
87
88Path absPath(Path path, Path dir)
89{
90 if (path[0] != '/') {
91 if (dir == "") {
92#ifdef __GNU__
93 /* GNU (aka. GNU/Hurd) doesn't have any limitation on path
94 lengths and doesn't define `PATH_MAX'. */
95 char *buf = getcwd(NULL, 0);
96 if (buf == NULL)
97#else
98 char buf[PATH_MAX];
99 if (!getcwd(buf, sizeof(buf)))
100#endif
101 throw SysError("cannot get cwd");
102 dir = buf;
103#ifdef __GNU__
104 free(buf);
105#endif
106 }
107 path = dir + "/" + path;
108 }
109 return canonPath(path);
110}
111
112
113Path canonPath(const Path & path, bool resolveSymlinks)
114{
115 string s;
116
117 if (path[0] != '/')
118 throw Error(std::format("not an absolute path: `{}'", path));
119
120 string::const_iterator i = path.begin(), end = path.end();
121 string temp;
122
123 /* Count the number of times we follow a symlink and stop at some
124 arbitrary (but high) limit to prevent infinite loops. */
125 unsigned int followCount = 0, maxFollow = 1024;
126
127 while (1) {
128
129 /* Skip slashes. */
130 while (i != end && *i == '/') i++;
131 if (i == end) break;
132
133 /* Ignore `.'. */
134 if (*i == '.' && (i + 1 == end || i[1] == '/'))
135 i++;
136
137 /* If `..', delete the last component. */
138 else if (*i == '.' && i + 1 < end && i[1] == '.' &&
139 (i + 2 == end || i[2] == '/'))
140 {
141 if (!s.empty()) s.erase(s.rfind('/'));
142 i += 2;
143 }
144
145 /* Normal component; copy it. */
146 else {
147 s += '/';
148 while (i != end && *i != '/') s += *i++;
149
150 /* If s points to a symlink, resolve it and restart (since
151 the symlink target might contain new symlinks). */
152 if (resolveSymlinks && isLink(s)) {
153 if (++followCount >= maxFollow)
154 throw Error(std::format("infinite symlink recursion in path `{}'", path));
155 temp = absPath(readLink(s), dirOf(s))
156 + string(i, end);
157 i = temp.begin(); /* restart */
158 end = temp.end();
159 s = "";
160 }
161 }
162 }
163
164 return s.empty() ? "/" : s;
165}
166
167
168Path dirOf(const Path & path)
169{
170 Path::size_type pos = path.rfind('/');
171 if (pos == string::npos)
172 throw Error(std::format("invalid file name `{}'", path));
173 return pos == 0 ? "/" : Path(path, 0, pos);
174}
175
176
177string baseNameOf(const Path & path)
178{
179 Path::size_type pos = path.rfind('/');
180 if (pos == string::npos)
181 throw Error(std::format("invalid file name `{}'", path));
182 return string(path, pos + 1);
183}
184
185
186bool isInDir(const Path & path, const Path & dir)
187{
188 return path[0] == '/'
189 && string(path, 0, dir.size()) == dir
190 && path.size() >= dir.size() + 2
191 && path[dir.size()] == '/';
192}
193
194
195struct stat lstat(const Path & path)
196{
197 struct stat st;
198 if (lstat(path.c_str(), &st))
199 throw SysError(std::format("getting status of `{}'", path));
200 return st;
201}
202
203
204bool pathExists(const Path & path)
205{
206 int res;
207#ifdef HAVE_STATX
208 struct statx st;
209 res = statx(AT_FDCWD, path.c_str(), AT_SYMLINK_NOFOLLOW, 0, &st);
210#else
211 struct stat st;
212 res = lstat(path.c_str(), &st);
213#endif
214 if (!res) return true;
215 if (errno != ENOENT && errno != ENOTDIR)
216 throw SysError(std::format("getting status of {}", path));
217 return false;
218}
219
220
221Path readLink(const Path & path)
222{
223 checkInterrupt();
224 struct stat st = lstat(path);
225 if (!S_ISLNK(st.st_mode))
226 throw Error(std::format("`{}' is not a symlink", path));
227 std::vector<char> buf(st.st_size);
228 ssize_t rlsize = readlink(path.c_str(), buf.data(), st.st_size);
229 if (rlsize == -1)
230 throw SysError(std::format("reading symbolic link '{}'", path));
231 else if (rlsize > st.st_size)
232 throw Error(std::format("symbolic link `{}' size overflow {} > {}",
233 path, rlsize, st.st_size));
234 return string(buf.begin(), buf.end());
235}
236
237
238bool isLink(const Path & path)
239{
240 struct stat st = lstat(path);
241 return S_ISLNK(st.st_mode);
242}
243
244
245static DirEntries readDirectory(DIR *dir)
246{
247 DirEntries entries;
248 entries.reserve(64);
249
250 struct dirent * dirent;
251 while (errno = 0, dirent = readdir(dir)) { /* sic */
252 checkInterrupt();
253 string name = dirent->d_name;
254 if (name == "." || name == "..") continue;
255 entries.emplace_back(name, dirent->d_ino, dirent->d_type);
256 }
257 if (errno) throw SysError("reading directory");
258
259 return entries;
260}
261
262DirEntries readDirectory(const Path & path)
263{
264 AutoCloseDir dir = opendir(path.c_str());
265 if (!dir) throw SysError(std::format("opening directory `{}'", path));
266 return readDirectory(dir);
267}
268
269static DirEntries readDirectory(int fd)
270{
271 /* Since 'closedir' closes the underlying file descriptor, duplicate FD
272 beforehand. */
273 int fdcopy = dup(fd);
274 if (fdcopy < 0) throw SysError("dup");
275
276 AutoCloseDir dir = fdopendir(fdcopy);
277 if (!dir) throw SysError(std::format("opening directory from file descriptor `{}'", fd));
278 return readDirectory(dir);
279}
280
281unsigned char getFileType(const Path & path)
282{
283 struct stat st = lstat(path);
284 if (S_ISDIR(st.st_mode)) return DT_DIR;
285 if (S_ISLNK(st.st_mode)) return DT_LNK;
286 if (S_ISREG(st.st_mode)) return DT_REG;
287 return DT_UNKNOWN;
288}
289
290bool isPlainFile(const Path & path)
291{
292 struct stat st = lstat(path);
293 return S_ISREG(st.st_mode)
294 && ((st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0);
295}
296
297
298string readFile(int fd)
299{
300 struct stat st;
301 if (fstat(fd, &st) == -1)
302 throw SysError("statting file");
303
304 std::vector<unsigned char> buf(st.st_size);
305 readFull(fd, buf.data(), buf.size());
306
307 return string((char *) buf.data(), buf.size());
308}
309
310
311string readFile(const Path & path, bool drain)
312{
313 AutoCloseFD fd = open(path.c_str(), O_RDONLY);
314 if (fd == -1)
315 throw SysError(std::format("reading file `{}'", path));
316 return drain ? drainFD(fd) : readFile(fd);
317}
318
319
320void writeFile(const Path & path, const string & s)
321{
322 AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT, 0666);
323 if (fd == -1)
324 throw SysError(std::format("writing file '{}'", path));
325 writeFull(fd, s);
326}
327
328
329string readLine(int fd)
330{
331 string s;
332 while (1) {
333 checkInterrupt();
334 char ch;
335 ssize_t rd = read(fd, &ch, 1);
336 if (rd == -1) {
337 if (errno != EINTR)
338 throw SysError("reading a line");
339 } else if (rd == 0)
340 throw EndOfFile("unexpected EOF reading a line");
341 else {
342 if (ch == '\n') return s;
343 s += ch;
344 }
345 }
346}
347
348
349void writeLine(int fd, string s)
350{
351 s += '\n';
352 writeFull(fd, s);
353}
354
355
356static void _deletePathAt(int fd, const Path & path, const Path & fullPath, unsigned long long & bytesFreed, size_t linkThreshold)
357{
358 checkInterrupt();
359
360 printMsg(lvlVomit, std::format("{}", fullPath));
361
362#ifdef HAVE_STATX
363# define st_mode stx_mode
364# define st_size stx_size
365# define st_nlink stx_nlink
366#define fstatat(fd, path, stat, flags) \
367 statx(fd, path, flags, STATX_SIZE | STATX_NLINK | STATX_MODE, stat)
368#define fstat(fd, stat) \
369 statx(fd, "", AT_EMPTY_PATH, STATX_SIZE | STATX_NLINK | STATX_MODE, stat)
370 struct statx st;
371#else
372 struct stat st;
373#endif
374 if (fstatat(fd, path.c_str(), &st, AT_SYMLINK_NOFOLLOW))
375 throw SysError(std::format("getting status of `{}'", fullPath));
376
377 /* Note: if another process modifies what is at 'path' between now and
378 when we actually delete it, this may be inaccurate, but I know of no
379 way to check which file we actually deleted after the fact. */
380 if (!S_ISDIR(st.st_mode) && st.st_nlink <= linkThreshold)
381 bytesFreed += st.st_size;
382
383 if (S_ISDIR(st.st_mode)) {
384 /* Note: fds required scales with depth of directory nesting */
385 AutoCloseFD dirfd = openat(fd, path.c_str(),
386 O_RDONLY |
387 O_DIRECTORY |
388 O_NOFOLLOW |
389 O_CLOEXEC);
390 if(!dirfd.isOpen()) {
391 if (errno != EACCES)
392 throw SysError(std::format("opening `{}'", fullPath));
393 /* Target directory must not have the right permissions for us to
394 * access it. Try changing them. We only do this after the
395 * initial attempt fails because some of the ways we might try
396 * changing the permissions have race conditions, and we'd rather
397 * avoid them if we can (e.g. because we're root). */
398#ifdef O_PATH
399 {
400 AutoCloseFD pathfd = openat(fd, path.c_str(),
401 O_PATH |
402 O_DIRECTORY |
403 O_NOFOLLOW |
404 O_CLOEXEC);
405 if (!pathfd.isOpen())
406 throw SysError(std::format("opening `{}'", fullPath));
407
408 /* fchmod doesn't work with O_PATH file descriptors. fchmodat
409 * does, but only on very recent kernels (linux 6.6). Despite
410 * this, regular chmod will work with a /proc/self/fd/N
411 * filename that names an O_PATH file descriptor. */
412 string procPath = "/proc/self/fd/" + std::to_string(pathfd);
413 if (chmod(procPath.c_str(), S_IRUSR | S_IWUSR | S_IXUSR) != 0) {
414 if (errno != ENOENT)
415 throw SysError(std::format("chmod of `{}", procPath));
416 /* Fall through */
417 } else {
418 goto retry_open;
419 }
420 }
421#endif
422 /* !!! If a malicious process can have replaced the directory at
423 PATH with a hardlink to an important file, this may change
424 its permissions to become overly-strict! This should only
425 be a concern where /proc/sys/fs/protected_hardlinks is 0, or
426 on systems without protected_hardlinks. */
427 if (fchmodat(fd, path.c_str(), S_IRUSR | S_IWUSR | S_IXUSR, AT_SYMLINK_NOFOLLOW) != 0)
428 throw SysError(std::format("fchmodat of `{}'", fullPath));
429
430 retry_open:
431 dirfd = openat(fd, path.c_str(),
432 O_RDONLY |
433 O_DIRECTORY |
434 O_NOFOLLOW |
435 O_CLOEXEC);
436 if (!dirfd.isOpen())
437 throw SysError(std::format("opening `{}'", fullPath));
438 }
439
440 /* st.st_mode may currently be from a different file than what we
441 actually opened, get it straight from the file instead */
442 if(fstat(dirfd, &st))
443 throw SysError(std::format("re-getting status of `{}'", fullPath));
444
445 /* Make the directory writable. */
446 if (!(st.st_mode & S_IWUSR)) {
447 if (fchmod(dirfd, st.st_mode | S_IWUSR) == -1)
448 throw SysError(std::format("making `{}' writable", fullPath));
449 }
450
451 for (auto & i : readDirectory(dirfd))
452 _deletePathAt(dirfd, i.name, path + "/" + i.name, bytesFreed, linkThreshold);
453 }
454
455 int ret;
456 ret = unlinkat(fd, path.c_str(), S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0 );
457 if (ret == -1)
458 throw SysError(std::format("cannot unlink `{}'", fullPath));
459
460#undef st_mode
461#undef st_size
462#undef st_nlink
463#undef fstatat
464#undef fstat
465}
466
467static void _deletePath(const Path & path, unsigned long long & bytesFreed, size_t linkThreshold)
468{
469 _deletePathAt(AT_FDCWD, path, path, bytesFreed, linkThreshold);
470}
471
472
473void deletePath(const Path & path)
474{
475 unsigned long long dummy;
476 deletePath(path, dummy);
477}
478
479
480void deletePath(const Path & path, unsigned long long & bytesFreed, size_t linkThreshold)
481{
482 startNest(nest, lvlDebug,
483 std::format("recursively deleting path `{}'", path));
484 bytesFreed = 0;
485 _deletePath(path, bytesFreed, linkThreshold);
486}
487
488static void copyFile(int sourceFd, int destinationFd)
489{
490 struct stat st;
491 if (fstat(sourceFd, &st) == -1) throw SysError("statting file");
492
493 ssize_t result = copy_file_range(sourceFd, NULL, destinationFd, NULL, st.st_size, 0);
494 if (result < 0 && errno == ENOSYS) {
495 for (size_t remaining = st.st_size; remaining > 0; ) {
496 unsigned char buf[8192];
497 size_t count = std::min(remaining, sizeof buf);
498
499 readFull(sourceFd, buf, count);
500 writeFull(destinationFd, buf, count);
501 remaining -= count;
502 }
503 } else {
504 if (result < 0)
505 throw SysError(std::format("copy_file_range `{}' to `{}'", sourceFd, destinationFd));
506
507 /* If 'copy_file_range' copied less than requested, try again. */
508 for (ssize_t copied = result; copied < st.st_size; copied += result) {
509 result = copy_file_range(sourceFd, NULL, destinationFd, NULL,
510 st.st_size - copied, 0);
511 if (result < 0)
512 throw SysError(std::format("copy_file_range `{}' to `{}'", sourceFd, destinationFd));
513 }
514 }
515}
516
517static void copyFileRecursively(int sourceroot, const Path &source,
518 int destinationroot, const Path &destination,
519 bool deleteSource)
520{
521 struct stat st;
522 if (fstatat(sourceroot, source.c_str(), &st, AT_SYMLINK_NOFOLLOW) == -1)
523 throw SysError(std::format("statting file `{}'", source));
524
525 if (S_ISREG(st.st_mode)) {
526 AutoCloseFD sourceFd = openat(sourceroot, source.c_str(),
527 O_CLOEXEC | O_NOFOLLOW | O_RDONLY);
528 if (sourceFd == -1) throw SysError(std::format("opening `{}'", source));
529
530 AutoCloseFD destinationFd = openat(destinationroot, destination.c_str(),
531 O_CLOEXEC | O_CREAT | O_WRONLY | O_TRUNC
532 | O_NOFOLLOW | O_EXCL,
533 st.st_mode);
534 if (destinationFd == -1) throw SysError(std::format("opening `{}'", source));
535
536 copyFile(sourceFd, destinationFd);
537 fchown(destinationFd, st.st_uid, st.st_gid);
538 } else if (S_ISLNK(st.st_mode)) {
539 std::vector<char> target(st.st_size + 1);
540 ssize_t result = readlinkat(sourceroot, source.c_str(), target.data(), st.st_size);
541 if (result != st.st_size) throw SysError("reading symlink target");
542 target[st.st_size] = '\0';
543 int err = symlinkat(target.data(), destinationroot, destination.c_str());
544 if (err != 0)
545 throw SysError(std::format("creating symlink `{}'", destination));
546 fchownat(destinationroot, destination.c_str(),
547 st.st_uid, st.st_gid, AT_SYMLINK_NOFOLLOW);
548 } else if (S_ISDIR(st.st_mode)) {
549 int err = mkdirat(destinationroot, destination.c_str(), 0755);
550 if (err != 0)
551 throw SysError(std::format("creating directory `{}'", destination));
552
553 AutoCloseFD destinationFd = openat(destinationroot, destination.c_str(),
554 O_CLOEXEC | O_RDONLY | O_DIRECTORY
555 | O_NOFOLLOW);
556 if (err != 0)
557 throw SysError(std::format("opening directory `{}'", destination));
558
559 AutoCloseFD sourceFd = openat(sourceroot, source.c_str(),
560 O_CLOEXEC | O_NOFOLLOW | O_RDONLY);
561 if (sourceFd == -1)
562 throw SysError(std::format("opening `{}'", source));
563
564 if (deleteSource && !(st.st_mode & S_IWUSR)) {
565 /* Ensure the directory is writable so files within it can be
566 deleted. */
567 if (fchmod(sourceFd, st.st_mode | S_IWUSR) == -1)
568 throw SysError(std::format("making `{}' directory writable", source));
569 }
570
571 for (auto & i : readDirectory(sourceFd))
572 copyFileRecursively((int)sourceFd, i.name, (int)destinationFd, i.name,
573 deleteSource);
574 fchown(destinationFd, st.st_uid, st.st_gid);
575 } else throw Error(std::format("refusing to copy irregular file `{}'", source));
576
577 if (deleteSource)
578 unlinkat(sourceroot, source.c_str(),
579 S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0);
580}
581
582void copyFileRecursively(const Path &source, const Path &destination, bool deleteSource)
583{
584 copyFileRecursively(AT_FDCWD, source, AT_FDCWD, destination, deleteSource);
585}
586
587static Path tempName(Path tmpRoot, const Path & prefix, bool includePid,
588 int & counter)
589{
590 tmpRoot = canonPath(tmpRoot.empty() ? getEnv("TMPDIR", "/tmp") : tmpRoot, true);
591 if (includePid)
592 return std::format("{}/{}-{}-{}", tmpRoot, prefix, getpid(), counter++);
593 else
594 return std::format("{}/{}-{}", tmpRoot, prefix, counter++);
595}
596
597
598Path createTempDir(const Path & tmpRoot, const Path & prefix,
599 bool includePid, bool useGlobalCounter, mode_t mode)
600{
601 static int globalCounter = 0;
602 int localCounter = 0;
603 int & counter(useGlobalCounter ? globalCounter : localCounter);
604
605 while (1) {
606 checkInterrupt();
607 Path tmpDir = tempName(tmpRoot, prefix, includePid, counter);
608 if (mkdir(tmpDir.c_str(), mode) == 0) {
609 /* Explicitly set the group of the directory. This is to
610 work around around problems caused by BSD's group
611 ownership semantics (directories inherit the group of
612 the parent). For instance, the group of /tmp on
613 FreeBSD is "wheel", so all directories created in /tmp
614 will be owned by "wheel"; but if the user is not in
615 "wheel", then "tar" will fail to unpack archives that
616 have the setgid bit set on directories. */
617 if (chown(tmpDir.c_str(), (uid_t) -1, getegid()) != 0)
618 throw SysError(std::format("setting group of directory `{}'", tmpDir));
619 return tmpDir;
620 }
621 if (errno != EEXIST)
622 throw SysError(std::format("creating directory `{}'", tmpDir));
623 }
624}
625
626
627Paths createDirs(const Path & path)
628{
629 Paths created;
630 if (path == "/") return created;
631
632 struct stat st;
633 if (lstat(path.c_str(), &st) == -1) {
634 created = createDirs(dirOf(path));
635 if (mkdir(path.c_str(), 0777) == -1 && errno != EEXIST)
636 throw SysError(std::format("creating directory `{}'", path));
637 st = lstat(path);
638 created.push_back(path);
639 }
640
641 if (S_ISLNK(st.st_mode) && stat(path.c_str(), &st) == -1)
642 throw SysError(std::format("statting symlink `{}'", path));
643
644 if (!S_ISDIR(st.st_mode)) throw Error(std::format("`{}' is not a directory", path));
645
646 return created;
647}
648
649
650void createSymlink(const Path & target, const Path & link)
651{
652 if (symlink(target.c_str(), link.c_str()))
653 throw SysError(std::format("creating symlink from `{}' to `{}'", link, target));
654}
655
656
657static int nestingLevel = 0;
658
659
660Nest::Nest()
661{
662 nest = false;
663}
664
665
666Nest::~Nest()
667{
668 close();
669}
670
671
672static string escVerbosity(Verbosity level)
673{
674 return std::to_string((int) level);
675}
676
677
678void Nest::open(Verbosity level, std::string_view fs)
679{
680 if (level <= verbosity) {
681 if (logType == ltEscapes)
682 std::cerr << "\033[" << escVerbosity(level) << "p"
683 << fs << "\n";
684 else
685 printMsg_(level, fs);
686 nest = true;
687 nestingLevel++;
688 }
689}
690
691
692void Nest::close()
693{
694 if (nest) {
695 nestingLevel--;
696 if (logType == ltEscapes)
697 std::cerr << "\033[q";
698 nest = false;
699 }
700}
701
702
703void printMsg_(Verbosity level, std::string_view fs)
704{
705 checkInterrupt();
706 if (level > verbosity) return;
707 string prefix;
708 if (logType == ltPretty)
709 for (int i = 0; i < nestingLevel; i++)
710 prefix += "| ";
711 else if (logType == ltEscapes && level != lvlInfo)
712 prefix = "\033[" + escVerbosity(level) + "s";
713 string s = std::format("{}{}\n", prefix, fs);
714 writeToStderr(s);
715}
716
717
718void warnOnce(bool & haveWarned, std::string_view fs)
719{
720 if (!haveWarned) {
721 printMsg(lvlError, std::format("warning: {}", fs));
722 haveWarned = true;
723 }
724}
725
726
727void writeToStderr(const string & s)
728{
729 try {
730 if (_writeToStderr)
731 _writeToStderr((const unsigned char *) s.data(), s.size());
732 else
733 writeFull(STDERR_FILENO, s);
734 } catch (SysError & e) {
735 /* Ignore failing writes to stderr if we're in an exception
736 handler, otherwise throw an exception. We need to ignore
737 write errors in exception handlers to ensure that cleanup
738 code runs to completion if the other side of stderr has
739 been closed unexpectedly. */
740 if (std::uncaught_exceptions() == 0) throw;
741 }
742}
743
744
745void readFull(int fd, unsigned char * buf, size_t count)
746{
747 while (count) {
748 checkInterrupt();
749 ssize_t res = read(fd, (char *) buf, count);
750 if (res == -1) {
751 if (errno == EINTR) continue;
752 throw SysError("reading from file");
753 }
754 if (res == 0) throw EndOfFile("unexpected end-of-file");
755 count -= res;
756 buf += res;
757 }
758}
759
760
761void writeFull(int fd, const unsigned char * buf, size_t count)
762{
763 while (count) {
764 checkInterrupt();
765 ssize_t res = write(fd, (char *) buf, count);
766 if (res == -1) {
767 if (errno == EINTR) continue;
768 throw SysError("writing to file");
769 }
770 count -= res;
771 buf += res;
772 }
773}
774
775
776void writeFull(int fd, const string & s)
777{
778 writeFull(fd, (const unsigned char *) s.data(), s.size());
779}
780
781
782string drainFD(int fd)
783{
784 string result;
785 unsigned char buffer[4096];
786 while (1) {
787 checkInterrupt();
788 ssize_t rd = read(fd, buffer, sizeof buffer);
789 if (rd == -1) {
790 if (errno != EINTR)
791 throw SysError("reading from file");
792 }
793 else if (rd == 0) break;
794 else result.append((char *) buffer, rd);
795 }
796 return result;
797}
798
799
800/* Wait on FD until MESSAGE has been read. */
801void waitForMessage(int fd, const string & message)
802{
803 string str(message.length(), '\0');
804 readFull(fd, (unsigned char*)str.data(), message.length());
805 if (str != message)
806 throw Error(std::format("did not receive message '{}' on file descriptor {}",
807 message, fd));
808}
809
810
811
812//////////////////////////////////////////////////////////////////////
813
814
815AutoDelete::AutoDelete(const string & p, bool recursive) : path(p)
816{
817 del = true;
818 this->recursive = recursive;
819}
820
821AutoDelete::~AutoDelete()
822{
823 try {
824 if (del) {
825 if (recursive)
826 deletePath(path);
827 else {
828 if (remove(path.c_str()) == -1)
829 throw SysError(std::format("cannot unlink `{}'", path));
830 }
831 }
832 } catch (...) {
833 ignoreException();
834 }
835}
836
837void AutoDelete::cancel()
838{
839 del = false;
840}
841
842
843
844//////////////////////////////////////////////////////////////////////
845
846
847AutoCloseFD::AutoCloseFD()
848{
849 fd = -1;
850}
851
852
853AutoCloseFD::AutoCloseFD(int fd)
854{
855 this->fd = fd;
856}
857
858
859AutoCloseFD::AutoCloseFD(const AutoCloseFD & fd)
860{
861 /* Copying an AutoCloseFD isn't allowed (who should get to close
862 it?). But as an edge case, allow copying of closed
863 AutoCloseFDs. This is necessary due to tiresome reasons
864 involving copy constructor use on default object values in STL
865 containers (like when you do `map[value]' where value isn't in
866 the map yet). */
867 this->fd = fd.fd;
868 if (this->fd != -1) abort();
869}
870
871
872AutoCloseFD::~AutoCloseFD()
873{
874 try {
875 close();
876 } catch (...) {
877 ignoreException();
878 }
879}
880
881
882void AutoCloseFD::operator =(int fd)
883{
884 if (this->fd != fd) close();
885 this->fd = fd;
886}
887
888
889AutoCloseFD::operator int() const
890{
891 return fd;
892}
893
894
895void AutoCloseFD::close()
896{
897 if (fd != -1) {
898 if (::close(fd) == -1)
899 /* This should never happen. */
900 throw SysError(std::format("closing file descriptor {}", fd));
901 fd = -1;
902 }
903}
904
905
906bool AutoCloseFD::isOpen()
907{
908 return fd != -1;
909}
910
911
912/* Pass responsibility for closing this fd to the caller. */
913int AutoCloseFD::borrow()
914{
915 int oldFD = fd;
916 fd = -1;
917 return oldFD;
918}
919
920
921void Pipe::create()
922{
923 int fds[2];
924 if (pipe(fds) != 0) throw SysError("creating pipe");
925 readSide = fds[0];
926 writeSide = fds[1];
927 closeOnExec(readSide);
928 closeOnExec(writeSide);
929}
930
931
932void sendFD(int sock, int fd)
933{
934 ssize_t rc;
935 struct msghdr msg;
936 struct cmsghdr *cmsg;
937 char cmsgbuf[CMSG_SPACE(sizeof(fd))];
938 struct iovec iov;
939 char dummy = '\0';
940 memset(&msg, 0, sizeof(msg));
941 iov.iov_base = &dummy;
942 iov.iov_len = 1;
943 msg.msg_iov = &iov;
944 msg.msg_iovlen = 1;
945 msg.msg_control = cmsgbuf;
946 msg.msg_controllen = sizeof(cmsgbuf);
947 cmsg = CMSG_FIRSTHDR(&msg);
948 cmsg->cmsg_level = SOL_SOCKET;
949 cmsg->cmsg_type = SCM_RIGHTS;
950 cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
951 memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));
952 msg.msg_controllen = cmsg->cmsg_len;
953 do
954 {
955 rc = sendmsg(sock, &msg, 0);
956 } while(rc < 0 && errno == EINTR);
957 if(rc < 0)
958 throw SysError("sending fd");
959}
960
961
962int receiveFD(int sock)
963{
964 int fd;
965 ssize_t rc;
966 struct msghdr msg;
967 struct cmsghdr *cmsg;
968 char cmsgbuf[CMSG_SPACE(sizeof(fd))];
969 struct iovec iov;
970 char dummy = '\0';
971 memset(&msg, 0, sizeof(msg));
972 iov.iov_base = &dummy;
973 iov.iov_len = 1;
974 msg.msg_iov = &iov;
975 msg.msg_iovlen = 1;
976 msg.msg_control = cmsgbuf;
977 msg.msg_controllen = sizeof(cmsgbuf);
978 do
979 {
980 rc = recvmsg(sock, &msg, 0);
981 } while(rc < 0 && errno == EINTR);
982 if (rc < 0)
983 throw SysError("receiving fd");
984 if (rc == 0)
985 throw Error("received EOF (empty message) while receiving fd");
986
987 cmsg = CMSG_FIRSTHDR(&msg);
988 if (cmsg == NULL || cmsg->cmsg_type != SCM_RIGHTS)
989 throw Error("received message without an fd");
990 memcpy(&fd, CMSG_DATA(cmsg), sizeof(fd));
991 return fd;
992}
993
994//////////////////////////////////////////////////////////////////////
995
996
997AutoCloseDir::AutoCloseDir()
998{
999 dir = 0;
1000}
1001
1002
1003AutoCloseDir::AutoCloseDir(DIR * dir)
1004{
1005 this->dir = dir;
1006}
1007
1008
1009AutoCloseDir::~AutoCloseDir()
1010{
1011 close();
1012}
1013
1014
1015void AutoCloseDir::operator =(DIR * dir)
1016{
1017 this->dir = dir;
1018}
1019
1020
1021AutoCloseDir::operator DIR *()
1022{
1023 return dir;
1024}
1025
1026
1027void AutoCloseDir::close()
1028{
1029 if (dir) {
1030 closedir(dir);
1031 dir = 0;
1032 }
1033}
1034
1035
1036//////////////////////////////////////////////////////////////////////
1037
1038
1039Pid::Pid()
1040 : pid(-1), separatePG(false), killSignal(SIGKILL)
1041{
1042}
1043
1044
1045Pid::Pid(pid_t pid)
1046 : pid(pid), separatePG(false), killSignal(SIGKILL)
1047{
1048}
1049
1050
1051Pid::~Pid()
1052{
1053 kill();
1054}
1055
1056
1057void Pid::operator =(pid_t pid)
1058{
1059 if (this->pid != pid) kill();
1060 this->pid = pid;
1061 killSignal = SIGKILL; // reset signal to default
1062}
1063
1064
1065Pid::operator pid_t()
1066{
1067 return pid;
1068}
1069
1070
1071void Pid::kill(bool quiet)
1072{
1073 if (pid == -1 || pid == 0) return;
1074
1075 if (!quiet)
1076 printMsg(lvlError, std::format("killing process {}", pid));
1077
1078 /* Send the requested signal to the child. If it has its own
1079 process group, send the signal to every process in the child
1080 process group (which hopefully includes *all* its children). */
1081 if (::kill(separatePG ? -pid : pid, killSignal) != 0)
1082 printMsg(lvlError, (SysError(std::format("killing process {}", pid)).msg()));
1083
1084 /* Wait until the child dies, disregarding the exit status. */
1085 int status;
1086 while (waitpid(pid, &status, 0) == -1) {
1087 checkInterrupt();
1088 if (errno != EINTR) {
1089 printMsg(lvlError,
1090 (SysError(std::format("waiting for process {}", pid)).msg()));
1091 break;
1092 }
1093 }
1094
1095 pid = -1;
1096}
1097
1098
1099int Pid::wait(bool block)
1100{
1101 assert(pid != -1);
1102 while (1) {
1103 int status;
1104 int res = waitpid(pid, &status, block ? 0 : WNOHANG);
1105 if (res == pid) {
1106 pid = -1;
1107 return status;
1108 }
1109 if (res == 0 && !block) return -1;
1110 if (errno != EINTR)
1111 throw SysError("cannot get child exit status");
1112 checkInterrupt();
1113 }
1114}
1115
1116
1117void Pid::setSeparatePG(bool separatePG)
1118{
1119 this->separatePG = separatePG;
1120}
1121
1122
1123void Pid::setKillSignal(int signal)
1124{
1125 this->killSignal = signal;
1126}
1127
1128
1129void killUser(uid_t uid)
1130{
1131 debug(std::format("killing all processes running under uid `{}'", uid));
1132
1133 assert(uid != 0); /* just to be safe... */
1134
1135 /* The system call kill(-1, sig) sends the signal `sig' to all
1136 users to which the current process can send signals. So we
1137 fork a process, switch to uid, and send a mass kill. */
1138
1139 Pid pid = startProcess([&]() {
1140
1141 if (setuid(uid) == -1)
1142 throw SysError("setting uid");
1143
1144 while (true) {
1145#ifdef __APPLE__
1146 /* OSX's kill syscall takes a third parameter that, among
1147 other things, determines if kill(-1, signo) affects the
1148 calling process. In the OSX libc, it's set to true,
1149 which means "follow POSIX", which we don't want here
1150 */
1151 if (syscall(SYS_kill, -1, SIGKILL, false) == 0) break;
1152#elif __GNU__
1153 /* Killing all a user's processes using PID=-1 does currently
1154 not work on the Hurd. */
1155 if (kill(getpid(), SIGKILL) == 0) break;
1156#else
1157 if (kill(-1, SIGKILL) == 0) break;
1158#endif
1159 if (errno == ESRCH) break; /* no more processes */
1160 if (errno != EINTR)
1161 throw SysError(std::format("cannot kill processes for uid `{}'", uid));
1162 }
1163
1164 _exit(0);
1165 });
1166
1167 int status = pid.wait(true);
1168#if __GNU__
1169 /* When the child killed itself, status = SIGKILL. */
1170 if (status == SIGKILL) return;
1171#endif
1172 if (status != 0)
1173 throw Error(std::format("cannot kill processes for uid `{}': {}", uid, statusToString(status)));
1174
1175 /* !!! We should really do some check to make sure that there are
1176 no processes left running under `uid', but there is no portable
1177 way to do so (I think). The most reliable way may be `ps -eo
1178 uid | grep -q $uid'. */
1179}
1180
1181
1182//////////////////////////////////////////////////////////////////////
1183
1184
1185pid_t startProcess(std::function<void()> fun,
1186 bool dieWithParent, const string & errorPrefix, bool runExitHandlers)
1187{
1188 pid_t pid = fork();
1189 if (pid == -1) throw SysError("unable to fork");
1190
1191 if (pid == 0) {
1192 _writeToStderr = 0;
1193 try {
1194#if __linux__
1195 if (dieWithParent && prctl(PR_SET_PDEATHSIG, SIGKILL) == -1)
1196 throw SysError("setting death signal");
1197#endif
1198 restoreAffinity();
1199 fun();
1200 } catch (std::exception & e) {
1201 try {
1202 std::cerr << errorPrefix << e.what() << "\n";
1203 } catch (...) { }
1204 } catch (...) { }
1205 if (runExitHandlers)
1206 exit(1);
1207 else
1208 _exit(1);
1209 }
1210
1211 return pid;
1212}
1213
1214
1215std::vector<char *> stringsToCharPtrs(const Strings & ss)
1216{
1217 std::vector<char *> res;
1218 for (auto & s : ss) res.push_back((char *) s.c_str());
1219 res.push_back(0);
1220 return res;
1221}
1222
1223
1224string runProgram(Path program, bool searchPath, const Strings & args)
1225{
1226 checkInterrupt();
1227
1228 /* Create a pipe. */
1229 Pipe pipe;
1230 pipe.create();
1231
1232 /* Fork. */
1233 Pid pid = startProcess([&]() {
1234 if (dup2(pipe.writeSide, STDOUT_FILENO) == -1)
1235 throw SysError("dupping stdout");
1236
1237 Strings args_(args);
1238 args_.push_front(program);
1239
1240 if (searchPath)
1241 execvp(program.c_str(), stringsToCharPtrs(args_).data());
1242 else
1243 execv(program.c_str(), stringsToCharPtrs(args_).data());
1244
1245 int err = errno;
1246 printMsg(lvlError, std::format("executing `{}': {}", program, strerror(err)));
1247 _exit(127);
1248 });
1249
1250 pipe.writeSide.close();
1251
1252 string result = drainFD(pipe.readSide);
1253
1254 /* Wait for the child to finish. */
1255 int status = pid.wait(true);
1256 if (!statusOk(status))
1257 throw ExecError(std::format("program `{}' {}",
1258 program, statusToString(status)));
1259
1260 return result;
1261}
1262
1263
1264void closeMostFDs(const set<int> & exceptions)
1265{
1266#ifdef HAVE_CLOSE_RANGE
1267 if (exceptions.empty())
1268 close_range(3, ~0U, 0);
1269 else
1270#endif
1271 {
1272 int maxFD = 0;
1273 maxFD = sysconf(_SC_OPEN_MAX);
1274 for (int fd = 0; fd < maxFD; ++fd)
1275 if (fd != STDIN_FILENO && fd != STDOUT_FILENO && fd != STDERR_FILENO
1276 && exceptions.find(fd) == exceptions.end())
1277 close(fd); /* ignore result */
1278 }
1279}
1280
1281
1282void closeOnExec(int fd)
1283{
1284 int prev;
1285 if ((prev = fcntl(fd, F_GETFD, 0)) == -1 ||
1286 fcntl(fd, F_SETFD, prev | FD_CLOEXEC) == -1)
1287 throw SysError("setting close-on-exec flag");
1288}
1289
1290void keepOnExec(int fd)
1291{
1292 int prev;
1293 if ((prev = fcntl(fd, F_GETFD, 0)) == -1 ||
1294 fcntl(fd, F_SETFD, prev & ~FD_CLOEXEC) == -1)
1295 throw SysError("clearing close-on-exec flag");
1296}
1297
1298//////////////////////////////////////////////////////////////////////
1299
1300
1301
1302void _interrupted()
1303{
1304 /* Block user interrupts while an exception is being handled.
1305 Throwing an exception while another exception is being handled
1306 kills the program! */
1307 if (std::uncaught_exceptions() == 0) {
1308 _isInterrupted = 0;
1309 throw Interrupted("interrupted by the user");
1310 }
1311}
1312
1313
1314
1315//////////////////////////////////////////////////////////////////////
1316
1317
1318template<class C> C tokenizeString(const string & s, const string & separators)
1319{
1320 C result;
1321 string::size_type pos = s.find_first_not_of(separators, 0);
1322 while (pos != string::npos) {
1323 string::size_type end = s.find_first_of(separators, pos + 1);
1324 if (end == string::npos) end = s.size();
1325 string token(s, pos, end - pos);
1326 result.insert(result.end(), token);
1327 pos = s.find_first_not_of(separators, end);
1328 }
1329 return result;
1330}
1331
1332template Strings tokenizeString(const string & s, const string & separators);
1333template StringSet tokenizeString(const string & s, const string & separators);
1334template vector<string> tokenizeString(const string & s, const string & separators);
1335
1336
1337string concatStringsSep(const string & sep, const Strings & ss)
1338{
1339 string s;
1340 for (const auto& i : ss) {
1341 if (s.size() != 0) s += sep;
1342 s += i;
1343 }
1344 return s;
1345}
1346
1347
1348string concatStringsSep(const string & sep, const StringSet & ss)
1349{
1350 string s;
1351 for (const auto& i : ss) {
1352 if (s.size() != 0) s += sep;
1353 s += i;
1354 }
1355 return s;
1356}
1357
1358
1359string chomp(const string & s)
1360{
1361 size_t i = s.find_last_not_of(" \n\r\t");
1362 return i == string::npos ? "" : string(s, 0, i + 1);
1363}
1364
1365
1366string statusToString(int status)
1367{
1368 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
1369 if (WIFEXITED(status))
1370 return std::format("failed with exit code {}", WEXITSTATUS(status));
1371 else if (WIFSIGNALED(status)) {
1372 int sig = WTERMSIG(status);
1373#if HAVE_STRSIGNAL
1374 const char * description = strsignal(sig);
1375 return std::format("failed due to signal {} ({})", sig, description);
1376#else
1377 return std::format("failed due to signal {}", sig);
1378#endif
1379 }
1380 else
1381 return "died abnormally";
1382 } else return "succeeded";
1383}
1384
1385
1386bool statusOk(int status)
1387{
1388 return WIFEXITED(status) && WEXITSTATUS(status) == 0;
1389}
1390
1391
1392void expect(std::istream & str, std::string_view s)
1393{
1394 std::vector<char> s2(s.size());
1395 str.read(s2.data(), s2.size());
1396 if (string(s2.begin(), s2.end()) != s)
1397 throw FormatError(std::format("expected string `{}'", s));
1398}
1399
1400
1401string parseString(std::istream & str)
1402{
1403 string res;
1404 expect(str, "\"");
1405 int c;
1406 while ((c = str.get()) != '"')
1407 if (c == '\\') {
1408 c = str.get();
1409 if (c == 'n') res += '\n';
1410 else if (c == 'r') res += '\r';
1411 else if (c == 't') res += '\t';
1412 else res += c;
1413 }
1414 else res += c;
1415 return res;
1416}
1417
1418
1419bool endOfList(std::istream & str)
1420{
1421 if (str.peek() == ',') {
1422 str.get();
1423 return false;
1424 }
1425 if (str.peek() == ']') {
1426 str.get();
1427 return true;
1428 }
1429 return false;
1430}
1431
1432string decodeOctalEscaped(const string & s)
1433{
1434 string r;
1435 for (string::const_iterator i = s.begin(); i != s.end(); ) {
1436 if (*i != '\\') { r += *(i++); continue; }
1437 unsigned char c = 0;
1438 ++i;
1439 for(int j = 0; j < 3; j++) {
1440 if(i == s.end() || *i < '0' || *i >= '8')
1441 throw Error("malformed octal escape");
1442 c = c * 8 + (*i - '0');
1443 ++i;
1444 }
1445 r += c;
1446 }
1447 return r;
1448}
1449
1450
1451void ignoreException()
1452{
1453 try {
1454 throw;
1455 } catch (std::exception & e) {
1456 printMsg(lvlError, std::format("error (ignored): {}", e.what()));
1457 }
1458}
1459
1460static const string pathNullDevice = "/dev/null";
1461
1462/* Common initialisation performed in child processes. */
1463void commonChildInit(Pipe & logPipe)
1464{
1465 /* Put the child in a separate session (and thus a separate
1466 process group) so that it has no controlling terminal (meaning
1467 that e.g. ssh cannot open /dev/tty) and it doesn't receive
1468 terminal signals. */
1469 if (setsid() == -1)
1470 throw SysError("creating a new session");
1471
1472 /* Close the read end so only the parent holds a reference to it. */
1473 logPipe.readSide.close();
1474
1475 /* Dup the write side of the logger pipe into stderr. */
1476 if (dup2(logPipe.writeSide, STDERR_FILENO) == -1)
1477 throw SysError("cannot pipe standard error into log file");
1478
1479 /* Dup stderr to stdout. */
1480 if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1)
1481 throw SysError("cannot dup stderr into stdout");
1482
1483 /* Reroute stdin to /dev/null. */
1484 int fdDevNull = open(pathNullDevice.c_str(), O_RDWR);
1485 if (fdDevNull == -1)
1486 throw SysError(std::format("cannot open `{}'", pathNullDevice));
1487 if (dup2(fdDevNull, STDIN_FILENO) == -1)
1488 throw SysError("cannot dup null device into stdin");
1489 close(fdDevNull);
1490}
1491
1492//////////////////////////////////////////////////////////////////////
1493
1494Agent::Agent(const string &command, const Strings &args, const std::map<string, string> &env)
1495{
1496 debug(std::format("starting agent '{}'", command));
1497
1498 /* Create a pipe to get the output of the child. */
1499 fromAgent.create();
1500
1501 /* Create the communication pipes. */
1502 toAgent.create();
1503
1504 /* Create a pipe to get the output of the builder. */
1505 builderOut.create();
1506
1507 /* Fork the hook. */
1508 pid = startProcess([&]() {
1509
1510 commonChildInit(fromAgent);
1511
1512 for (auto pair: env) {
1513 setenv(pair.first.c_str(), pair.second.c_str(), 1);
1514 }
1515
1516 if (chdir("/") == -1) throw SysError("changing into `/");
1517
1518 /* Dup the communication pipes. */
1519 if (dup2(toAgent.readSide, STDIN_FILENO) == -1)
1520 throw SysError("dupping to-hook read side");
1521
1522 /* Use fd 4 for the builder's stdout/stderr. */
1523 if (dup2(builderOut.writeSide, 4) == -1)
1524 throw SysError("dupping builder's stdout/stderr");
1525
1526 Strings allArgs;
1527 allArgs.push_back(command);
1528 allArgs.insert(allArgs.end(), args.begin(), args.end()); // append
1529
1530 execv(command.c_str(), stringsToCharPtrs(allArgs).data());
1531
1532 throw SysError(std::format("executing `{}'", command));
1533 });
1534
1535 pid.setSeparatePG(true);
1536 fromAgent.writeSide.close();
1537 toAgent.readSide.close();
1538}
1539
1540
1541Agent::~Agent()
1542{
1543 try {
1544 toAgent.writeSide.close();
1545 pid.kill(true);
1546 } catch (...) {
1547 ignoreException();
1548 }
1549}
1550
1551
1552}