Completed
Push — master ( d794c4...a065cf )
by Vasily
05:01
created

ShellCommand::getStatus()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 2
eloc 2
nc 2
nop 0
1
<?php
2
namespace PHPDaemon\Core;
3
4
use PHPDaemon\Core\CallbackWrapper;
5
use PHPDaemon\Core\Daemon;
6
use PHPDaemon\FS\File;
7
use PHPDaemon\Network\IOStream;
8
9
/**
10
 * Process
11
 * @package PHPDaemon\Core
12
 * @author  Vasily Zorin <[email protected]>
13
 */
14
class ShellCommand extends IOStream {
15
16
	protected $finishWrite;
17
18
	/**
19
	 * @var string Command string
20
	 */
21
	protected $cmd;
22
23
	/**
24
	 * @var string Executable path
25
	 */
26
	public $binPath;
27
28
	/**
29
	 * @var array Opened pipes
30
	 */
31
	protected $pipes;
32
33
	/**
34
	 * @var resource Process descriptor
35
	 */
36
	protected $pd;
37
38
	/**
39
	 * @var resource FD write
40
	 */
41
	protected $fdWrite;
42
43
	/**
44
	 * @var boolean Output errors?
45
	 */
46
	protected $outputErrors = true;
47
48
	/**
49
	 * @var string SUID
50
	 */
51
	public $setUser;
52
53
	/**
54
	 * @var string SGID
55
	 */
56
	public $setGroup;
57
58
	/**
59
	 * @var string Chroot
60
	 */
61
	public $chroot = '/';
62
63
	/**
64
	 * @var array Hash of environment's variables
65
	 */
66
	protected $env = []; // 
67
68
	/**
69
	 * @var string Chdir
70
	 */
71
	public $cwd;
72
73
	/**
74
	 * @var string Path to error logfile
75
	 */
76
	protected $errlogfile = null;
77
78
	/**
79
	 * @var array Array of arguments
80
	 */
81
	protected $args;
82
83
	/**
84
	 * @var integer Process priority
85
	 */
86
	protected $nice;
87
88
	/**
89
	 * @var \EventBufferEvent
90
	 */
91
	protected $bevWrite;
92
93
	/**
94
	 * @var \EventBufferEvent
95
	 */
96
	protected $bevErr;
97
98
	/**
99
	 * @var boolean Got EOF?
100
	 */
101
	protected $EOF = false;
102
103
	/**
104
	 * Get command string
105
	 * @return string
106
	 */
107
	public function getCmd() {
108
		return $this->cmd;
109
	}
110
111
	/**
112
	 * Set group
113
	 * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be ShellCommand?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
114
	 */
115
	public function setGroup($val) {
116
		$this->setGroup = $val;
117
		return $this;
118
	}
119
120
	/**
121
	 * Set cwd
122
	 * @param  string $dir
123
	 * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be ShellCommand?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
124
	 */
125
	public function setCwd($dir) {
126
		$this->cwd = $dir;
127
		return $this;
128
	}
129
130
	/**
131
	 * Set group
132
	 * @param  string $val
133
	 * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be ShellCommand?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
134
	 */
135
	public function setUser($val) {
136
		$this->setUser = $val;
137
		return $this;
138
	}
139
140
	/**
141
	 * Set chroot
142
	 * @param  string $dir
143
	 * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be ShellCommand?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
144
	 */
145
	public function setChroot($dir) {
146
		$this->chroot = $dir;
147
		return $this;
148
	}
149
150
	/**
151
	 * Execute
152
	 * @param  string   $binPath Binpath
0 ignored issues
show
Documentation introduced by
Should the type for parameter $binPath not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
153
	 * @param  callable $cb 	 Callback
0 ignored issues
show
Documentation introduced by
Should the type for parameter $cb not be callable|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
154
	 * @param  array    $args    Optional. Arguments
0 ignored issues
show
Documentation introduced by
Should the type for parameter $args not be array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
155
	 * @param  array    $env     Optional. Hash of environment's variables
0 ignored issues
show
Documentation introduced by
Should the type for parameter $env not be array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
156
	 */
157
	public static function exec($binPath = null, $cb = null, $args = null, $env = null) {
158
		$o = new static;
159
		$data = '';
160
		$o->bind('read', function($o) use (&$data, $o) {
0 ignored issues
show
Bug Best Practice introduced by
The parameter name $o conflicts with one of the imported variables.
Loading history...
161
			$data .= $o->readUnlimited();
162
		});
163
		$o->bind('eof', function($o) use (&$data, $cb) {
164
			call_user_func($cb, $o, $data);
165
			$o->close();
166
		});
167
		$o->execute($binPath, $args, $env);
168
	}
169
170
171
	/**
172
	 * Sets fd
173
	 * @param  resource          $fd File descriptor
174
	 * @param  \EventBufferEvent $bev
0 ignored issues
show
Documentation introduced by
Should the type for parameter $bev not be \EventBufferEvent|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
175
	 * @return void
176
	 */
177
	public function setFd($fd, $bev = null) {
178
		$this->fd = $fd;
179
		if ($fd === false) {
180
			$this->finish();
181
			return;
182
		}
183
		$this->fdWrite = $this->pipes[0];
184
		$flags         = !is_resource($this->fd) ? \EventBufferEvent::OPT_CLOSE_ON_FREE : 0;
185
		$flags |= \EventBufferEvent::OPT_DEFER_CALLBACKS; /* buggy option */
186
		$this->bev      = new \EventBufferEvent(Daemon::$process->eventBase, $this->fd, 0, [$this, 'onReadEv'], null, [$this, 'onStateEv']);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 134 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
187
		$this->bevWrite = new \EventBufferEvent(Daemon::$process->eventBase, $this->fdWrite, 0, null, [$this, 'onWriteEv'], null);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 124 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
188
		if (!$this->bev || !$this->bevWrite) {
189
			$this->finish();
190
			return;
191
		}
192
		if ($this->priority !== null) {
193
			$this->bev->priority = $this->priority;
194
		}
195
		if ($this->timeout !== null) {
196
			$this->setTimeout($this->timeout);
197
		}
198 View Code Duplication
		if (!$this->bev->enable(\Event::READ | \Event::TIMEOUT | \Event::PERSIST)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
199
			$this->finish();
200
			return;
201
		}
202 View Code Duplication
		if (!$this->bevWrite->enable(\Event::WRITE | \Event::TIMEOUT | \Event::PERSIST)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
203
			$this->finish();
204
			return;
205
		}
206
		$this->bev->setWatermark(\Event::READ, $this->lowMark, $this->highMark);
207
208
		init:
209
		if (!$this->inited) {
210
			$this->inited = true;
211
			$this->init();
212
		}
213
	}
214
215
	/**
216
	 * Sets an array of arguments
217
	 * @param  array Arguments
218
	 * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be ShellCommand?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
219
	 */
220
	public function setArgs($args = NULL) {
221
		$this->args = $args;
222
223
		return $this;
224
	}
225
226
	/**
227
	 * Set a hash of environment's variables
228
	 * @param  array Hash of environment's variables
229
	 * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be ShellCommand?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
230
	 */
231
	public function setEnv($env = NULL) {
232
		$this->env = $env;
233
234
		return $this;
235
	}
236
237
	/**
238
	 * Called when got EOF
239
	 * @return void
240
	 */
241
	public function onEofEvent() {
242
		if ($this->EOF) {
243
			return;
244
		}
245
		$this->EOF = true;
246
247
		$this->event('eof');
248
	}
249
250
	/**
251
	 * Set priority
252
	 * @param  integer $nice Priority
0 ignored issues
show
Documentation introduced by
Should the type for parameter $nice not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
253
	 * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be ShellCommand?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
254
	 */
255
	public function nice($nice = NULL) {
256
		$this->nice = $nice;
257
258
		return $this;
259
	}
260
261
	/**
262
	 * Called when new data received
263
	 * @return this|null
0 ignored issues
show
Documentation introduced by
Should the return type not be ShellCommand|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
264
	 */
265
	protected function onRead() {
266
		if (func_num_args() === 1) {
267
			$this->onRead = func_get_arg(0);
0 ignored issues
show
Documentation introduced by
The property onRead does not exist on object<PHPDaemon\Core\ShellCommand>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
268
			return $this;
269
		}
270
		$this->event('read');
271
	}
272
273
	/**
274
	 * Build arguments string from associative/enumerated array (may be mixed)
275
	 * @param  array $args
276
	 * @return string
277
	 */
278
	public static function buildArgs($args) {
279
		if (!is_array($args)) {
280
			return '';
281
		}
282
		$ret = '';
283
		foreach ($args as $k => $v) {
284
			if (!is_int($v) && ($v !== null)) {
285
				$v = escapeshellarg($v);
286
			}
287
			if (is_int($k)) {
288
				$ret .= ' ' . $v;
289
			} else {
290
				if ($k{0} !== '-') {
291
					$ret .= ' --' . $k . ($v !== null ? '=' . $v : '');
292
				} else {
293
					$ret .= ' ' . $k . ($v !== null ? '=' . $v : '');
294
				}
295
			}
296
		}
297
		return $ret;
298
	}
299
300
	/**
301
	 * Execute
302
	 * @param  string $binPath Optional. Binpath
0 ignored issues
show
Documentation introduced by
Should the type for parameter $binPath not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
303
	 * @param  array  $args    Optional. Arguments
0 ignored issues
show
Documentation introduced by
Should the type for parameter $args not be array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
304
	 * @param  array  $env     Optional. Hash of environment's variables
0 ignored issues
show
Documentation introduced by
Should the type for parameter $env not be array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
305
	 * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be ShellCommand?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
306
	 */
307
	public function execute($binPath = NULL, $args = NULL, $env = NULL) {
308
		if ($binPath !== NULL) {
309
			$this->binPath = $binPath;
310
		}
311
312
		if ($env !== NULL) {
313
			$this->env = $env;
314
		}
315
316
		if ($args !== NULL) {
317
			$this->args = $args;
318
		}
319
		$this->cmd = $this->binPath . static::buildArgs($this->args) . ($this->outputErrors ? ' 2>&1' : '');
320
321
		if (
322
				isset($this->setUser)
323
				|| isset($this->setGroup)
324
		) {
325
			if (
326
					isset($this->setUser)
327
					&& isset($this->setGroup)
328
					&& ($this->setUser !== $this->setGroup)
329
			) {
330
				$this->cmd = 'sudo -g ' . escapeshellarg($this->setGroup) . '  -u ' . escapeshellarg($this->setUser) . ' ' . $this->cmd;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 124 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
331
			}
332
			else {
333
				$this->cmd = 'su ' . escapeshellarg($this->setGroup) . ' -c ' . escapeshellarg($this->cmd);
334
			}
335
		}
336
337
		if ($this->chroot !== '/') {
338
			$this->cmd = 'chroot ' . escapeshellarg($this->chroot) . ' ' . $this->cmd;
339
		}
340
341
		if ($this->nice !== NULL) {
342
			$this->cmd = 'nice -n ' . ((int)$this->nice) . ' ' . $this->cmd;
343
		}
344
345
		$pipesDescr = [
346
			0 => ['pipe', 'r'], // stdin is a pipe that the child will read from
347
			1 => ['pipe', 'w'] // stdout is a pipe that the child will write to
348
		];
349
350
		if (
351
				($this->errlogfile !== NULL)
352
				&& !$this->outputErrors
353
		) {
354
			$pipesDescr[2] = ['file', $this->errlogfile, 'a']; // @TODO: refactoring
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
355
		}
356
357
		$this->pd = proc_open($this->cmd, $pipesDescr, $this->pipes, $this->cwd, $this->env);
358
		if ($this->pd) {
359
			$this->setFd($this->pipes[1]);
360
		}
361
362
		return $this;
363
	}
364
365
	/**
366
	 * Finish write stream
367
	 * @return boolean
368
	 */
369
	public function finishWrite() {
370
		if (!$this->writing) {
371
			$this->closeWrite();
372
		}
373
374
		$this->finishWrite = true;
375
376
		return true;
377
	}
378
379
	/**
380
	 * Close the process
381
	 * @return void
382
	 */
383
	public function close() {
384
		parent::close();
385
		$this->closeWrite();
386
		if (is_resource($this->pd)) {
387
			proc_close($this->pd);
388
		}
389
	}
390
391
	/**
392
	 * Called when stream is finished
393
	 */
394
	public function onFinish() {
395
		$this->onEofEvent();
396
	}
397
398
	/**
399
	 * Close write stream
400
	 * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be ShellCommand?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
401
	 */
402
	public function closeWrite() {
403
		if ($this->bevWrite) {
404
			if (isset($this->bevWrite)) {
405
				$this->bevWrite->free();
406
			}
407
			$this->bevWrite = null;
408
		}
409
410
		if ($this->fdWrite) {
411
			fclose($this->fdWrite);
412
			$this->fdWrite = null;
413
		}
414
415
		return $this;
416
	}
417
418
	/**
419
	 * Got EOF?
420
	 * @return boolean
421
	 */
422
	public function eof() {
423
		return $this->EOF;
424
425
	}
426
427
	/**
428
	 * Send data to the connection. Note that it just writes to buffer that flushes at every baseloop
429
	 * @param  string $data Data to send
430
	 * @return boolean Success
431
	 */
432 View Code Duplication
	public function write($data) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
433
		if (!$this->alive) {
434
			Daemon::log('Attempt to write to dead IOStream (' . get_class($this) . ')');
435
			return false;
436
		}
437
		if (!isset($this->bevWrite)) {
438
			return false;
439
		}
440
		if (!strlen($data)) {
441
			return true;
442
		}
443
		$this->writing   = true;
444
		Daemon::$noError = true;
445
		if (!$this->bevWrite->write($data) || !Daemon::$noError) {
446
			$this->close();
447
			return false;
448
		}
449
		return true;
450
	}
451
452
	/**
453
	 * Send data and appending \n to connection. Note that it just writes to buffer flushed at every baseloop
454
	 * @param  string Data to send
455
	 * @return boolean Success
456
	 */
457 View Code Duplication
	public function writeln($data) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
458
		if (!$this->alive) {
459
			Daemon::log('Attempt to write to dead IOStream (' . get_class($this) . ')');
460
			return false;
461
		}
462
		if (!isset($this->bevWrite)) {
463
			return false;
464
		}
465
		if (!strlen($data) && !strlen($this->EOL)) {
466
			return true;
467
		}
468
		$this->writing = true;
469
		$this->bevWrite->write($data);
470
		$this->bevWrite->write($this->EOL);
471
		return true;
472
	}
473
474
	/**
475
	 * Sets callback which will be called once when got EOF
476
	 * @param  callable $cb
0 ignored issues
show
Documentation introduced by
Should the type for parameter $cb not be callable|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
477
	 * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be ShellCommand?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
478
	 */
479
	public function onEOF($cb = NULL) {
480
		$this->onEOF = CallbackWrapper::wrap($cb);
0 ignored issues
show
Documentation introduced by
The property onEOF does not exist on object<PHPDaemon\Core\ShellCommand>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Bug introduced by
It seems like $cb defined by parameter $cb on line 479 can also be of type null; however, PHPDaemon\Core\CallbackWrapper::wrap() does only seem to accept callable, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
481
		return $this;
482
	}
483
484
	public function getStatus()
485
	{
486
		return !empty($this->pd) ? proc_get_status($this->pd) : null;
487
	}
488
}
489