3ea63d00438f2887c8f69ce9c5ee1e3e008adf3f
[pharext/pharext] / src / pharext / ExecCmd.php
1 <?php
2
3 namespace pharext;
4
5 /**
6 * Execute system command
7 */
8 class ExecCmd
9 {
10 /**
11 * Sudo command, if the cmd needs escalated privileges
12 * @var string
13 */
14 private $sudo;
15
16 /**
17 * Executable of the cmd
18 * @var string
19 */
20 private $command;
21
22 /**
23 * Passthrough cmd output
24 * @var bool
25 */
26 private $verbose;
27
28 /**
29 * Output of cmd run
30 * @var string
31 */
32 private $output;
33
34 /**
35 * Return code of cmd run
36 * @var int
37 */
38 private $status;
39
40 /**
41 * @param string $command
42 * @param bool verbose
43 */
44 public function __construct($command, $verbose = false) {
45 $this->command = $command;
46 $this->verbose = $verbose;
47 }
48
49 /**
50 * (Re-)set sudo command
51 * @param string $sudo
52 */
53 public function setSu($sudo = false) {
54 $this->sudo = $sudo;
55 }
56
57 /**
58 * Execute a program with escalated privileges handling interactive password prompt
59 * @param string $command
60 * @param string $output
61 * @param int $status
62 */
63 private function suExec($command, &$output, &$status) {
64 if (!($proc = proc_open($command, [STDIN,["pipe","w"],["pipe","w"]], $pipes))) {
65 $status = -1;
66 throw new Exception("Failed to run {$command}");
67 }
68 $stdout = $pipes[1];
69 $passwd = 0;
70 while (!feof($stdout)) {
71 $R = [$stdout]; $W = []; $E = [];
72 if (!stream_select($R, $W, $E, null)) {
73 continue;
74 }
75 $data = fread($stdout, 0x1000);
76 /* only check a few times */
77 if ($passwd++ < 10) {
78 if (stristr($data, "password")) {
79 printf("\n%s", $data);
80 }
81 }
82 $output .= $data;
83 }
84 $status = proc_close($proc);
85 }
86
87 /**
88 * Run the command
89 * @param array $args
90 * @throws \pharext\Exception
91 */
92 public function run(array $args = null) {
93 $exec = escapeshellcmd($this->command);
94 if ($args) {
95 $exec .= " ". implode(" ", array_map("escapeshellarg", (array) $args));
96 }
97
98 if ($this->sudo) {
99 $this->suExec(sprintf($this->sudo." 2>&1", $exec), $this->output, $this->status);
100 } elseif ($this->verbose) {
101 ob_start(function($s) {
102 $this->output .= $s;
103 return $s;
104 }, 1);
105 passthru($exec, $this->status);
106 ob_end_flush();
107 } else {
108 exec($exec ." 2>&1", $output, $this->status);
109 $this->output = implode("\n", $output);
110 }
111
112 if ($this->status) {
113 throw new Exception("Command {$this->command} failed ({$this->status})");
114 }
115 }
116
117 /**
118 * Retrieve exit code of cmd run
119 * @return int
120 */
121 public function getStatus() {
122 return $this->status;
123 }
124
125 /**
126 * Retrieve output of cmd run
127 * @return string
128 */
129 public function getOutput() {
130 return $this->output;
131 }
132 }