2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010- 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 +----------------------------------------------------------------------+
17 #include "hphp/util/process.h"
18 #include "hphp/util/base.h"
20 #include "hphp/util/logger.h"
21 #include "hphp/util/async_func.h"
22 #include "hphp/util/text_color.h"
27 ///////////////////////////////////////////////////////////////////////////////
30 static void swap_fd(const string
&filename
, FILE *fdesc
) {
31 FILE *f
= fopen(filename
.c_str(), "a");
32 if (f
== nullptr || dup2(fileno(f
), fileno(fdesc
)) < 0) {
38 ///////////////////////////////////////////////////////////////////////////////
42 FileReader(FilePtr f
, string
&out
) : m_f(f
), m_out(out
) {}
43 void read() { readString(m_f
.get(), m_out
); }
45 static void readString(FILE *f
, string
&out
) {
47 const unsigned int BUFFER_SIZE
= 1024;
48 char buf
[BUFFER_SIZE
];
49 while ((nread
= fread(buf
, 1, BUFFER_SIZE
, f
)) != 0) {
50 out
.append(buf
, nread
);
59 ///////////////////////////////////////////////////////////////////////////////
61 // Cached process statics
62 std::string
Process::HostName
;
63 std::string
Process::CurrentWorkingDirectory
;
65 void Process::InitProcessStatics() {
66 HostName
= GetHostName();
67 CurrentWorkingDirectory
= GetCurrentDirectory();
70 bool Process::Exec(const char *path
, const char *argv
[], const char *in
,
71 string
&out
, string
*err
/* = NULL */,
72 bool color
/* = false */) {
74 int fdin
= 0; int fdout
= 0; int fderr
= 0;
75 int pid
= Exec(path
, argv
, &fdin
, &fdout
, &fderr
);
76 if (pid
== 0) return false;
79 FilePtr
sin(fdopen(fdin
, "w"), file_closer());
80 if (!sin
) return false;
82 fwrite(in
, 1, strlen(in
), sin
.get());
87 if (fcntl(fdout
, F_SETFL
, O_NONBLOCK
)) {
88 perror("fcntl failed on fdout");
91 if (fcntl(fderr
, F_SETFL
, O_NONBLOCK
)) {
92 perror("fcntl failed on fderr");
95 while (fdout
|| fderr
) {
100 fds
[n
].events
= POLLIN
| POLLHUP
;
105 fds
[n
].events
= POLLIN
| POLLHUP
;
109 n
= poll(fds
, n
, -1);
116 if (fds
[n
++].revents
& (POLLIN
| POLLHUP
)) {
117 int e
= read(fdout
, buffer
, sizeof buffer
);
122 if (color
&& s_stdout_color
) {
123 out
.append(s_stdout_color
);
124 out
.append(buffer
, e
);
125 out
.append(ANSI_COLOR_END
);
127 out
.append(buffer
, e
);
134 if (fds
[n
++].revents
& (POLLIN
| POLLHUP
)) {
135 int e
= read(fderr
, buffer
, sizeof buffer
);
140 if (color
&& s_stdout_color
) {
141 err
->append(s_stderr_color
);
142 err
->append(buffer
, e
);
143 err
->append(ANSI_COLOR_END
);
145 err
->append(buffer
, e
);
154 if (waitpid(pid
, &status
, 0) != pid
) {
155 Logger::Error("Failed to wait for `%s'\n", path
);
156 } else if (WIFEXITED(status
)) {
157 if (WEXITSTATUS(status
) != 0) {
158 Logger::Verbose("Status %d running command: `%s'\n",
159 WEXITSTATUS(status
), path
);
161 Logger::Verbose(" arg: `%s'\n", *argv
);
168 Logger::Verbose("Non-normal exit\n");
169 if (WIFSIGNALED(status
)) {
170 Logger::Verbose(" signaled with %d\n", WTERMSIG(status
));
176 int Process::Exec(const std::string
&cmd
, const std::string
&outf
,
177 const std::string
&errf
) {
178 vector
<string
> argvs
;
179 Util::split(' ', cmd
.c_str(), argvs
);
186 Logger::Error("Unable to fork: %d %s", errno
,
187 Util::safe_strerror(errno
).c_str());
191 signal(SIGTSTP
,SIG_IGN
);
193 swap_fd(outf
, stdout
);
194 swap_fd(errf
, stderr
);
196 int count
= argvs
.size();
197 char **argv
= (char**)calloc(count
+ 1, sizeof(char*));
198 for (int i
= 0; i
< count
; i
++) {
199 argv
[i
] = (char*)argvs
[i
].c_str();
201 argv
[count
] = nullptr;
203 execvp(argv
[0], argv
);
204 Logger::Error("Failed to exec `%s'\n", cmd
.c_str());
212 int Process::Exec(const char *path
, const char *argv
[], int *fdin
, int *fdout
,
214 CPipe pipein
, pipeout
, pipeerr
;
215 if (!pipein
.open() || !pipeout
.open() || !pipeerr
.open()) {
221 Logger::Error("Unable to fork: %d %s", errno
,
222 Util::safe_strerror(errno
).c_str());
227 * I don't know why, but things work alot better if this process ignores
228 * the tstp signal (ctrl-Z). If not, it locks up if you hit ctrl-Z then
231 signal(SIGTSTP
,SIG_IGN
);
233 if (pipein
.dupOut2(fileno(stdin
)) && pipeout
.dupIn2(fileno(stdout
)) &&
234 pipeerr
.dupIn2(fileno(stderr
))) {
235 pipeout
.close(); pipeerr
.close(); pipein
.close();
237 const char *argvnull
[2] = {"", nullptr};
238 execvp(path
, const_cast<char**>(argv
? argv
: argvnull
));
240 Logger::Error("Failed to exec `%s'\n", path
);
243 if (fdout
) *fdout
= pipeout
.detachOut();
244 if (fderr
) *fderr
= pipeerr
.detachOut();
245 if (fdin
) *fdin
= pipein
.detachIn();
250 * Copied from http://www-theorie.physik.unizh.ch/~dpotter/howto/daemonize
252 #define EXIT_SUCCESS 0
253 #define EXIT_FAILURE 1
254 void Process::Daemonize(const char *stdoutFile
/* = "/dev/null" */,
255 const char *stderrFile
/* = "/dev/null" */) {
258 /* already a daemon */
259 if (getppid() == 1) return;
261 /* Fork off the parent process */
266 /* If we got a good PID, then we can exit the parent process. */
271 /* At this point we are executing as the child process */
273 /* Change the file mode mask */
276 /* Create a new SID for the child process */
282 /* Change the current working directory. This prevents the current
283 directory from being locked; hence not being able to remove it. */
284 if ((chdir("/")) < 0) {
288 /* Redirect standard files to /dev/null */
289 if (!freopen("/dev/null", "r", stdin
)) exit(EXIT_FAILURE
);
290 if (stdoutFile
&& *stdoutFile
) {
291 if (!freopen(stdoutFile
, "a", stdout
)) exit(EXIT_FAILURE
);
293 if (!freopen("/dev/null", "w", stdout
)) exit(EXIT_FAILURE
);
295 if (stderrFile
&& *stderrFile
) {
296 if (!freopen(stderrFile
, "a", stderr
)) exit(EXIT_FAILURE
);
298 if (!freopen("/dev/null", "w", stderr
)) exit(EXIT_FAILURE
);
302 ///////////////////////////////////////////////////////////////////////////////
303 // /proc/* parsing functions
305 pid_t
Process::GetProcessId(const std::string
&cmd
,
306 bool matchAll
/* = false */) {
307 std::vector
<pid_t
> pids
;
308 GetProcessId(cmd
, pids
, matchAll
);
309 return pids
.empty() ? 0 : pids
[0];
312 void Process::GetProcessId(const std::string
&cmd
, std::vector
<pid_t
> &pids
,
313 bool matchAll
/* = false */) {
314 const char *argv
[] = {"", "/proc", "-regex", "/proc/[0-9]+/cmdline", nullptr};
316 Exec("find", argv
, nullptr, out
);
318 vector
<string
> files
;
319 Util::split('\n', out
.c_str(), files
, true);
323 size_t pos
= ccmd
.find(' ');
324 if (pos
!= string::npos
) {
325 ccmd
= ccmd
.substr(0, pos
);
327 pos
= ccmd
.rfind('/');
328 if (pos
!= string::npos
) {
329 ccmd
= ccmd
.substr(pos
+ 1);
335 for (unsigned int i
= 0; i
< files
.size(); i
++) {
336 string
&filename
= files
[i
];
338 FILE * f
= fopen(filename
.c_str(), "r");
341 FileReader::readString(f
, cmdline
);
345 for (unsigned int i
= 0; i
< cmdline
.size(); i
++) {
346 char ch
= cmdline
[i
];
347 converted
+= ch
? ch
: ' ';
351 size_t pos
= converted
.find('\0');
352 if (pos
!= string::npos
) {
353 converted
= converted
.substr(0, pos
);
355 pos
= converted
.rfind('/');
356 if (pos
!= string::npos
) {
357 converted
= converted
.substr(pos
+ 1);
361 if (converted
== ccmd
&& filename
.find("/proc/") == 0) {
362 long long pid
= atoll(filename
.c_str() + strlen("/proc/"));
371 std::string
Process::GetCommandLine(pid_t pid
) {
372 string name
= "/proc/" + boost::lexical_cast
<string
>(pid
) + "/cmdline";
375 FILE * f
= fopen(name
.c_str(), "r");
377 FileReader::readString(f
, cmdline
);
382 for (unsigned int i
= 0; i
< cmdline
.size(); i
++) {
383 char ch
= cmdline
[i
];
384 converted
+= ch
? ch
: ' ';
389 bool Process::CommandStartsWith(pid_t pid
, const std::string
&cmd
) {
391 std::string cmdline
= GetCommandLine(pid
);
392 if (cmdline
.length() >= cmd
.length() &&
393 cmdline
.substr(0, cmd
.length()) == cmd
) {
400 bool Process::IsUnderGDB() {
401 return CommandStartsWith(GetParentProcessId(), "gdb ");
404 int Process::GetProcessRSS(pid_t pid
) {
405 string name
= "/proc/" + boost::lexical_cast
<string
>(pid
) + "/status";
408 FILE * f
= fopen(name
.c_str(), "r");
410 FileReader::readString(f
, status
);
414 vector
<string
> lines
;
415 Util::split('\n', status
.c_str(), lines
, true);
416 for (unsigned int i
= 0; i
< lines
.size(); i
++) {
417 string
&line
= lines
[i
];
418 if (line
.find("VmRSS:") == 0) {
419 for (unsigned int j
= strlen("VmRSS:"); j
< line
.size(); j
++) {
420 if (line
[j
] != ' ') {
421 long long mem
= atoll(line
.c_str() + j
);
431 int Process::GetCPUCount() {
432 return sysconf(_SC_NPROCESSORS_ONLN
);
435 size_t Process::GetCodeFootprint(pid_t pid
) {
436 // /proc/<pid>/statm reports the following whitespace-separated values (in
437 // terms of page counts):
438 // size total program size
439 // resident resident set size
440 // share shared pages
442 // lib library (unused in Linux 2.6)
444 // dt dirty pages (unused in Linux 2.6)
446 // Return (share + text), under the assumption that share consists only of
448 string name
= "/proc/" + boost::lexical_cast
<string
>(pid
) + "/statm";
451 FILE * f
= fopen(name
.c_str(), "r");
453 FileReader::readString(f
, statm
);
457 size_t pageSize
= size_t(sysconf(_SC_PAGESIZE
));
458 size_t pos0
, pos1
= 0;
459 #define STATM_FIELD_NEXT() do { \
461 pos1 = statm.find(" ", pos0) + 1; \
463 #define STATM_FIELD_READ(name) \
464 STATM_FIELD_NEXT(); \
465 size_t name = strtoull(statm.substr(pos0, pos1-pos0).c_str(), \
466 nullptr, 0) * pageSize;
467 STATM_FIELD_NEXT(); // size.
468 STATM_FIELD_NEXT(); // resident.
469 STATM_FIELD_READ(share
);
470 STATM_FIELD_READ(text
);
471 #undef STATM_FIELD_NEXT
472 #undef STATM_FIELD_READ
478 static __inline
void do_cpuid(u_int ax
, u_int
*p
) {
479 asm volatile ("cpuid"
480 : "=a" (p
[0]), "=b" (p
[1]), "=c" (p
[2]), "=d" (p
[3])
485 std::string
Process::GetCPUModel() {
491 //uint32_t cpu_high = regs[0];
492 ((uint32_t *)&cpu_vendor
)[0] = regs
[1];
493 ((uint32_t *)&cpu_vendor
)[1] = regs
[3];
494 ((uint32_t *)&cpu_vendor
)[2] = regs
[2];
495 cpu_vendor
[12] = '\0';
497 uint32_t cpu_exthigh
= 0;
498 if (strcmp(cpu_vendor
, "GenuineIntel") == 0 ||
499 strcmp(cpu_vendor
, "AuthenticAMD") == 0) {
500 do_cpuid(0x80000000, regs
);
501 cpu_exthigh
= regs
[0];
504 char cpu_brand
[3 * sizeof(regs
) + 1];
505 char *brand
= cpu_brand
;
506 if (cpu_exthigh
>= 0x80000004) {
507 for (u_int i
= 0x80000002; i
< 0x80000005; i
++) {
509 memcpy(brand
, regs
, sizeof(regs
));
510 brand
+= sizeof(regs
);
514 assert(brand
- cpu_brand
< sizeof(cpu_brand
));
518 // On non-x64, fall back to calling uname
519 std::string model
= "Unknown ";
520 struct utsname uname_buf
;
522 model
.append(uname_buf
.machine
);
528 ///////////////////////////////////////////////////////////////////////////////
530 std::string
Process::GetAppName() {
531 const char* progname
= getenv("_");
532 if (!progname
|| !*progname
) {
533 progname
= "unknown program";
538 std::string
Process::GetAppVersion() {
540 #undefine HPHP_VERSION
542 #define HPHP_VERSION(v) return #v;
543 #include "../version"
546 std::string
Process::GetHostName() {
548 hostbuf
[0] = '\0'; // for cleaner valgrind output when gethostname() fails
549 gethostname(hostbuf
, 127);
554 std::string
Process::GetCurrentUser() {
555 const char *name
= getenv("LOGNAME");
559 passwd
*pwd
= getpwuid(geteuid());
560 if (pwd
&& pwd
->pw_name
) {
566 std::string
Process::GetCurrentDirectory() {
568 memset(buf
, 0, PATH_MAX
);
569 return getcwd(buf
, PATH_MAX
);
572 std::string
Process::GetHomeDirectory() {
575 const char *home
= getenv("HOME");
579 passwd
*pwd
= getpwent();
580 if (pwd
&& pwd
->pw_dir
) {
585 if (ret
.empty() || ret
[ret
.size() - 1] != '/') {
591 ///////////////////////////////////////////////////////////////////////////////