| 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 |
|
| 33 | extern char * * environ;
|
| 34 |
|
| 35 |
|
| 36 | namespace nix {
|
| 37 |
|
| 38 |
|
| 39 | BaseError::BaseError(std::string_view fs, unsigned int status)
|
| 40 | : status(status)
|
| 41 | {
|
| 42 | err = fs;
|
| 43 | }
|
| 44 |
|
| 45 |
|
| 46 | BaseError & BaseError::addPrefix(std::string fs)
|
| 47 | {
|
| 48 | prefix_ = fs + prefix_;
|
| 49 | return *this;
|
| 50 | }
|
| 51 |
|
| 52 |
|
| 53 | SysError::SysError(std::string_view fs)
|
| 54 | : Error(std::format("{}: {}", fs, strerror(errno)))
|
| 55 | , errNo(errno)
|
| 56 | {
|
| 57 | }
|
| 58 |
|
| 59 |
|
| 60 | string getEnv(const string & key, const string & def)
|
| 61 | {
|
| 62 | char * value = getenv(key.c_str());
|
| 63 | return value ? string(value) : def;
|
| 64 | }
|
| 65 |
|
| 66 |
|
| 67 | string 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 |
|
| 88 | Path absPath(Path path, Path dir)
|
| 89 | {
|
| 90 | if (path[0] != '/') {
|
| 91 | if (dir == "") {
|
| 92 | #ifdef __GNU__
|
| 93 | |
| 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 |
|
| 113 | Path 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 | |
| 124 | arbitrary (but high) limit to prevent infinite loops.
|
| 125 | unsigned int followCount = 0, maxFollow = 1024;
|
| 126 |
|
| 127 | while (1) {
|
| 128 |
|
| 129 |
|
| 130 | while (i != end && *i == '/') i++;
|
| 131 | if (i == end) break;
|
| 132 |
|
| 133 |
|
| 134 | if (*i == '.' && (i + 1 == end || i[1] == '/'))
|
| 135 | i++;
|
| 136 |
|
| 137 |
|
| 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 |
|
| 146 | else {
|
| 147 | s += '/';
|
| 148 | while (i != end && *i != '/') s += *i++;
|
| 149 |
|
| 150 | |
| 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();
|
| 158 | end = temp.end();
|
| 159 | s = "";
|
| 160 | }
|
| 161 | }
|
| 162 | }
|
| 163 |
|
| 164 | return s.empty() ? "/" : s;
|
| 165 | }
|
| 166 |
|
| 167 |
|
| 168 | Path 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 |
|
| 177 | string 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 |
|
| 186 | bool 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 |
|
| 195 | struct 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 |
|
| 204 | bool 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 |
|
| 221 | Path 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 |
|
| 238 | bool isLink(const Path & path)
|
| 239 | {
|
| 240 | struct stat st = lstat(path);
|
| 241 | return S_ISLNK(st.st_mode);
|
| 242 | }
|
| 243 |
|
| 244 |
|
| 245 | static DirEntries readDirectory(DIR *dir)
|
| 246 | {
|
| 247 | DirEntries entries;
|
| 248 | entries.reserve(64);
|
| 249 |
|
| 250 | struct dirent * dirent;
|
| 251 | while (errno = 0, dirent = readdir(dir)) {
|
| 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 |
|
| 262 | DirEntries 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 |
|
| 269 | static DirEntries readDirectory(int fd)
|
| 270 | {
|
| 271 | |
| 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 |
|
| 281 | unsigned 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 |
|
| 290 | bool 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 |
|
| 298 | string 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 |
|
| 311 | string 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 |
|
| 320 | void 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 |
|
| 329 | string 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 |
|
| 349 | void writeLine(int fd, string s)
|
| 350 | {
|
| 351 | s += '\n';
|
| 352 | writeFull(fd, s);
|
| 353 | }
|
| 354 |
|
| 355 |
|
| 356 | static 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 | |
| 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 |
|
| 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 | |
| 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 | |
| 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 |
|
| 417 | } else {
|
| 418 | goto retry_open;
|
| 419 | }
|
| 420 | }
|
| 421 | #endif
|
| 422 | |
| 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 | |
| 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 |
|
| 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 |
|
| 467 | static void _deletePath(const Path & path, unsigned long long & bytesFreed, size_t linkThreshold)
|
| 468 | {
|
| 469 | _deletePathAt(AT_FDCWD, path, path, bytesFreed, linkThreshold);
|
| 470 | }
|
| 471 |
|
| 472 |
|
| 473 | void deletePath(const Path & path)
|
| 474 | {
|
| 475 | unsigned long long dummy;
|
| 476 | deletePath(path, dummy);
|
| 477 | }
|
| 478 |
|
| 479 |
|
| 480 | void 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 |
|
| 488 | static 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 |
|
| 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 |
|
| 517 | static 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 | |
| 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 |
|
| 582 | void copyFileRecursively(const Path &source, const Path &destination, bool deleteSource)
|
| 583 | {
|
| 584 | copyFileRecursively(AT_FDCWD, source, AT_FDCWD, destination, deleteSource);
|
| 585 | }
|
| 586 |
|
| 587 | static 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 |
|
| 598 | Path 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 | |
| 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 |
|
| 627 | Paths 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 |
|
| 650 | void 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 |
|
| 657 | static int nestingLevel = 0;
|
| 658 |
|
| 659 |
|
| 660 | Nest::Nest()
|
| 661 | {
|
| 662 | nest = false;
|
| 663 | }
|
| 664 |
|
| 665 |
|
| 666 | Nest::~Nest()
|
| 667 | {
|
| 668 | close();
|
| 669 | }
|
| 670 |
|
| 671 |
|
| 672 | static string escVerbosity(Verbosity level)
|
| 673 | {
|
| 674 | return std::to_string((int) level);
|
| 675 | }
|
| 676 |
|
| 677 |
|
| 678 | void 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 |
|
| 692 | void Nest::close()
|
| 693 | {
|
| 694 | if (nest) {
|
| 695 | nestingLevel--;
|
| 696 | if (logType == ltEscapes)
|
| 697 | std::cerr << "\033[q";
|
| 698 | nest = false;
|
| 699 | }
|
| 700 | }
|
| 701 |
|
| 702 |
|
| 703 | void 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 |
|
| 718 | void 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 |
|
| 727 | void 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 | |
| 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 |
|
| 745 | void 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 |
|
| 761 | void 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 |
|
| 776 | void writeFull(int fd, const string & s)
|
| 777 | {
|
| 778 | writeFull(fd, (const unsigned char *) s.data(), s.size());
|
| 779 | }
|
| 780 |
|
| 781 |
|
| 782 | string 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 |
|
| 801 | void 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 |
|
| 815 | AutoDelete::AutoDelete(const string & p, bool recursive) : path(p)
|
| 816 | {
|
| 817 | del = true;
|
| 818 | this->recursive = recursive;
|
| 819 | }
|
| 820 |
|
| 821 | AutoDelete::~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 |
|
| 837 | void AutoDelete::cancel()
|
| 838 | {
|
| 839 | del = false;
|
| 840 | }
|
| 841 |
|
| 842 |
|
| 843 |
|
| 844 | |
| 845 |
|
| 846 |
|
| 847 | AutoCloseFD::AutoCloseFD()
|
| 848 | {
|
| 849 | fd = -1;
|
| 850 | }
|
| 851 |
|
| 852 |
|
| 853 | AutoCloseFD::AutoCloseFD(int fd)
|
| 854 | {
|
| 855 | this->fd = fd;
|
| 856 | }
|
| 857 |
|
| 858 |
|
| 859 | AutoCloseFD::AutoCloseFD(const AutoCloseFD & fd)
|
| 860 | {
|
| 861 | |
| 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 |
|
| 872 | AutoCloseFD::~AutoCloseFD()
|
| 873 | {
|
| 874 | try {
|
| 875 | close();
|
| 876 | } catch (...) {
|
| 877 | ignoreException();
|
| 878 | }
|
| 879 | }
|
| 880 |
|
| 881 |
|
| 882 | void AutoCloseFD::operator =(int fd)
|
| 883 | {
|
| 884 | if (this->fd != fd) close();
|
| 885 | this->fd = fd;
|
| 886 | }
|
| 887 |
|
| 888 |
|
| 889 | AutoCloseFD::operator int() const
|
| 890 | {
|
| 891 | return fd;
|
| 892 | }
|
| 893 |
|
| 894 |
|
| 895 | void AutoCloseFD::close()
|
| 896 | {
|
| 897 | if (fd != -1) {
|
| 898 | if (::close(fd) == -1)
|
| 899 |
|
| 900 | throw SysError(std::format("closing file descriptor {}", fd));
|
| 901 | fd = -1;
|
| 902 | }
|
| 903 | }
|
| 904 |
|
| 905 |
|
| 906 | bool AutoCloseFD::isOpen()
|
| 907 | {
|
| 908 | return fd != -1;
|
| 909 | }
|
| 910 |
|
| 911 |
|
| 912 |
|
| 913 | int AutoCloseFD::borrow()
|
| 914 | {
|
| 915 | int oldFD = fd;
|
| 916 | fd = -1;
|
| 917 | return oldFD;
|
| 918 | }
|
| 919 |
|
| 920 |
|
| 921 | void 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 |
|
| 932 | void 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 |
|
| 962 | int 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 |
|
| 997 | AutoCloseDir::AutoCloseDir()
|
| 998 | {
|
| 999 | dir = 0;
|
| 1000 | }
|
| 1001 |
|
| 1002 |
|
| 1003 | AutoCloseDir::AutoCloseDir(DIR * dir)
|
| 1004 | {
|
| 1005 | this->dir = dir;
|
| 1006 | }
|
| 1007 |
|
| 1008 |
|
| 1009 | AutoCloseDir::~AutoCloseDir()
|
| 1010 | {
|
| 1011 | close();
|
| 1012 | }
|
| 1013 |
|
| 1014 |
|
| 1015 | void AutoCloseDir::operator =(DIR * dir)
|
| 1016 | {
|
| 1017 | this->dir = dir;
|
| 1018 | }
|
| 1019 |
|
| 1020 |
|
| 1021 | AutoCloseDir::operator DIR *()
|
| 1022 | {
|
| 1023 | return dir;
|
| 1024 | }
|
| 1025 |
|
| 1026 |
|
| 1027 | void AutoCloseDir::close()
|
| 1028 | {
|
| 1029 | if (dir) {
|
| 1030 | closedir(dir);
|
| 1031 | dir = 0;
|
| 1032 | }
|
| 1033 | }
|
| 1034 |
|
| 1035 |
|
| 1036 | |
| 1037 |
|
| 1038 |
|
| 1039 | Pid::Pid()
|
| 1040 | : pid(-1), separatePG(false), killSignal(SIGKILL)
|
| 1041 | {
|
| 1042 | }
|
| 1043 |
|
| 1044 |
|
| 1045 | Pid::Pid(pid_t pid)
|
| 1046 | : pid(pid), separatePG(false), killSignal(SIGKILL)
|
| 1047 | {
|
| 1048 | }
|
| 1049 |
|
| 1050 |
|
| 1051 | Pid::~Pid()
|
| 1052 | {
|
| 1053 | kill();
|
| 1054 | }
|
| 1055 |
|
| 1056 |
|
| 1057 | void Pid::operator =(pid_t pid)
|
| 1058 | {
|
| 1059 | if (this->pid != pid) kill();
|
| 1060 | this->pid = pid;
|
| 1061 | killSignal = SIGKILL; |
| 1062 | }
|
| 1063 |
|
| 1064 |
|
| 1065 | Pid::operator pid_t()
|
| 1066 | {
|
| 1067 | return pid;
|
| 1068 | }
|
| 1069 |
|
| 1070 |
|
| 1071 | void 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 | |
| 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 |
|
| 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 |
|
| 1099 | int 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 |
|
| 1117 | void Pid::setSeparatePG(bool separatePG)
|
| 1118 | {
|
| 1119 | this->separatePG = separatePG;
|
| 1120 | }
|
| 1121 |
|
| 1122 |
|
| 1123 | void Pid::setKillSignal(int signal)
|
| 1124 | {
|
| 1125 | this->killSignal = signal;
|
| 1126 | }
|
| 1127 |
|
| 1128 |
|
| 1129 | void killUser(uid_t uid)
|
| 1130 | {
|
| 1131 | debug(std::format("killing all processes running under uid `{}'", uid));
|
| 1132 |
|
| 1133 | assert(uid != 0);
|
| 1134 |
|
| 1135 | |
| 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 | |
| 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 | |
| 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;
|
| 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 |
|
| 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 | |
| 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 |
|
| 1185 | pid_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 |
|
| 1215 | std::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 |
|
| 1224 | string runProgram(Path program, bool searchPath, const Strings & args)
|
| 1225 | {
|
| 1226 | checkInterrupt();
|
| 1227 |
|
| 1228 |
|
| 1229 | Pipe pipe;
|
| 1230 | pipe.create();
|
| 1231 |
|
| 1232 |
|
| 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 |
|
| 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 |
|
| 1264 | void 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);
|
| 1278 | }
|
| 1279 | }
|
| 1280 |
|
| 1281 |
|
| 1282 | void 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 |
|
| 1290 | void 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 |
|
| 1302 | void _interrupted()
|
| 1303 | {
|
| 1304 | |
| 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 |
|
| 1318 | template<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 |
|
| 1332 | template Strings tokenizeString(const string & s, const string & separators);
|
| 1333 | template StringSet tokenizeString(const string & s, const string & separators);
|
| 1334 | template vector<string> tokenizeString(const string & s, const string & separators);
|
| 1335 |
|
| 1336 |
|
| 1337 | string 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 |
|
| 1348 | string 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 |
|
| 1359 | string 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 |
|
| 1366 | string 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 |
|
| 1386 | bool statusOk(int status)
|
| 1387 | {
|
| 1388 | return WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
| 1389 | }
|
| 1390 |
|
| 1391 |
|
| 1392 | void 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 |
|
| 1401 | string 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 |
|
| 1419 | bool 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 |
|
| 1432 | string 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 |
|
| 1451 | void ignoreException()
|
| 1452 | {
|
| 1453 | try {
|
| 1454 | throw;
|
| 1455 | } catch (std::exception & e) {
|
| 1456 | printMsg(lvlError, std::format("error (ignored): {}", e.what()));
|
| 1457 | }
|
| 1458 | }
|
| 1459 |
|
| 1460 | static const string pathNullDevice = "/dev/null";
|
| 1461 |
|
| 1462 |
|
| 1463 | void commonChildInit(Pipe & logPipe)
|
| 1464 | {
|
| 1465 | |
| 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 |
|
| 1473 | logPipe.readSide.close();
|
| 1474 |
|
| 1475 |
|
| 1476 | if (dup2(logPipe.writeSide, STDERR_FILENO) == -1)
|
| 1477 | throw SysError("cannot pipe standard error into log file");
|
| 1478 |
|
| 1479 |
|
| 1480 | if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1)
|
| 1481 | throw SysError("cannot dup stderr into stdout");
|
| 1482 |
|
| 1483 |
|
| 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 |
|
| 1494 | Agent::Agent(const string &command, const Strings &args, const std::map<string, string> &env)
|
| 1495 | {
|
| 1496 | debug(std::format("starting agent '{}'", command));
|
| 1497 |
|
| 1498 |
|
| 1499 | fromAgent.create();
|
| 1500 |
|
| 1501 |
|
| 1502 | toAgent.create();
|
| 1503 |
|
| 1504 |
|
| 1505 | builderOut.create();
|
| 1506 |
|
| 1507 |
|
| 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 |
|
| 1519 | if (dup2(toAgent.readSide, STDIN_FILENO) == -1)
|
| 1520 | throw SysError("dupping to-hook read side");
|
| 1521 |
|
| 1522 |
|
| 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()); |
| 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 |
|
| 1541 | Agent::~Agent()
|
| 1542 | {
|
| 1543 | try {
|
| 1544 | toAgent.writeSide.close();
|
| 1545 | pid.kill(true);
|
| 1546 | } catch (...) {
|
| 1547 | ignoreException();
|
| 1548 | }
|
| 1549 | }
|
| 1550 |
|
| 1551 |
|
| 1552 | }
|