more refactoring; now the package hook starts to make sense
[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\n", $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 $c = 0;
116 static $s = ["\\","|","/","-"];
117
118 $this->output .= $string;
119
120 return $flags & PHP_OUTPUT_HANDLER_FINAL
121 ? " \r"
122 : sprintf(" %s\r", $s[$c++ % count($s)]);
123 }
124
125 /**
126 * Run the command
127 * @param array $args
128 * @return \pharext\ExecCmd self
129 * @throws \pharext\Exception
130 */
131 public function run(array $args = null) {
132 $exec = escapeshellcmd($this->command);
133 if ($args) {
134 $exec .= " ". implode(" ", array_map("escapeshellarg", (array) $args));
135 }
136
137 if ($this->sudo) {
138 $this->suExec(sprintf($this->sudo." 2>&1", $exec), $this->verbose);
139 } elseif ($this->verbose) {
140 ob_start(function($s) {
141 $this->output .= $s;
142 return $s;
143 }, 1);
144 passthru($exec, $this->status);
145 ob_end_flush();
146 } elseif ($this->verbose !== false /* !quiet */) {
147 ob_start([$this, "progress"], 1);
148 passthru($exec . " 2>&1", $this->status);
149 ob_end_flush();
150 } else {
151 exec($exec ." 2>&1", $output, $this->status);
152 $this->output = implode("\n", $output);
153 }
154
155 if ($this->status) {
156 throw new Exception("Command {$exec} failed ({$this->status})");
157 }
158
159 return $this;
160 }
161
162 /**
163 * Retrieve exit code of cmd run
164 * @return int
165 */
166 public function getStatus() {
167 return $this->status;
168 }
169
170 /**
171 * Retrieve output of cmd run
172 * @return string
173 */
174 public function getOutput() {
175 return $this->output;
176 }
177 }