Passed
Push — master ( eabf33...342b63 )
by Michiel
05:53
created

ExecTask::main()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3.054

Importance

Changes 0
Metric Value
cc 3
eloc 11
nc 3
nop 0
dl 0
loc 18
ccs 9
cts 11
cp 0.8182
crap 3.054
rs 9.9
c 0
b 0
f 0
1
<?php
2
/**
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the LGPL. For more information please see
17
 * <http://phing.info>.
18
 */
19
20
use Phing\Exception\BuildException;
21
use Phing\Exception\NullPointerException;
22
use Phing\Io\FileUtils;
23
use Phing\Io\IOException;
24
use Phing\Io\File;
25
use Phing\Phing;
26
use Phing\Project;
27
use Phing\Type\Commandline;
28
use Phing\Type\CommandlineArgument;
29
use Phing\Type\Environment;
30
use Phing\Type\Path;
31
use Phing\Util\StringHelper;
32
33
/**
34
 * Executes a command on the shell.
35
 *
36
 * @author  Andreas Aderhold <[email protected]>
37
 * @author  Hans Lellelid <[email protected]>
38
 * @author  Christian Weiske <[email protected]>
39
 * @package phing.tasks.system
40
 */
41
class ExecTask extends Task
42
{
43
    use LogLevelAware;
44
45
    public const INVALID = PHP_INT_MAX;
46
47
    private $exitValue = self::INVALID;
48
49
    /**
50
     * Command to be executed
51
     *
52
     * @var string
53
     */
54
    protected $realCommand;
55
56
    /**
57
     * Commandline managing object
58
     *
59
     * @var Commandline
60
     */
61
    protected $commandline;
62
63
    /**
64
     * Working directory.
65
     *
66
     * @var File
67
     */
68
    protected $dir;
69
70
    protected $currdir;
71
72
    /**
73
     * Operating system.
74
     *
75
     * @var string
76
     */
77
    protected $os;
78
79
    /**
80
     * Whether to escape shell command using escapeshellcmd().
81
     *
82
     * @var boolean
83
     */
84
    protected $escape = false;
85
86
    /**
87
     * Where to direct output.
88
     *
89
     * @var File
90
     */
91
    protected $output;
92
93
    /**
94
     * Whether to use PHP's passthru() function instead of exec()
95
     *
96
     * @var boolean
97
     */
98
    protected $passthru = false;
99
100
    /**
101
     * Whether to log returned output as MSG_INFO instead of MSG_VERBOSE
102
     *
103
     * @var boolean
104
     */
105
    protected $logOutput = false;
106
107
    /**
108
     * Where to direct error output.
109
     *
110
     * @var File
111
     */
112
    protected $error;
113
114
    /**
115
     * If spawn is set then [unix] programs will redirect stdout and add '&'.
116
     *
117
     * @var boolean
118
     */
119
    protected $spawn = false;
120
121
    /**
122
     * Property name to set with return value from exec call.
123
     *
124
     * @var string
125
     */
126
    protected $returnProperty;
127
128
    /**
129
     * Property name to set with output value from exec call.
130
     *
131
     * @var string
132
     */
133
    protected $outputProperty;
134
135
    /**
136
     * Whether to check the return code.
137
     *
138
     * @var boolean
139
     */
140
    protected $checkreturn = false;
141
142
    private $osFamily;
143
    private $executable;
144
    private $resolveExecutable = false;
145
    private $searchPath = false;
146
    private $env;
147
148
    /**
149
     * @throws \Phing\Exception\BuildException
150
     */
151 84
    public function __construct()
152
    {
153 84
        parent::__construct();
154 84
        $this->commandline = new Commandline();
155 84
        $this->env = new Environment();
156 84
    }
157
158
    /**
159
     * Main method: wraps execute() command.
160
     *
161
     * @throws \Phing\Exception\BuildException
162
     */
163 29
    public function main()
164
    {
165 29
        if (!$this->isValidOs()) {
166 1
            return null;
167
        }
168
169
        try {
170 27
            $this->commandline->setExecutable($this->resolveExecutable($this->executable, $this->searchPath));
171
        } catch (IOException | NullPointerException $e) {
172
            throw new BuildException($e);
173
        }
174
175 27
        $this->prepare();
176 25
        $this->buildCommand();
177 25
        [$return, $output] = $this->executeCommand();
178 25
        $this->cleanup($return, $output);
179
180 24
        return $return;
181
    }
182
183
    /**
184
     * Prepares the command building and execution, i.e.
185
     * changes to the specified directory.
186
     *
187
     * @throws BuildException
188
     * @return void
189
     */
190 27
    protected function prepare()
191
    {
192 27
        if ($this->dir === null) {
193 24
            $this->dir = $this->getProject()->getBasedir();
194
        }
195
196 27
        if ($this->commandline->getExecutable() === null) {
0 ignored issues
show
introduced by
The condition $this->commandline->getExecutable() === null is always false.
Loading history...
197 1
            throw new BuildException(
198 1
                'ExecTask: Please provide "executable"'
199
            );
200
        }
201
202
        // expand any symbolic links first
203
        try {
204 26
            if (!$this->dir->getCanonicalFile()->exists()) {
205 1
                throw new BuildException(
206 1
                    "The directory '" . (string) $this->dir . "' does not exist"
207
                );
208
            }
209 25
            if (!$this->dir->getCanonicalFile()->isDirectory()) {
210
                throw new BuildException(
211 25
                    "'" . (string) $this->dir . "' is not a directory"
212
                );
213
            }
214 1
        } catch (IOException $e) {
215
            throw new BuildException(
216
                "'" . (string) $this->dir . "' is not a readable directory"
217
            );
218
        }
219 25
        $this->currdir = getcwd();
220 25
        @chdir($this->dir->getPath());
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

220
        /** @scrutinizer ignore-unhandled */ @chdir($this->dir->getPath());

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
221
222 25
        $this->commandline->setEscape($this->escape);
223 25
    }
224
225
    /**
226
     * @param int $exitValue
227
     * @return bool
228
     */
229 1
    public function isFailure($exitValue = null)
230
    {
231 1
        if ($exitValue === null) {
232
            $exitValue = $this->getExitValue();
233
        }
234
235 1
        return $exitValue !== 0;
236
    }
237
238
    /**
239
     * Builds the full command to execute and stores it in $command.
240
     *
241
     * @throws BuildException
242
     * @return void
243
     * @uses   $command
244
     */
245 25
    protected function buildCommand()
246
    {
247 25
        if ($this->error !== null) {
248 1
            $this->realCommand .= ' 2> ' . escapeshellarg($this->error->getPath());
249 1
            $this->log(
250 1
                'Writing error output to: ' . $this->error->getPath(),
251 1
                $this->logLevel
252
            );
253
        }
254
255 25
        if ($this->output !== null) {
256 1
            $this->realCommand .= ' 1> ' . escapeshellarg($this->output->getPath());
257 1
            $this->log(
258 1
                'Writing standard output to: ' . $this->output->getPath(),
259 1
                $this->logLevel
260
            );
261 24
        } elseif ($this->spawn) {
262 1
            $this->realCommand .= ' 1>/dev/null';
263 1
            $this->log('Sending output to /dev/null', $this->logLevel);
264
        }
265
266
        // If neither output nor error are being written to file
267
        // then we'll redirect error to stdout so that we can dump
268
        // it to screen below.
269
270 25
        if ($this->output === null && $this->error === null && $this->passthru === false) {
271 22
            $this->realCommand .= ' 2>&1';
272
        }
273
274
        // we ignore the spawn boolean for windows
275 25
        if ($this->spawn) {
276 1
            $this->realCommand .= ' &';
277
        }
278
279 25
        $envString = '';
280 25
        $environment = $this->env->getVariables();
281 25
        if ($environment !== null) {
0 ignored issues
show
introduced by
The condition $environment !== null is always true.
Loading history...
282
            foreach ($environment as $variable) {
283
                if ($this->isPath($variable)) {
284
                    continue;
285
                }
286
                $this->log('Setting environment variable: ' . $variable, Project::MSG_VERBOSE);
287
                if (OsCondition::isOS(OsCondition::FAMILY_WINDOWS)) {
288
                    $envString .= 'set ' . $variable . '& ';
289
                } else {
290
                    $envString .= 'export ' . $variable . '; ';
291
                }
292
            }
293
        }
294
295 25
        $this->realCommand = $envString . $this->commandline . $this->realCommand;
296 25
    }
297
298
    /**
299
     * Executes the command and returns return code and output.
300
     *
301
     * @return array array(return code, array with output)
302
     * @throws \Phing\Exception\BuildException
303
     */
304 43
    protected function executeCommand()
305
    {
306 43
        $cmdl = $this->realCommand;
307
308 43
        $this->log('Executing command: ' . $cmdl, $this->logLevel);
309
310 43
        $output = [];
311 43
        $return = null;
312
313 43
        if ($this->passthru) {
314 2
            passthru($cmdl, $return);
315
        } else {
316 41
            exec($cmdl, $output, $return);
317
        }
318
319 43
        return [$return, $output];
320
    }
321
322
    /**
323
     * Runs all tasks after command execution:
324
     * - change working directory back
325
     * - log output
326
     * - verify return value
327
     *
328
     * @param integer $return Return code
329
     * @param array $output Array with command output
330
     *
331
     * @throws BuildException
332
     * @return void
333
     */
334 25
    protected function cleanup($return, $output): void
335
    {
336 25
        if ($this->dir !== null) {
337 25
            @chdir($this->currdir);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

337
            /** @scrutinizer ignore-unhandled */ @chdir($this->currdir);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
338
        }
339
340 25
        $outloglevel = $this->logOutput ? Project::MSG_INFO : Project::MSG_VERBOSE;
341 25
        foreach ($output as $line) {
342 18
            $this->log($line, $outloglevel);
343
        }
344
345 25
        $this->maybeSetReturnPropertyValue($return);
346
347 25
        if ($this->outputProperty) {
348 2
            $this->project->setProperty(
349 2
                $this->outputProperty,
350 2
                implode("\n", $output)
351
            );
352
        }
353
354 25
        $this->setExitValue($return);
355
356 25
        if ($return !== 0) {
357 11
            if ($this->checkreturn) {
358 1
                throw new BuildException($this->getTaskType() . ' returned: ' . $return, $this->getLocation());
359
            }
360 10
            $this->log('Result: ' . $return, Project::MSG_ERR);
361
        }
362 24
    }
363
364
    /**
365
     * Set the exit value.
366
     *
367
     * @param int $value exit value of the process.
368
     */
369 25
    protected function setExitValue($value): void
370
    {
371 25
        $this->exitValue = $value;
372 25
    }
373
374
    /**
375
     * Query the exit value of the process.
376
     *
377
     * @return int the exit value or self::INVALID if no exit value has
378
     *             been received.
379
     */
380
    public function getExitValue(): int
381
    {
382
        return $this->exitValue;
383
    }
384
385
    /**
386
     * The command to use.
387
     *
388
     * @param string $command String or string-compatible (e.g. w/ __toString()).
389
     *
390
     * @return void
391
     * @throws \Phing\Exception\BuildException
392
     */
393 32
    public function setCommand($command): void
394
    {
395 32
        $this->log(
396 32
            "The command attribute is deprecated.\nPlease use the executable attribute and nested arg elements.",
397 32
            Project::MSG_WARN
398
        );
399 32
        $this->commandline = new Commandline($command);
400 32
        $this->executable = $this->commandline->getExecutable();
401 32
    }
402
403
    /**
404
     * The executable to use.
405
     *
406
     * @param string|bool $value String or string-compatible (e.g. w/ __toString()).
407
     *
408
     * @return void
409
     */
410 51
    public function setExecutable($value): void
411
    {
412 51
        if (is_bool($value)) {
413 3
            $value = $value === true ? 'true' : 'false';
414
        }
415 51
        $this->executable = $value;
416 51
        $this->commandline->setExecutable($value);
417 51
    }
418
419
    /**
420
     * Whether to use escapeshellcmd() to escape command.
421
     *
422
     * @param boolean $escape If the command shall be escaped or not
423
     *
424
     * @return void
425
     */
426 7
    public function setEscape(bool $escape): void
427
    {
428 7
        $this->escape = $escape;
429 7
    }
430
431
    /**
432
     * Specify the working directory for executing this command.
433
     *
434
     * @param File $dir Working directory
435
     *
436
     * @return void
437
     */
438 9
    public function setDir(File $dir): void
439
    {
440 9
        $this->dir = $dir;
441 9
    }
442
443
    /**
444
     * Specify OS (or multiple OS) that must match in order to execute this command.
445
     *
446
     * @param string $os Operating system string (e.g. "Linux")
447
     *
448
     * @return void
449
     */
450 6
    public function setOs($os): void
451
    {
452 6
        $this->os = (string) $os;
453 6
    }
454
455
    /**
456
     * List of operating systems on which the command may be executed.
457
     */
458
    public function getOs(): string
459
    {
460
        return $this->os;
461
    }
462
463
    /**
464
     * Restrict this execution to a single OS Family
465
     *
466
     * @param string $osFamily the family to restrict to.
467
     */
468 2
    public function setOsFamily($osFamily): void
469
    {
470 2
        $this->osFamily = strtolower($osFamily);
471 2
    }
472
473
    /**
474
     * Restrict this execution to a single OS Family
475
     */
476
    public function getOsFamily()
477
    {
478
        return $this->osFamily;
479
    }
480
481
    /**
482
     * File to which output should be written.
483
     *
484
     * @param File $f Output log file
485
     *
486
     * @return void
487
     */
488 6
    public function setOutput(File $f): void
489
    {
490 6
        $this->output = $f;
491 6
    }
492
493
    /**
494
     * File to which error output should be written.
495
     *
496
     * @param File $f Error log file
497
     *
498
     * @return void
499
     */
500 4
    public function setError(File $f): void
501
    {
502 4
        $this->error = $f;
503 4
    }
504
505
    /**
506
     * Whether to use PHP's passthru() function instead of exec()
507
     *
508
     * @param boolean $passthru If passthru shall be used
509
     *
510
     * @return void
511
     */
512 4
    public function setPassthru($passthru): void
513
    {
514 4
        $this->passthru = $passthru;
515 4
    }
516
517
    /**
518
     * Whether to log returned output as MSG_INFO instead of MSG_VERBOSE
519
     *
520
     * @param boolean $logOutput If output shall be logged visibly
521
     *
522
     * @return void
523
     */
524 1
    public function setLogoutput($logOutput): void
525
    {
526 1
        $this->logOutput = $logOutput;
527 1
    }
528
529
    /**
530
     * Whether to suppress all output and run in the background.
531
     *
532
     * @param boolean $spawn If the command is to be run in the background
533
     *
534
     * @return void
535
     */
536 4
    public function setSpawn($spawn): void
537
    {
538 4
        $this->spawn = $spawn;
539 4
    }
540
541
    /**
542
     * Whether to check the return code.
543
     *
544
     * @param boolean $checkreturn If the return code shall be checked
545
     *
546
     * @return void
547
     */
548 13
    public function setCheckreturn($checkreturn): void
549
    {
550 13
        $this->checkreturn = $checkreturn;
551 13
    }
552
553
    /**
554
     * The name of property to set to return value from exec() call.
555
     *
556
     * @param string $prop Property name
557
     *
558
     * @return void
559
     */
560 5
    public function setReturnProperty($prop): void
561
    {
562 5
        $this->returnProperty = $prop;
563 5
    }
564
565 43
    protected function maybeSetReturnPropertyValue(int $return)
566
    {
567 43
        if ($this->returnProperty) {
568 3
            $this->getProject()->setNewProperty($this->returnProperty, $return);
569
        }
570 43
    }
571
572
    /**
573
     * The name of property to set to output value from exec() call.
574
     *
575
     * @param string $prop Property name
576
     *
577
     * @return void
578
     */
579 6
    public function setOutputProperty($prop): void
580
    {
581 6
        $this->outputProperty = $prop;
582 6
    }
583
584
    /**
585
     * Add an environment variable to the launched process.
586
     *
587
     * @param EnvVariable $var new environment variable.
588
     */
589
    public function addEnv(EnvVariable $var)
0 ignored issues
show
Bug introduced by
The type EnvVariable was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
590
    {
591
        $this->env->addVariable($var);
592
    }
593
594
    /**
595
     * Creates a nested <arg> tag.
596
     *
597
     * @return CommandlineArgument Argument object
598
     */
599 22
    public function createArg()
600
    {
601 22
        return $this->commandline->createArgument();
602
    }
603
604
    /**
605
     * Is this the OS the user wanted?
606
     *
607
     * @return boolean.
0 ignored issues
show
Documentation Bug introduced by
The doc comment boolean. at position 0 could not be parsed: Unknown type name 'boolean.' at position 0 in boolean..
Loading history...
608
     * <ul>
609
     * <li>
610
     * <li><code>true</code> if the os and osfamily attributes are null.</li>
611
     * <li><code>true</code> if osfamily is set, and the os family and must match
612
     * that of the current OS, according to the logic of
613
     * {@link Os#isOs(String, String, String, String)}, and the result of the
614
     * <code>os</code> attribute must also evaluate true.
615
     * </li>
616
     * <li>
617
     * <code>true</code> if os is set, and the system.property os.name
618
     * is found in the os attribute,</li>
619
     * <li><code>false</code> otherwise.</li>
620
     * </ul>
621
     */
622 48
    protected function isValidOs(): bool
623
    {
624
        //hand osfamily off to OsCondition class, if set
625 48
        if ($this->osFamily !== null && !OsCondition::isFamily($this->osFamily)) {
626
            return false;
627
        }
628
        //the Exec OS check is different from Os.isOs(), which
629
        //probes for a specific OS. Instead it searches the os field
630
        //for the current os.name
631 47
        $myos = Phing::getProperty("os.name");
632 47
        $this->log("Current OS is " . $myos, Project::MSG_VERBOSE);
633 47
        if (($this->os !== null) && (strpos($this->os, $myos) === false)) {
634
            // this command will be executed only on the specified OS
635 2
            $this->log(
636 2
                "This OS, " . $myos
637 2
                . " was not found in the specified list of valid OSes: " . $this->os,
638 2
                Project::MSG_VERBOSE
639
            );
640 2
            return false;
641
        }
642 45
        return true;
643
    }
644
645
    /**
646
     * Set whether to attempt to resolve the executable to a file.
647
     *
648
     * @param bool $resolveExecutable if true, attempt to resolve the
649
     * path of the executable.
650
     */
651
    public function setResolveExecutable($resolveExecutable): void
652
    {
653
        $this->resolveExecutable = $resolveExecutable;
654
    }
655
656
    /**
657
     * Set whether to search nested, then
658
     * system PATH environment variables for the executable.
659
     *
660
     * @param bool $searchPath if true, search PATHs.
661
     */
662
    public function setSearchPath($searchPath): void
663
    {
664
        $this->searchPath = $searchPath;
665
    }
666
667
    /**
668
     * Indicates whether to attempt to resolve the executable to a
669
     * file.
670
     *
671
     * @return bool the resolveExecutable flag
672
     */
673
    public function getResolveExecutable(): bool
674
    {
675
        return $this->resolveExecutable;
676
    }
677
678
    /**
679
     * The method attempts to figure out where the executable is so that we can feed
680
     * the full path. We first try basedir, then the exec dir, and then
681
     * fallback to the straight executable name (i.e. on the path).
682
     *
683
     * @param string $exec the name of the executable.
684
     * @param bool $mustSearchPath if true, the executable will be looked up in
685
     *                               the PATH environment and the absolute path
686
     *                               is returned.
687
     *
688
     * @return string the executable as a full path if it can be determined.
689
     * @throws \Phing\Exception\BuildException
690
     * @throws IOException
691
     * @throws NullPointerException
692
     */
693 27
    protected function resolveExecutable($exec, $mustSearchPath): ?string
694
    {
695 27
        if (!$this->resolveExecutable) {
696 27
            return $exec;
697
        }
698
        // try to find the executable
699
        $executableFile = $this->getProject()->resolveFile($exec);
700
        if ($executableFile->exists()) {
701
            return $executableFile->getAbsolutePath();
702
        }
703
        // now try to resolve against the dir if given
704
        if ($this->dir !== null) {
705
            $executableFile = (new FileUtils())->resolveFile($this->dir, $exec);
706
            if ($executableFile->exists()) {
707
                return $executableFile->getAbsolutePath();
708
            }
709
        }
710
        // couldn't find it - must be on path
711
        if ($mustSearchPath) {
712
            $p = null;
713
            $environment = $this->env->getVariables();
714
            if ($environment !== null) {
0 ignored issues
show
introduced by
The condition $environment !== null is always true.
Loading history...
715
                foreach ($environment as $env) {
716
                    if ($this->isPath($env)) {
717
                        $p = new Path($this->getProject(), $this->getPath($env));
718
                        break;
719
                    }
720
                }
721
            }
722
            if ($p === null) {
723
                $p = new Path($this->getProject(), getenv('path'));
724
            }
725
            if ($p !== null) {
726
                $dirs = $p->listPaths();
727
                foreach ($dirs as $dir) {
728
                    $executableFile = (new FileUtils())->resolveFile(new File($dir), $exec);
729
                    if ($executableFile->exists()) {
730
                        return $executableFile->getAbsolutePath();
731
                    }
732
                }
733
            }
734
        }
735
736
        return $exec;
737
    }
738
739
    private function isPath($line)
740
    {
741
        return StringHelper::startsWith('PATH=', $line) || StringHelper::startsWith('Path=', $line);
742
    }
743
744
    private function getPath($value)
745
    {
746
        if (is_string($value)) {
747
            return StringHelper::substring($value, strlen("PATH="));
748
        }
749
750
        if (is_array($value)) {
751
            $p = $value['PATH'];
752
            return $p ?? $value['Path'];
753
        }
754
755
        throw new InvalidArgumentException('$value should be of type array or string.');
756
    }
757
}
758