make #includes consistent
[hiphop-php.git] / hphp / util / process.cpp
blob0461d94e3f97384312e6599ab3f9fe8cd27a9500
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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"
19 #include "util.h"
20 #include "hphp/util/logger.h"
21 #include "hphp/util/async_func.h"
22 #include "hphp/util/text_color.h"
24 #include <pwd.h>
26 namespace HPHP {
27 ///////////////////////////////////////////////////////////////////////////////
28 // helpers
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) {
33 if (f) fclose(f);
34 _exit(-1);
38 ///////////////////////////////////////////////////////////////////////////////
40 class FileReader {
41 public:
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) {
46 size_t nread = 0;
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);
54 private:
55 FilePtr m_f;
56 string &m_out;
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;
81 if (in && *in) {
82 fwrite(in, 1, strlen(in), sin.get());
86 char buffer[4096];
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) {
96 pollfd fds[2];
97 int n = 0;
98 if (fdout) {
99 fds[n].fd = fdout;
100 fds[n].events = POLLIN | POLLHUP;
101 n++;
103 if (fderr) {
104 fds[n].fd = fderr;
105 fds[n].events = POLLIN | POLLHUP;
106 n++;
109 n = poll(fds, n, -1);
110 if (n < 0) {
111 continue;
114 n = 0;
115 if (fdout) {
116 if (fds[n++].revents & (POLLIN | POLLHUP)) {
117 int e = read(fdout, buffer, sizeof buffer);
118 if (e <= 0) {
119 close(fdout);
120 fdout = 0;
121 } else {
122 if (color && s_stdout_color) {
123 out.append(s_stdout_color);
124 out.append(buffer, e);
125 out.append(ANSI_COLOR_END);
126 } else {
127 out.append(buffer, e);
133 if (fderr) {
134 if (fds[n++].revents & (POLLIN | POLLHUP)) {
135 int e = read(fderr, buffer, sizeof buffer);
136 if (e <= 0) {
137 close(fderr);
138 fderr = 0;
139 } else if (err) {
140 if (color && s_stdout_color) {
141 err->append(s_stderr_color);
142 err->append(buffer, e);
143 err->append(ANSI_COLOR_END);
144 } else {
145 err->append(buffer, e);
152 int status;
153 bool ret = false;
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);
160 while (*argv) {
161 Logger::Verbose(" arg: `%s'\n", *argv);
162 argv++;
164 } else {
165 ret = true;
167 } else {
168 Logger::Verbose("Non-normal exit\n");
169 if (WIFSIGNALED(status)) {
170 Logger::Verbose(" signaled with %d\n", WTERMSIG(status));
173 return ret;
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);
180 if (argvs.empty()) {
181 return -1;
184 int pid = fork();
185 if (pid < 0) {
186 Logger::Error("Unable to fork: %d %s", errno,
187 Util::safe_strerror(errno).c_str());
188 return 0;
190 if (pid == 0) {
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());
205 _exit(-1);
207 int status = -1;
208 wait(&status);
209 return status;
212 int Process::Exec(const char *path, const char *argv[], int *fdin, int *fdout,
213 int *fderr) {
214 CPipe pipein, pipeout, pipeerr;
215 if (!pipein.open() || !pipeout.open() || !pipeerr.open()) {
216 return 0;
219 int pid = fork();
220 if (pid < 0) {
221 Logger::Error("Unable to fork: %d %s", errno,
222 Util::safe_strerror(errno).c_str());
223 return 0;
225 if (pid == 0) {
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
229 * "bg" the program.
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);
241 _exit(-1);
243 if (fdout) *fdout = pipeout.detachOut();
244 if (fderr) *fderr = pipeerr.detachOut();
245 if (fdin) *fdin = pipein.detachIn();
246 return pid;
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" */) {
256 pid_t pid, sid;
258 /* already a daemon */
259 if (getppid() == 1) return;
261 /* Fork off the parent process */
262 pid = fork();
263 if (pid < 0) {
264 exit(EXIT_FAILURE);
266 /* If we got a good PID, then we can exit the parent process. */
267 if (pid > 0) {
268 exit(EXIT_SUCCESS);
271 /* At this point we are executing as the child process */
273 /* Change the file mode mask */
274 umask(0);
276 /* Create a new SID for the child process */
277 sid = setsid();
278 if (sid < 0) {
279 exit(EXIT_FAILURE);
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) {
285 exit(EXIT_FAILURE);
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);
292 } else {
293 if (!freopen("/dev/null", "w", stdout)) exit(EXIT_FAILURE);
295 if (stderrFile && *stderrFile) {
296 if (!freopen(stderrFile, "a", stderr)) exit(EXIT_FAILURE);
297 } else {
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};
315 string out;
316 Exec("find", argv, nullptr, out);
318 vector<string> files;
319 Util::split('\n', out.c_str(), files, true);
321 string ccmd = cmd;
322 if (!matchAll) {
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);
331 } else {
332 ccmd += " ";
335 for (unsigned int i = 0; i < files.size(); i++) {
336 string &filename = files[i];
338 FILE * f = fopen(filename.c_str(), "r");
339 if (f) {
340 string cmdline;
341 FileReader::readString(f, cmdline);
342 fclose(f);
343 string converted;
344 if (matchAll) {
345 for (unsigned int i = 0; i < cmdline.size(); i++) {
346 char ch = cmdline[i];
347 converted += ch ? ch : ' ';
349 } else {
350 converted = cmdline;
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/"));
363 if (pid) {
364 pids.push_back(pid);
371 std::string Process::GetCommandLine(pid_t pid) {
372 string name = "/proc/" + boost::lexical_cast<string>(pid) + "/cmdline";
374 string cmdline;
375 FILE * f = fopen(name.c_str(), "r");
376 if (f) {
377 FileReader::readString(f, cmdline);
378 fclose(f);
381 string converted;
382 for (unsigned int i = 0; i < cmdline.size(); i++) {
383 char ch = cmdline[i];
384 converted += ch ? ch : ' ';
386 return converted;
389 bool Process::CommandStartsWith(pid_t pid, const std::string &cmd) {
390 if (!cmd.empty()) {
391 std::string cmdline = GetCommandLine(pid);
392 if (cmdline.length() >= cmd.length() &&
393 cmdline.substr(0, cmd.length()) == cmd) {
394 return true;
397 return false;
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";
407 string status;
408 FILE * f = fopen(name.c_str(), "r");
409 if (f) {
410 FileReader::readString(f, status);
411 fclose(f);
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);
422 return mem/1024;
428 return 0;
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
441 // text text (code)
442 // lib library (unused in Linux 2.6)
443 // data data/stack
444 // dt dirty pages (unused in Linux 2.6)
446 // Return (share + text), under the assumption that share consists only of
447 // shared libraries.
448 string name = "/proc/" + boost::lexical_cast<string>(pid) + "/statm";
450 string statm;
451 FILE * f = fopen(name.c_str(), "r");
452 if (f) {
453 FileReader::readString(f, statm);
454 fclose(f);
457 size_t pageSize = size_t(sysconf(_SC_PAGESIZE));
458 size_t pos0, pos1 = 0;
459 #define STATM_FIELD_NEXT() do { \
460 pos0 = pos1; \
461 pos1 = statm.find(" ", pos0) + 1; \
462 } while (0)
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
473 return share + text;
477 #ifdef __x86_64__
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])
481 : "0" (ax));
483 #endif
485 std::string Process::GetCPUModel() {
486 #ifdef __x86_64__
487 uint32_t regs[4];
488 do_cpuid(0, regs);
490 char cpu_vendor[13];
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++) {
508 do_cpuid(i, regs);
509 memcpy(brand, regs, sizeof(regs));
510 brand += sizeof(regs);
513 *brand = '\0';
514 assert(brand - cpu_brand < sizeof(cpu_brand));
515 return cpu_brand;
517 #else
518 // On non-x64, fall back to calling uname
519 std::string model = "Unknown ";
520 struct utsname uname_buf;
521 uname(&uname_buf);
522 model.append(uname_buf.machine);
523 return model;
525 #endif // __x86_64__
528 ///////////////////////////////////////////////////////////////////////////////
530 std::string Process::GetAppName() {
531 const char* progname = getenv("_");
532 if (!progname || !*progname) {
533 progname = "unknown program";
535 return progname;
538 std::string Process::GetAppVersion() {
539 #ifdef HPHP_VERSION
540 #undefine HPHP_VERSION
541 #endif
542 #define HPHP_VERSION(v) return #v;
543 #include "../version"
546 std::string Process::GetHostName() {
547 char hostbuf[128];
548 hostbuf[0] = '\0'; // for cleaner valgrind output when gethostname() fails
549 gethostname(hostbuf, 127);
550 hostbuf[127] = '\0';
551 return hostbuf;
554 std::string Process::GetCurrentUser() {
555 const char *name = getenv("LOGNAME");
556 if (name && *name) {
557 return name;
559 passwd *pwd = getpwuid(geteuid());
560 if (pwd && pwd->pw_name) {
561 return pwd->pw_name;
563 return "";
566 std::string Process::GetCurrentDirectory() {
567 char buf[PATH_MAX];
568 memset(buf, 0, PATH_MAX);
569 return getcwd(buf, PATH_MAX);
572 std::string Process::GetHomeDirectory() {
573 string ret;
575 const char *home = getenv("HOME");
576 if (home && *home) {
577 ret = home;
578 } else {
579 passwd *pwd = getpwent();
580 if (pwd && pwd->pw_dir) {
581 ret = pwd->pw_dir;
585 if (ret.empty() || ret[ret.size() - 1] != '/') {
586 ret += '/';
588 return ret;
591 ///////////////////////////////////////////////////////////////////////////////