2 +----------------------------------------------------------------------+
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>
26 #include <sys/fcntl.h>
27 #include <sys/utsname.h>
30 #include <folly/portability/Sockets.h>
31 #include <folly/portability/SysMman.h>
32 #include <folly/portability/Unistd.h>
36 #include <crt_externs.h>
39 #include <folly/Conv.h>
40 #include <folly/Format.h>
41 #include <folly/ScopeGuard.h>
42 #include <folly/String.h>
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"
53 ///////////////////////////////////////////////////////////////////////////////
57 static void readString(FILE *f
, string
&out
) {
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
;
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
);
93 auto const f
= fopen(name
.c_str(), "r");
95 readString(f
, cmdline
);
99 std::string converted
;
100 for (auto ch
: cmdline
) {
101 converted
+= ch
? ch
: ' ';
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;
127 if (!(fp
= fopen("/proc/schedstat", "r"))) {
131 // Refresh the proc info.
135 int64_t totalCpuDelay
= 0;
136 // Supposedly this should be enough to hold th important lines of the
139 while (fgets(buf
, sizeof(buf
), fp
) != nullptr) {
140 if (strncmp(buf
, "cpu", 3) == 0) {
143 "%*s %*u %*u %*u %*u %*u %*u %*u %lu %*u",
147 totalCpuDelay
+= cpuDelay
;
150 // The kernel reports the information in nanoseconds. Convert it
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) {
171 auto n
= sscanf(line
, "%*s %" SCNd64
" %7s", &result
, tail
);
173 if (n
< 2) return -1;
174 if (tail
[0] != 'k' || tail
[1] != 'B') return -1;
179 bool Process::GetMemoryInfo(MemInfo
& info
) {
181 #error "Process::GetMemoryInfo() doesn't support Windows (yet)."
186 FILE* f
= fopen("/proc/meminfo", "r");
188 SCOPE_EXIT
{ fclose(f
); };
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
;
217 int Process::GetCPUCount() {
218 return sysconf(_SC_NPROCESSORS_ONLN
);
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])
227 #elif defined(_M_X64)
229 static ALWAYS_INLINE
void do_cpuid(int func
, uint32_t* p
) {
230 __cpuid((int*)p
, func
);
234 std::string
Process::GetCPUModel() {
235 #if defined(__x86_64__) || defined(_M_X64)
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
++) {
253 memcpy(brand
, regs
, sizeof(regs
));
254 brand
+= sizeof(regs
);
258 assert(brand
- cpu_brand
< sizeof(cpu_brand
));
262 // On non-x64, fall back to calling uname
263 std::string model
= "Unknown ";
264 struct utsname uname_buf
;
266 model
.append(uname_buf
.machine
);
272 ///////////////////////////////////////////////////////////////////////////////
274 std::string
Process::GetAppName() {
275 const char* progname
= getenv("_");
276 if (!progname
|| !*progname
) {
277 progname
= "unknown program";
282 std::string
Process::GetHostName() {
284 hostbuf
[0] = '\0'; // for cleaner valgrind output when gethostname() fails
285 gethostname(hostbuf
, sizeof(hostbuf
));
286 hostbuf
[sizeof(hostbuf
) - 1] = '\0';
290 std::string
Process::GetCurrentUser() {
291 const char *name
= getenv("LOGNAME");
297 char username
[UNLEN
+ 1];
298 DWORD username_len
= UNLEN
+ 1;
299 if (GetUserName(username
, &username_len
))
300 return std::string(username
, username_len
);
302 auto buf
= PasswdBuffer
{};
304 if (!getpwuid_r(geteuid(), &buf
.ent
, buf
.data
.get(), buf
.size
, &pwd
) &&
305 pwd
&& pwd
->pw_name
) {
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
) {
321 // Read cwd symlink directly if it leads to the deleted path.
322 int r
= readlink("/proc/self/cwd", buf
, sizeof(buf
));
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;
333 // /proc/self/cwd is not available.
338 std::string
Process::GetHomeDirectory() {
341 const char *home
= getenv("HOME");
347 if (SHGetKnownFolderPath(FOLDERID_UsersFiles
, 0, nullptr, &path
) == S_OK
) {
348 char hPath
[PATH_MAX
];
349 size_t len
= wcstombs(hPath
, path
, MAX_PATH
);
351 ret
= std::string(hPath
, len
);
354 passwd
*pwd
= getpwent();
355 if (pwd
&& pwd
->pw_dir
) {
361 if (ret
.empty() || ret
[ret
.size() - 1] != '/') {
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+")) {
386 if (fscanf(f
, "%x", &mask
)) {
387 constexpr unsigned hugetlbMask
= 0x60;
388 if ((mask
& hugetlbMask
) != hugetlbMask
) {
391 fprintf(f
, "0x%x", mask
);
399 void ProcStatus::update() {
400 if (FILE* f
= fopen("/proc/self/status", "r")) {
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
);
421 lastUpdate
.store(0, std::memory_order_release
);
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
);
432 #if USE_JEMALLOC_EXTENT_HOOKS
434 // Various arenas where range of hugetlb pages can be reserved but only
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
451 bool Process::OOMScoreAdj(int adj
) {
453 if (adj
>= -1000 && adj
< 1000) {
454 if (auto f
= fopen("/proc/self/oom_score_adj", "r+")) {
455 fprintf(f
, "%d", adj
);
464 int Process::Relaunch() {
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()) {
486 // don't conflict with STDIO
487 next_fd
= dup(current
);
488 if (next_fd
<= STDERR_FILENO
) {
492 // don't conflict with targets
494 for (auto [target
, _current
] : fds
) {
495 if (next_fd
== target
) {
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.
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
520 const char* fd_dir
= "/dev/fd";
523 const char* fd_dir
= "/proc/self/fd";
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()) {
538 fds_to_close
.emplace(fd
);
540 for (const auto& fd
: fds_to_close
) {
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
;
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
) {
561 close(dups
.at(orig
));
567 char **build_cstrarr(const std::vector
<std::string
> &vec
) {
568 char **cstrarr
= nullptr;
569 int size
= vec
.size();
571 cstrarr
= (char **)malloc((size
+ 1) * sizeof(char *));
573 for (unsigned int i
= 0; i
< vec
.size(); i
++, j
++) {
574 *(cstrarr
+ j
) = (char *)vec
[i
].c_str();
576 *(cstrarr
+ j
) = nullptr;
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
,
591 // Distinguish execve failure: if the write side of the pipe
592 // is closed with no data, it succeeded.
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();
606 mprotect_1g_pages(PROT_READ
);
607 Process::OOMScoreAdj(1000);
612 std::map
<int, int> fds(orig_fds
);
614 const auto remapped
= Process::RemapFDsPreExec(fds
);
615 fork_w
= remapped
.at(-1);
618 if (cwd
!= Process::GetCurrentDirectory()) {
619 if (chdir(cwd
.c_str()) == -1) {
620 dprintf(fork_w
, "%s %d", "chdir", errno
);
626 if (flags
& Process::FORK_AND_EXECVE_FLAG_SETSID
) {
627 if (setsid() == -1) {
628 dprintf(fork_w
, "%s\n%d\n", "setsid", errno
);
631 } else if (flags
& Process::FORK_AND_EXECVE_FLAG_SETPGID
) {
632 if (setpgid(0, pgid
) == -1) {
633 dprintf(fork_w
, "%s %d", "setpgid", errno
);
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
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
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)...
655 execvp(path
.c_str(), argv_arr
);
657 // ... but it feels nasty enough that I'll use the glibcism
658 execvpe(path
.c_str(), argv_arr
, envp_arr
);
661 execve(path
.c_str(), argv_arr
, envp_arr
);
663 dprintf(fork_w
, "%s %d", "execve", errno
);
671 pfd
[0].events
= POLLIN
;
674 ret
= poll(pfd
, /* number of fds = */ 1, /* timeout = no timeout*/ -1);
675 } while (ret
== -1 && errno
== EINTR
);
678 auto len
= read(fork_r
, &buf
, sizeof(buf
));
681 // Closed without write, which means close-on-exec
686 char failed_call_buf
[17]; // 16 + trailing null
688 if (sscanf(buf
, "%16s %d", failed_call_buf
, &saved_errno
) != 2) {
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") {
700 if (failed_call
== "setsid") {
703 if (failed_call
== "setpgid") {
706 if (failed_call
== "execve") {
709 if (failed_call
== "putenv") {
715 ///////////////////////////////////////////////////////////////////////////////