consistent verbosity
[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 bool $verbose
61 * @return int exit status
62 */
63 private function suExec($command, $verbose = null) {
64 if (!($proc = proc_open($command, [STDIN,["pipe","w"],["pipe","w"]], $pipes))) {
65 $this->status = -1;
66 throw new Exception("Failed to run {$command}");
67 }
68
69 $stdout = $pipes[1];
70 $passwd = 0;
71 $checks = 10;
72
73 while (!feof($stdout)) {
74 $R = [$stdout]; $W = []; $E = [];
75 if (!stream_select($R, $W, $E, null)) {
76 continue;
77 }
78 $data = fread($stdout, 0x1000);
79 /* only check a few times */
80 if ($passwd < $checks) {
81 $passwd++;
82 if (stristr($data, "password")) {
83 $passwd = $checks + 1;
84 printf("\n%s", $data);
85 continue;
86 }
87 } elseif ($passwd > $checks) {
88 /* new line after pw entry */
89 printf("\n");
90 $passwd = $checks;
91 }
92
93 if ($verbose === null) {
94 print $this->progress($data, 0);
95 } else {
96 if ($verbose) {
97 printf("%s", $data);
98 }
99 $this->output .= $data;
100 }
101 }
102 if ($verbose === null) {
103 $this->progress("", PHP_OUTPUT_HANDLER_FINAL);
104 }
105 return $this->status = proc_close($proc);
106 }
107
108 /**
109 * Output handler that displays some progress while soaking output
110 * @param string $string
111 * @param int $flags
112 * @return string
113 */
114 private function progress($string, $flags) {
115 static $counter = 0;
116 static $symbols = ["\\","|","/","-"];
117
118 $this->output .= $string;
119
120 if (false !== strpos($string, "\n")) {
121 ++$counter;
122 }
123
124 return $flags & PHP_OUTPUT_HANDLER_FINAL
125 ? " \r"
126 : sprintf(" %s\r", $symbols[$counter % 4]);
127 }
128
129 /**
130 * Run the command
131 * @param array $args
132 * @return \pharext\ExecCmd self
133 * @throws \pharext\Exception
134 */
135 public function run(array $args = null) {
136 $exec = escapeshellcmd($this->command);
137 if ($args) {
138 $exec .= " ". implode(" ", array_map("escapeshellarg", (array) $args));
139 }
140
141 if ($this->sudo) {
142 $this->suExec(sprintf($this->sudo." 2>&1", $exec), $this->verbose);
143 } elseif ($this->verbose) {
144 ob_start(function($s) {
145 $this->output .= $s;
146 return $s;
147 }, 1);
148 passthru($exec, $this->status);
149 ob_end_flush();
150 } elseif ($this->verbose !== false /* !quiet */) {
151 ob_start([$this, "progress"], 1);
152 passthru($exec . " 2>&1", $this->status);
153 ob_end_flush();
154 } else {
155 exec($exec ." 2>&1", $output, $this->status);
156 $this->output = implode("\n", $output);
157 }
158
159 if ($this->status) {
160 throw new Exception("Command {$exec} failed ({$this->status})");
161 }
162
163 return $this;
164 }
165
166 /**
167 * Retrieve exit code of cmd run
168 * @return int
169 */
170 public function getStatus() {
171 return $this->status;
172 }
173
174 /**
175 * Retrieve output of cmd run
176 * @return string
177 */
178 public function getOutput() {
179 return $this->output;
180 }
181 }