Completed
Push — master ( 64e415...6dc953 )
by Vasily
02:00
created

ShellCommand::setErrorLog()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace PHPDaemon\Core;
4
5
use PHPDaemon\Network\IOStream;
6
7
/**
8
 * Process
9
 *
10
 * @package PHPDaemon\Core
11
 * @author  Vasily Zorin <[email protected]>
12
 */
13
class ShellCommand extends IOStream
14
{
15
16
    /**
17
     * @var string Executable path
18
     */
19
    public $binPath;
20
    /**
21
     * @var string SUID
22
     */
23
    public $setUser;
24
    /**
25
     * @var string SGID
26
     */
27
    public $setGroup;
28
    /**
29
     * @var string Chroot
30
     */
31
    public $chroot = '/';
32
    /**
33
     * @var string Chdir
34
     */
35
    public    $cwd;
36
    protected $finishWrite;
37
    /**
38
     * @var string Command string
39
     */
40
    protected $cmd;
41
    /**
42
     * @var array Opened pipes
43
     */
44
    protected $pipes;
45
    /**
46
     * @var resource Process descriptor
47
     */
48
    protected $pd;
49
    /**
50
     * @var resource FD write
51
     */
52
    protected $fdWrite;
53
    /**
54
     * @var boolean Output errors?
55
     */
56
    protected $outputErrors = true;
57
    /**
58
     * @var array Hash of environment's variables
59
     */
60
    protected $env = [];
61
    /**
62
     * @var string Path to error logfile
63
     */
64
    protected $errlogfile = null;
65
66
    /**
67
     * @var array Array of arguments
68
     */
69
    protected $args;
70
71
    /**
72
     * @var integer Process priority
73
     */
74
    protected $nice;
75
76
    /**
77
     * @var \EventBufferEvent
78
     */
79
    protected $bevWrite;
80
81
    /**
82
     * @var \EventBufferEvent
83
     */
84
    protected $bevErr;
85
86
    /**
87
     * @var boolean Got EOF?
88
     */
89
    protected $EOF = false;
90
91
    /**
92
     * @param string $path
93
     *
94
     * @return $this
95
     */
96
    public function setErrorLog (string $path): self
97
    {
98
        $this->errlogfile = $path;
99
100
        return $this;
101
    }
102
103
    /**
104
     * Execute
105
     *
106
     * @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...
107
     * @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...
108
     * @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...
109
     * @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...
110
     */
111
    public static function exec ($binPath = null, $cb = null, $args = null, $env = null)
112
    {
113
        $o    = new static;
114
        $data = '';
115
        $o->bind(
116
            'read',
117
            function ($o) use (&$data) {
118
                $data .= $o->readUnlimited();
119
            }
120
        );
121
        $o->bind(
122
            'eof',
123
            function ($o) use (&$data, $cb) {
124
                $cb($o, $data);
125
                $o->close();
126
            }
127
        );
128
        $o->execute($binPath, $args, $env);
129
    }
130
131
    /**
132
     * @param bool $bool
133
     */
134
    public function setOutputErrors (bool $bool): void
135
    {
136
        $this->outputErrors = $bool;
137
    }
138
139
    /**
140
     * Execute
141
     *
142
     * @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...
143
     * @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...
144
     * @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...
145
     *
146
     * @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...
147
     */
148
    public function execute ($binPath = null, $args = null, $env = null)
149
    {
150
        if ($binPath !== null) {
151
            $this->binPath = $binPath;
152
        }
153
154
        if ($env !== null) {
155
            $this->env = $env;
156
        }
157
158
        if ($args !== null) {
159
            $this->args = $args;
160
        }
161
        $this->cmd = $this->binPath . static::buildArgs($this->args) . ($this->outputErrors ? ' 2>&1' : '');
162
163
        if (isset($this->setUser) || isset($this->setGroup)) {
164
            if (isset($this->setUser) && isset($this->setGroup) && ($this->setUser !== $this->setGroup)) {
165
                $this->cmd = 'sudo -g ' . escapeshellarg($this->setGroup) . '  -u ' . escapeshellarg(
166
                        $this->setUser
167
                    ) . ' ' . $this->cmd;
168
            }
169
            else {
170
                $this->cmd = 'su ' . escapeshellarg($this->setGroup) . ' -c ' . escapeshellarg($this->cmd);
171
            }
172
        }
173
174
        if ($this->chroot !== '/') {
175
            $this->cmd = 'chroot ' . escapeshellarg($this->chroot) . ' ' . $this->cmd;
176
        }
177
178
        if ($this->nice !== null) {
179
            $this->cmd = 'nice -n ' . ((int)$this->nice) . ' ' . $this->cmd;
180
        }
181
182
        $pipesDescr = [
183
            0 => ['pipe', 'r'], // stdin is a pipe that the child will read from
184
            1 => ['pipe', 'w'] // stdout is a pipe that the child will write to
185
        ];
186
187
        if (($this->errlogfile !== null) && !$this->outputErrors) {
188
            $pipesDescr[2] = ['file', $this->errlogfile, 'a']; // @TODO: refactoring
189
        }
190
191
        $this->pd = proc_open($this->cmd, $pipesDescr, $this->pipes, $this->cwd, $this->env);
192
        if ($this->pd) {
193
            $this->setFd($this->pipes[1]);
194
        }
195
196
        return $this;
197
    }
198
199
    /**
200
     * Build arguments string from associative/enumerated array (may be mixed)
201
     *
202
     * @param array $args
203
     *
204
     * @return string
205
     */
206
    public static function buildArgs ($args)
207
    {
208
        if (!is_array($args)) {
209
            return '';
210
        }
211
        $ret = '';
212
        foreach ($args as $k => $v) {
213
            if (!is_int($v) && ($v !== null)) {
214
                $v = escapeshellarg($v);
215
            }
216
            if (is_int($k)) {
217
                $ret .= ' ' . $v;
218
            }
219
            else {
220
                if ($k[0] !== '-') {
221
                    $ret .= ' --' . $k . ($v !== null ? '=' . $v : '');
222
                }
223
                else {
224
                    $ret .= ' ' . $k . ($v !== null ? '=' . $v : '');
225
                }
226
            }
227
        }
228
229
        return $ret;
230
    }
231
232
    /**
233
     * Sets fd
234
     *
235
     * @param resource          $fd File descriptor
236
     * @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...
237
     *
238
     * @return void
239
     */
240
    public function setFd ($fd, $bev = null)
241
    {
242
        $this->fd = $fd;
243
        if ($fd === false) {
244
            $this->finish();
245
246
            return;
247
        }
248
        $this->fdWrite = $this->pipes[0];
249
        $flags         = !is_resource($this->fd) ? \EventBufferEvent::OPT_CLOSE_ON_FREE : 0;
250
        $flags         |= \EventBufferEvent::OPT_DEFER_CALLBACKS; // buggy option
251
252
        if ($this->eventLoop === null) {
253
            $this->eventLoop = EventLoop::$instance;
254
        }
255
        $this->bev      = $this->eventLoop->bufferEvent(
256
            $this->fd,
257
            0,
258
            [$this, 'onReadEv'],
259
            null,
260
            [$this, 'onStateEv']
261
        );
262
        $this->bevWrite = $this->eventLoop->bufferEvent(
263
            $this->fdWrite,
264
            0,
265
            null,
266
            [$this, 'onWriteEv'],
267
            null
268
        );
269
        if (!$this->bev || !$this->bevWrite) {
270
            $this->finish();
271
272
            return;
273
        }
274
        if ($this->priority !== null) {
275
            $this->bev->priority = $this->priority;
276
        }
277
        if ($this->timeout !== null) {
278
            $this->setTimeout($this->timeout);
279
        }
280 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...
281
            $this->finish();
282
283
            return;
284
        }
285 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...
286
            $this->finish();
287
288
            return;
289
        }
290
        $this->bev->setWatermark(\Event::READ, $this->lowMark, $this->highMark);
291
        $this->alive = true;
292
293
        init:
294
        if (!$this->inited) {
295
            $this->inited = true;
296
            $this->init();
297
        }
298
    }
299
300
    /**
301
     * Get command string
302
     *
303
     * @return string
304
     */
305
    public function getCmd ()
306
    {
307
        return $this->cmd;
308
    }
309
310
    /**
311
     * Set group
312
     *
313
     * @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...
314
     */
315
    public function setGroup ($val)
316
    {
317
        $this->setGroup = $val;
318
319
        return $this;
320
    }
321
322
    /**
323
     * Set cwd
324
     *
325
     * @param string $dir
326
     *
327
     * @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...
328
     */
329
    public function setCwd ($dir)
330
    {
331
        $this->cwd = $dir;
332
333
        return $this;
334
    }
335
336
    /**
337
     * Set group
338
     *
339
     * @param string $val
340
     *
341
     * @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...
342
     */
343
    public function setUser ($val)
344
    {
345
        $this->setUser = $val;
346
347
        return $this;
348
    }
349
350
    /**
351
     * Set chroot
352
     *
353
     * @param string $dir
354
     *
355
     * @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...
356
     */
357
    public function setChroot ($dir)
358
    {
359
        $this->chroot = $dir;
360
361
        return $this;
362
    }
363
364
    /**
365
     * Sets an array of arguments
366
     *
367
     * @param array Arguments
368
     *
369
     * @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...
370
     */
371
    public function setArgs ($args = null)
372
    {
373
        $this->args = $args;
374
375
        return $this;
376
    }
377
378
    /**
379
     * Set a hash of environment's variables
380
     *
381
     * @param array Hash of environment's variables
382
     *
383
     * @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...
384
     */
385
    public function setEnv ($env = null)
386
    {
387
        $this->env = $env;
388
389
        return $this;
390
    }
391
392
    /**
393
     * Set priority
394
     *
395
     * @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...
396
     *
397
     * @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...
398
     */
399
    public function nice ($nice = null)
400
    {
401
        $this->nice = $nice;
402
403
        return $this;
404
    }
405
406
    /**
407
     * Finish write stream
408
     *
409
     * @return boolean
410
     */
411
    public function finishWrite ()
412
    {
413
        if (!$this->writing) {
414
            $this->closeWrite();
415
        }
416
417
        $this->finishWrite = true;
418
419
        return true;
420
    }
421
422
    /**
423
     * Close write stream
424
     *
425
     * @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...
426
     */
427
    public function closeWrite ()
428
    {
429
        if ($this->bevWrite) {
430
            if (isset($this->bevWrite)) {
431
                $this->bevWrite->free();
432
            }
433
            $this->bevWrite = null;
434
        }
435
436
        if ($this->fdWrite) {
437
            fclose($this->fdWrite);
438
            $this->fdWrite = null;
439
        }
440
441
        return $this;
442
    }
443
444
    /**
445
     * Called when stream is finished
446
     */
447
    public function onFinish ()
448
    {
449
        $this->onEofEvent();
450
    }
451
452
    /**
453
     * Called when got EOF
454
     *
455
     * @return void
456
     */
457
    public function onEofEvent ()
458
    {
459
        if ($this->EOF) {
460
            return;
461
        }
462
        $this->EOF = true;
463
464
        $this->event('eof');
465
    }
466
467
    /**
468
     * Got EOF?
469
     *
470
     * @return boolean
471
     */
472
    public function eof ()
473
    {
474
        return $this->EOF;
475
    }
476
477
    /**
478
     * Send data to the connection. Note that it just writes to buffer that flushes at every baseloop
479
     *
480
     * @param string $data Data to send
481
     *
482
     * @return boolean Success
483
     */
484 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...
485
    {
486
        if (!$this->alive) {
487
            Daemon::log('Attempt to write to dead IOStream (' . get_class($this) . ')');
488
489
            return false;
490
        }
491
        if (!isset($this->bevWrite)) {
492
            return false;
493
        }
494
        if (!mb_orig_strlen($data)) {
495
            return true;
496
        }
497
        $this->writing   = true;
498
        Daemon::$noError = true;
499
        if (!$this->bevWrite->write($data) || !Daemon::$noError) {
500
            $this->close();
501
502
            return false;
503
        }
504
505
        return true;
506
    }
507
508
    /**
509
     * Close the process
510
     *
511
     * @return void
512
     */
513
    public function close ()
514
    {
515
        parent::close();
516
        $this->closeWrite();
517
        if (is_resource($this->pd)) {
518
            proc_close($this->pd);
519
        }
520
    }
521
522
    /**
523
     * Send data and appending \n to connection. Note that it just writes to buffer flushed at every baseloop
524
     *
525
     * @param string Data to send
526
     *
527
     * @return boolean Success
528
     */
529 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...
530
    {
531
        if (!$this->alive) {
532
            Daemon::log('Attempt to write to dead IOStream (' . get_class($this) . ')');
533
534
            return false;
535
        }
536
        if (!isset($this->bevWrite)) {
537
            return false;
538
        }
539
        if (!mb_orig_strlen($data) && !mb_orig_strlen($this->EOL)) {
540
            return true;
541
        }
542
        $this->writing = true;
543
        $this->bevWrite->write($data);
544
        $this->bevWrite->write($this->EOL);
545
546
        return true;
547
    }
548
549
    /**
550
     * Sets callback which will be called once when got EOF
551
     *
552
     * @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...
553
     *
554
     * @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...
555
     */
556
    public function onEOF ($cb = null)
557
    {
558
        return $this->bind('eof', $cb);
0 ignored issues
show
Bug introduced by
It seems like $cb defined by parameter $cb on line 556 can also be of type null; however, PHPDaemon\Traits\EventHandlers::bind() 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...
559
    }
560
561
    public function getStatus ()
562
    {
563
        return !empty($this->pd) ? proc_get_status($this->pd) : null;
564
    }
565
566
    /**
567
     * Called when new data received
568
     *
569
     * @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...
570
     */
571
    protected function onRead ()
572
    {
573
        if (func_num_args() === 1) {
574
            $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...
575
576
            return $this;
577
        }
578
        $this->event('read');
579
    }
580
}
581