This fixes a bug in PHP/HH's crypt_blowfish implementation that can cause a short...
[hiphop-php.git] / hphp / util / process.cpp
blobdc0327f0b9b3155e41555c9eb223b8f9971d1aa0
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/util/process.h"
18 #include <sys/types.h>
19 #include <stdlib.h>
21 #ifdef _MSC_VER
22 #include <lmcons.h>
23 #include <Windows.h>
24 #include <ShlObj.h>
25 #else
26 #include <sys/fcntl.h>
27 #include <sys/utsname.h>
28 #include <sys/wait.h>
29 #include <pwd.h>
30 #include <folly/portability/Sockets.h>
31 #include <folly/portability/SysMman.h>
32 #include <folly/portability/Unistd.h>
33 #endif
35 #ifdef __APPLE__
36 #include <crt_externs.h>
37 #endif
39 #include <folly/Conv.h>
40 #include <folly/Format.h>
41 #include <folly/ScopeGuard.h>
42 #include <folly/String.h>
44 #include <filesystem>
45 #include <set>
47 #include "hphp/util/hugetlb.h"
48 #include "hphp/util/managed-arena.h"
49 #include "hphp/util/text-color.h"
50 #include "hphp/util/user-info.h"
52 namespace HPHP {
53 ///////////////////////////////////////////////////////////////////////////////
55 using std::string;
57 static void readString(FILE *f, string &out) {
58 size_t nread = 0;
59 constexpr unsigned int BUFFER_SIZE = 1024;
60 char buf[BUFFER_SIZE];
61 while ((nread = fread(buf, 1, BUFFER_SIZE, f)) != 0) {
62 out.append(buf, nread);
66 ///////////////////////////////////////////////////////////////////////////////
68 // Cached process statics
69 std::string Process::HostName;
70 std::string Process::CurrentWorkingDirectory;
71 char** Process::Argv;
72 std::atomic_int64_t ProcStatus::VmSizeKb;
73 std::atomic_int64_t ProcStatus::VmRSSKb;
74 std::atomic_int64_t ProcStatus::VmHWMKb;
75 std::atomic_int64_t ProcStatus::VmSwapKb;
76 std::atomic_int64_t ProcStatus::HugetlbPagesKb;
77 std::atomic_int64_t ProcStatus::UnusedKb;
78 std::atomic_int ProcStatus::threads;
79 std::atomic_uint ProcStatus::lastUpdate;
81 void Process::InitProcessStatics() {
82 HostName = GetHostName();
83 CurrentWorkingDirectory = GetCurrentDirectory();
86 ///////////////////////////////////////////////////////////////////////////////
87 // /proc/* parsing functions
89 std::string Process::GetCommandLine(pid_t pid) {
90 auto const name = folly::sformat("/proc/{}/cmdline", pid);
92 std::string cmdline;
93 auto const f = fopen(name.c_str(), "r");
94 if (f) {
95 readString(f, cmdline);
96 fclose(f);
99 std::string converted;
100 for (auto ch : cmdline) {
101 converted += ch ? ch : ' ';
103 return converted;
106 bool Process::IsUnderGDB() {
107 auto const cmdStr = GetCommandLine(getppid());
108 auto const cmdPiece = folly::StringPiece{cmdStr};
110 if (cmdPiece.empty()) return false;
112 auto const spaceIdx = std::min(cmdPiece.find(' '), cmdPiece.size() - 1);
113 auto const binaryPiece = cmdPiece.subpiece(0, spaceIdx + 1);
115 std::filesystem::path binaryPath(binaryPiece.begin(), binaryPiece.end());
116 return binaryPath.filename() == "gdb ";
119 int64_t Process::GetMemUsageMb() {
120 ProcStatus::update();
121 return ProcStatus::valid() ? ProcStatus::adjustedRssKb() / 1024 : 0;
124 int64_t Process::GetSystemCPUDelayMS() {
125 static FILE* fp = nullptr;
126 if (!fp) {
127 if (!(fp = fopen("/proc/schedstat", "r"))) {
128 return -1;
131 // Refresh the proc info.
132 rewind(fp);
133 fflush(fp);
135 int64_t totalCpuDelay = 0;
136 // Supposedly this should be enough to hold th important lines of the
137 // schedstat file.
138 char buf[320];
139 while (fgets(buf, sizeof(buf), fp) != nullptr) {
140 if (strncmp(buf, "cpu", 3) == 0) {
141 uint64_t cpuDelay;
142 if (sscanf(buf,
143 "%*s %*u %*u %*u %*u %*u %*u %*u %lu %*u",
144 &cpuDelay) != 1) {
145 return -1;
147 totalCpuDelay += cpuDelay;
150 // The kernel reports the information in nanoseconds. Convert it
151 // to milliseconds.
152 return totalCpuDelay / 1000000;
156 int Process::GetNumThreads() {
157 ProcStatus::update();
158 return ProcStatus::valid() ? ProcStatus::nThreads() : 1;
161 // Files such as /proc/meminfo and /proc/self/status contain many lines
162 // formatted as one of the following:
163 // <fieldName>: <number>
164 // <fieldName>: <number> kB
165 // This function parses the line and return the number in it. -1 is returned
166 // when the line isn't formatted as expected (until one day we need to read a
167 // line where -1 is a legit value).
168 static int64_t readSize(const char* line, bool expectKB = false) {
169 int64_t result = -1;
170 char tail[8];
171 auto n = sscanf(line, "%*s %" SCNd64 " %7s", &result, tail);
172 if (expectKB) {
173 if (n < 2) return -1;
174 if (tail[0] != 'k' || tail[1] != 'B') return -1;
176 return result;
179 bool Process::GetMemoryInfo(MemInfo& info) {
180 #ifdef _WIN32
181 #error "Process::GetMemoryInfo() doesn't support Windows (yet)."
182 return false;
183 #endif
185 info = MemInfo{};
186 FILE* f = fopen("/proc/meminfo", "r");
187 if (f) {
188 SCOPE_EXIT{ fclose(f); };
190 char line[128];
191 while (fgets(line, sizeof(line), f)) {
192 auto const kb = readSize(line, true);
193 if (!strncmp(line, "MemTotal:", 9)) {
194 if (kb >= 0) info.totalMb = kb / 1024;
195 } else if (!strncmp(line, "MemFree:", 8)) {
196 if (kb >= 0) info.freeMb = kb / 1024;
197 } else if (!strncmp(line, "Buffers:", 8)) {
198 if (kb >= 0) info.buffersMb = kb / 1024;
199 } else if (!strncmp(line, "Cached:", 7)) {
200 if (kb >= 0) info.cachedMb = kb / 1024;
201 } else if (!strncmp(line, "MemAvailable:", 13)) {
202 if (kb >= 0) info.availableMb = kb / 1024;
204 if (info.valid()) return true;
206 // If MemAvailable isn't available, which shouldn't be the case for kernel
207 // versions later than 3.14, we get a rough estimation.
208 if (info.availableMb < 0 && info.freeMb >= 0 &&
209 info.cachedMb >= 0 && info.buffersMb >= 0) {
210 info.availableMb = info.freeMb + info.cachedMb;
211 return true;
214 return false;
217 int Process::GetCPUCount() {
218 return sysconf(_SC_NPROCESSORS_ONLN);
221 #ifdef __x86_64__
222 static __inline void do_cpuid(u_int ax, u_int *p) {
223 asm volatile ("cpuid"
224 : "=a" (p[0]), "=b" (p[1]), "=c" (p[2]), "=d" (p[3])
225 : "0" (ax));
227 #elif defined(_M_X64)
228 #include <intrin.h>
229 static ALWAYS_INLINE void do_cpuid(int func, uint32_t* p) {
230 __cpuid((int*)p, func);
232 #endif
234 std::string Process::GetCPUModel() {
235 #if defined(__x86_64__) || defined(_M_X64)
236 uint32_t regs[4];
237 do_cpuid(0, regs);
239 const int vendor_size = sizeof(regs[1])*3;
240 std::swap(regs[2], regs[3]);
241 uint32_t cpu_exthigh = 0;
242 if (memcmp(regs + 1, "GenuineIntel", vendor_size) == 0 ||
243 memcmp(regs + 1, "AuthenticAMD", vendor_size) == 0) {
244 do_cpuid(0x80000000, regs);
245 cpu_exthigh = regs[0];
248 char cpu_brand[3 * sizeof(regs) + 1];
249 char *brand = cpu_brand;
250 if (cpu_exthigh >= 0x80000004) {
251 for (u_int i = 0x80000002; i < 0x80000005; i++) {
252 do_cpuid(i, regs);
253 memcpy(brand, regs, sizeof(regs));
254 brand += sizeof(regs);
257 *brand = '\0';
258 assert(brand - cpu_brand < sizeof(cpu_brand));
259 return cpu_brand;
261 #else
262 // On non-x64, fall back to calling uname
263 std::string model = "Unknown ";
264 struct utsname uname_buf;
265 uname(&uname_buf);
266 model.append(uname_buf.machine);
267 return model;
269 #endif // __x86_64__
272 ///////////////////////////////////////////////////////////////////////////////
274 std::string Process::GetAppName() {
275 const char* progname = getenv("_");
276 if (!progname || !*progname) {
277 progname = "unknown program";
279 return progname;
282 std::string Process::GetHostName() {
283 char hostbuf[128];
284 hostbuf[0] = '\0'; // for cleaner valgrind output when gethostname() fails
285 gethostname(hostbuf, sizeof(hostbuf));
286 hostbuf[sizeof(hostbuf) - 1] = '\0';
287 return hostbuf;
290 std::string Process::GetCurrentUser() {
291 const char *name = getenv("LOGNAME");
292 if (name && *name) {
293 return name;
296 #ifdef _MSC_VER
297 char username[UNLEN + 1];
298 DWORD username_len = UNLEN + 1;
299 if (GetUserName(username, &username_len))
300 return std::string(username, username_len);
301 #else
302 auto buf = PasswdBuffer{};
303 passwd *pwd;
304 if (!getpwuid_r(geteuid(), &buf.ent, buf.data.get(), buf.size, &pwd) &&
305 pwd && pwd->pw_name) {
306 return pwd->pw_name;
308 #endif
309 return "";
312 std::string Process::GetCurrentDirectory() {
313 char buf[PATH_MAX + 64]; // additional space for suffixes like " (deleted)";
314 memset(buf, 0, sizeof(buf));
315 if (char* cwd = getcwd(buf, PATH_MAX)) return cwd;
317 #if defined(__linux__)
318 if (errno != ENOENT) {
319 return "";
321 // Read cwd symlink directly if it leads to the deleted path.
322 int r = readlink("/proc/self/cwd", buf, sizeof(buf));
323 if (r == -1) {
324 return "";
326 auto const kDeleted = " (deleted)";
327 auto const kDeletedLen = strlen(kDeleted);
328 if (r >= kDeletedLen && !strcmp(buf + r - kDeletedLen, kDeleted)) {
329 buf[r - kDeletedLen] = 0;
331 return &(buf[0]);
332 #else
333 // /proc/self/cwd is not available.
334 return "";
335 #endif
338 std::string Process::GetHomeDirectory() {
339 string ret;
341 const char *home = getenv("HOME");
342 if (home && *home) {
343 ret = home;
344 } else {
345 #ifdef _MSC_VER
346 PWSTR path;
347 if (SHGetKnownFolderPath(FOLDERID_UsersFiles, 0, nullptr, &path) == S_OK) {
348 char hPath[PATH_MAX];
349 size_t len = wcstombs(hPath, path, MAX_PATH);
350 CoTaskMemFree(path);
351 ret = std::string(hPath, len);
353 #else
354 passwd *pwd = getpwent();
355 if (pwd && pwd->pw_dir) {
356 ret = pwd->pw_dir;
358 #endif
361 if (ret.empty() || ret[ret.size() - 1] != '/') {
362 ret += '/';
364 return ret;
367 void Process::SetCoreDumpHugePages() {
368 #if defined(__linux__)
370 * From documentation athttp://man7.org/linux/man-pages/man5/core.5.html
372 * The bits in coredump_filter have the following meanings:
374 * bit 0 Dump anonymous private mappings.
375 * bit 1 Dump anonymous shared mappings.
376 * bit 2 Dump file-backed private mappings.
377 * bit 3 Dump file-backed shared mappings.
378 * bit 4 (since Linux 2.6.24) Dump ELF headers.
379 * bit 5 (since Linux 2.6.28) Dump private huge pages.
380 * bit 6 (since Linux 2.6.28) Dump shared huge pages.
381 * bit 7 (since Linux 4.4) Dump private DAX pages.
382 * bit 8 (since Linux 4.4) Dump shared DAX pages.
384 if (FILE* f = fopen("/proc/self/coredump_filter", "r+")) {
385 unsigned mask = 0;
386 if (fscanf(f, "%x", &mask)) {
387 constexpr unsigned hugetlbMask = 0x60;
388 if ((mask & hugetlbMask) != hugetlbMask) {
389 mask |= hugetlbMask;
390 rewind(f);
391 fprintf(f, "0x%x", mask);
394 fclose(f);
396 #endif
399 void ProcStatus::update() {
400 if (FILE* f = fopen("/proc/self/status", "r")) {
401 char line[128];
402 int64_t vmsize = 0, vmrss = 0, vmhwm = 0, vmswap = 0, hugetlb = 0;
403 while (fgets(line, sizeof(line), f)) {
404 if (!strncmp(line, "VmSize:", 7)) {
405 vmsize = readSize(line, true);
406 } else if (!strncmp(line, "VmRSS:", 6)) {
407 vmrss = readSize(line, true);
408 } else if (!strncmp(line, "VmHWM:", 6)) {
409 vmhwm = readSize(line, true);
410 } else if (!strncmp(line, "VmSwap:", 7)) {
411 vmswap = readSize(line, true);
412 } else if (!strncmp(line, "HugetlbPages:", 13)) {
413 hugetlb = readSize(line, true);
414 } else if (!strncmp(line, "Threads:", 8)) {
415 threads.store(readSize(line, false), std::memory_order_relaxed);
418 fclose(f);
419 if (vmrss <= 0) {
420 // Invalid
421 lastUpdate.store(0, std::memory_order_release);
422 } else {
423 VmSizeKb.store(vmsize, std::memory_order_relaxed);
424 VmRSSKb.store(vmrss, std::memory_order_relaxed);
425 VmSwapKb.store(vmswap, std::memory_order_relaxed);
426 VmHWMKb.store(vmhwm + hugetlb, std::memory_order_relaxed);
427 HugetlbPagesKb.store(hugetlb, std::memory_order_relaxed);
428 lastUpdate.store(time(), std::memory_order_release);
430 #ifdef USE_JEMALLOC
431 mallctl_epoch();
432 #if USE_JEMALLOC_EXTENT_HOOKS
433 size_t unused = 0;
434 // Various arenas where range of hugetlb pages can be reserved but only
435 // partially used.
436 unused += alloc::getRange(alloc::AddrRangeClass::VeryLow).retained();
437 unused += alloc::getRange(alloc::AddrRangeClass::Low).retained();
438 unused += alloc::getRange(alloc::AddrRangeClass::Uncounted).retained();
439 if (alloc::g_arena0) {
440 unused += alloc::g_arena0->retained();
442 for (auto const arena : alloc::g_local_arenas) {
443 if (arena) unused += arena->retained();
445 updateUnused(unused >> 10); // convert to kB
446 #endif
447 #endif
451 bool Process::OOMScoreAdj(int adj) {
452 #ifdef __linux__
453 if (adj >= -1000 && adj < 1000) {
454 if (auto f = fopen("/proc/self/oom_score_adj", "r+")) {
455 fprintf(f, "%d", adj);
456 fclose(f);
457 return true;
460 #endif
461 return false;
464 int Process::Relaunch() {
465 if (!Argv) {
466 errno = EINVAL;
467 return -1;
469 return execvp(Argv[0], Argv);
472 std::map<int, int> Process::RemapFDsPreExec(const std::map<int, int>& fds) {
473 std::map<int, int> unspecified;
474 // 1. copy all to FDs outside of STDIO range
475 std::map<int, int> dups;
476 std::set<int> preserve_set;
477 for (auto& [_target, current] : fds) {
478 if (dups.find(current) != dups.end()) {
479 continue;
482 int next_fd;
483 bool conflict;
484 do {
485 conflict = false;
486 // don't conflict with STDIO
487 next_fd = dup(current);
488 if (next_fd <= STDERR_FILENO) {
489 conflict = true;
490 continue;
492 // don't conflict with targets
493 conflict = false;
494 for (auto [target, _current] : fds) {
495 if (next_fd == target) {
496 conflict = true;
497 break;
500 } while (conflict);
501 dups[current] = next_fd;
502 preserve_set.emplace(next_fd);
505 // 2. clean up libc STDIO
507 // Don't want to swap the FD underlying these FILE*...
509 // If they are closed already, these are silent no-ops.
510 fclose(stdin);
511 fclose(stdout);
512 fclose(stderr);
514 // 3. close all FDs except our dups
516 // This includes the STDIO FDs as it's possible that:
517 // - the FILE* were previously closed (so the fclose above were no-ops)
518 // - the FDs were then re-used
519 #ifdef __APPLE__
520 const char* fd_dir = "/dev/fd";
521 #endif
522 #ifdef __linux__
523 const char* fd_dir = "/proc/self/fd";
524 #endif
525 // If you close FDs while in this loop, they get removed from /proc/self/fd
526 // and the iterator gets sad ("Bad file descriptor: /proc/self/fd")
527 std::set<int> fds_to_close;
528 for (const auto& entry : std::filesystem::directory_iterator(fd_dir)) {
529 char* endptr = nullptr;
530 auto filename = entry.path().filename();
531 const char* filename_c = filename.c_str();
532 const int fd = strtol(filename_c, &endptr, 10);
533 assert(endptr != filename_c); // no matching characters
534 assert(*endptr == '\0'); // entire string
535 if (preserve_set.find(fd) != preserve_set.end()) {
536 continue;
538 fds_to_close.emplace(fd);
540 for (const auto& fd: fds_to_close) {
541 close(fd);
544 // 4. Move the dups into place.
545 for (const auto& [target, orig] : fds) {
546 int tmp = dups[orig];
548 if (target < 0 /* don't care what the FD is */) {
549 unspecified[target] = tmp;
550 } else {
551 dup2(tmp, target);
555 // 5. Close the dups; do this separately to above in case
556 // the same orig was used for multiple targets
557 for (const auto& [target, orig] : fds) {
558 if (target < 0) {
559 continue;
561 close(dups.at(orig));
563 return unspecified;
566 namespace {
567 char **build_cstrarr(const std::vector<std::string> &vec) {
568 char **cstrarr = nullptr;
569 int size = vec.size();
570 if (size) {
571 cstrarr = (char **)malloc((size + 1) * sizeof(char *));
572 int j = 0;
573 for (unsigned int i = 0; i < vec.size(); i++, j++) {
574 *(cstrarr + j) = (char *)vec[i].c_str();
576 *(cstrarr + j) = nullptr;
578 return cstrarr;
580 } // namespace {
582 pid_t Process::ForkAndExecve(
583 const std::string& path,
584 const std::vector<std::string>& argv,
585 const std::vector<std::string>& envp,
586 const std::string& cwd,
587 const std::map<int, int>& orig_fds,
588 int flags,
589 pid_t pgid
591 // Distinguish execve failure: if the write side of the pipe
592 // is closed with no data, it succeeded.
593 int fork_fds[2];
594 pipe(fork_fds);
595 int fork_r = fork_fds[0];
596 int fork_w = fork_fds[1];
597 fcntl(fork_w, F_SETFD, fcntl(fork_w, F_GETFD) | O_CLOEXEC);
599 pid_t child = fork();
601 if (child == -1) {
602 return -1;
605 if (child == 0) {
606 mprotect_1g_pages(PROT_READ);
607 Process::OOMScoreAdj(1000);
609 close(fork_r);
611 // Need mutable copy
612 std::map<int, int> fds(orig_fds);
613 fds[-1] = fork_w;
614 const auto remapped = Process::RemapFDsPreExec(fds);
615 fork_w = remapped.at(-1);
617 if (!cwd.empty()) {
618 if (cwd != Process::GetCurrentDirectory()) {
619 if (chdir(cwd.c_str()) == -1) {
620 dprintf(fork_w, "%s %d", "chdir", errno);
621 _Exit(1);
626 if (flags & Process::FORK_AND_EXECVE_FLAG_SETSID) {
627 if (setsid() == -1) {
628 dprintf(fork_w, "%s\n%d\n", "setsid", errno);
629 _Exit(1);
631 } else if (flags & Process::FORK_AND_EXECVE_FLAG_SETPGID) {
632 if (setpgid(0, pgid) == -1) {
633 dprintf(fork_w, "%s %d", "setpgid", errno);
634 _Exit(1);
638 char** argv_arr = build_cstrarr(argv);
639 char** envp_arr = build_cstrarr(envp);
640 SCOPE_EXIT { free(argv_arr); free(envp_arr); };
642 if (flags & Process::FORK_AND_EXECVE_FLAG_EXECVPE) {
643 #if defined(__APPLE__)
644 // execvpe() is a glibcism
646 // We could either:
647 // - use `execve()` and implement our own $PATH behavior
648 // - use `execvp()` and implement our own envp behavior
649 // The latter seems less likely to lead to accidental problems, so let's
650 // do that.
651 char**& environ = *_NSGetEnviron();
652 // We could also use this implementation on Linux (using the standard
653 // `extern char** environ` instead of the Apple-specific call above)...
654 environ = envp_arr;
655 execvp(path.c_str(), argv_arr);
656 #else
657 // ... but it feels nasty enough that I'll use the glibcism
658 execvpe(path.c_str(), argv_arr, envp_arr);
659 #endif
660 } else {
661 execve(path.c_str(), argv_arr, envp_arr);
663 dprintf(fork_w, "%s %d", "execve", errno);
664 _Exit(1);
667 close(fork_w);
669 pollfd pfd[1];
670 pfd[0].fd = fork_w;
671 pfd[0].events = POLLIN;
672 int ret;
673 do {
674 ret = poll(pfd, /* number of fds = */ 1, /* timeout = no timeout*/ -1);
675 } while (ret == -1 && errno == EINTR);
677 char buf[16];
678 auto len = read(fork_r, &buf, sizeof(buf));
679 close(fork_r);
681 // Closed without write, which means close-on-exec
682 if (len < 1) {
683 return child;
686 char failed_call_buf[17]; // 16 + trailing null
687 int saved_errno;
688 if (sscanf(buf, "%16s %d", failed_call_buf, &saved_errno) != 2) {
689 return -999;
692 // Doing the call => return value here instead of sending return values over
693 // the pipe so that it's all in one place, and we're less likely to introduce
694 // bugs when/if we add additional features.
695 const std::string failed_call(failed_call_buf);
696 SCOPE_EXIT { errno = saved_errno; };
697 if (failed_call == "chdir") {
698 return -2;
700 if (failed_call == "setsid") {
701 return -3;
703 if (failed_call == "setpgid") {
704 return -4;
706 if (failed_call == "execve") {
707 return -5;
709 if (failed_call == "putenv") {
710 return -6;
712 return -9999;
715 ///////////////////////////////////////////////////////////////////////////////