Passed
Push — master ( 4d6caf...4726e4 )
by Siad
10:49
created

FileSystem::umask()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 1
dl 0
loc 15
ccs 0
cts 8
cp 0
crap 6
rs 10
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
/**
21
 * This is an abstract class for platform specific filesystem implementations
22
 * you have to implement each method in the platform specific filesystem implementation
23
 * classes Your local filesytem implementation must extend this class.
24
 * You should also use this class as a template to write your local implementation
25
 * Some native PHP filesystem specific methods are abstracted here as well. Anyway
26
 * you _must_ always use this methods via a PhingFile object (that by nature uses the
27
 * *FileSystem drivers to access the real filesystem via this class using natives.
28
 *
29
 * FIXME:
30
 *  - Error handling reduced to min fallthrough runtime exceptions
31
 *    more precise errorhandling is done by the PhingFile class
32
 *
33
 * @author Charlie Killian <[email protected]>
34
 * @author Hans Lellelid <[email protected]>
35
 *
36
 * @package phing.system.io
37
 */
38
abstract class FileSystem
39
{
40
41
    /**
42
     * @var int
43
     */
44
    public const BA_EXISTS = 0x01;
45
46
    /**
47
     * @var int
48
     */
49
    public const BA_REGULAR = 0x02;
50
51
    /**
52
     * @var int
53
     */
54
    public const BA_DIRECTORY = 0x04;
55
56
    /**
57
     * @var int
58
     */
59
    public const BA_HIDDEN = 0x08;
60
61
    /**
62
     * Instance for getFileSystem() method.
63
     *
64
     * @var FileSystem
65
     */
66
    private static $fs;
67
68
    /**
69
     * @var PhingFile[]
70
     */
71
    private static $filesToDelete = [];
72
73
    /**
74
     * Static method to return the FileSystem singelton representing
75
     * this platform's local filesystem driver.
76
     *
77
     * @return FileSystem
78
     * @throws IOException
79
     */
80 857
    public static function getFileSystem()
81
    {
82 857
        if (self::$fs === null) {
83 6
            switch (Phing::getProperty('host.fstype')) {
84 6
                case 'UNIX':
85 4
                    self::$fs = new UnixFileSystem();
86 4
                    break;
87 2
                case 'WINDOWS':
88 1
                    self::$fs = new WindowsFileSystem();
89 1
                    break;
90
                default:
91 1
                    throw new IOException("Host uses unsupported filesystem, unable to proceed");
92
            }
93
        }
94
95 856
        return self::$fs;
96
    }
97
98
    /* -- Normalization and construction -- */
99
100
    /**
101
     * Return the local filesystem's name-separator character.
102
     */
103
    abstract public function getSeparator();
104
105
    /**
106
     * Return the local filesystem's path-separator character.
107
     */
108
    abstract public function getPathSeparator();
109
110
    /**
111
     * Convert the given pathname string to normal form.  If the string is
112
     * already in normal form then it is simply returned.
113
     *
114
     * @param string $strPath
115
     */
116
    abstract public function normalize($strPath);
117
118
    /**
119
     * Compute the length of this pathname string's prefix.  The pathname
120
     * string must be in normal form.
121
     *
122
     * @param string $pathname
123
     */
124
    abstract public function prefixLength($pathname);
125
126
    /**
127
     * Resolve the child pathname string against the parent.
128
     * Both strings must be in normal form, and the result
129
     * will be a string in normal form.
130
     *
131
     * @param string $parent
132
     * @param string $child
133
     */
134
    abstract public function resolve($parent, $child);
135
136
    /**
137
     * Resolve the given abstract pathname into absolute form.  Invoked by the
138
     * getAbsolutePath and getCanonicalPath methods in the PhingFile class.
139
     *
140
     * @param PhingFile $f
141
     */
142
    abstract public function resolveFile(PhingFile $f);
143
144
    /**
145
     * Return the parent pathname string to be used when the parent-directory
146
     * argument in one of the two-argument PhingFile constructors is the empty
147
     * pathname.
148
     */
149
    abstract public function getDefaultParent();
150
151
    /**
152
     * Post-process the given URI path string if necessary.  This is used on
153
     * win32, e.g., to transform "/c:/foo" into "c:/foo".  The path string
154
     * still has slash separators; code in the PhingFile class will translate them
155
     * after this method returns.
156
     *
157
     * @param string $path
158
     */
159
    abstract public function fromURIPath($path);
160
161
    /* -- Path operations -- */
162
163
    /**
164
     * Tell whether or not the given abstract pathname is absolute.
165
     *
166
     * @param PhingFile $f
167
     */
168
    abstract public function isAbsolute(PhingFile $f);
169
170
    /**
171
     * canonicalize filename by checking on disk
172
     *
173
     * @param  string $strPath
174
     * @return mixed  Canonical path or false if the file doesn't exist.
175
     */
176 835
    public function canonicalize($strPath)
177
    {
178 835
        return @realpath($strPath);
179
    }
180
181
    /* -- Attribute accessors -- */
182
183
    /**
184
     * Check whether the file or directory denoted by the given abstract
185
     * pathname may be accessed by this process.  If the second argument is
186
     * false, then a check for read access is made; if the second
187
     * argument is true, then a check for write (not read-write)
188
     * access is made.  Return false if access is denied or an I/O error
189
     * occurs.
190
     *
191
     * @param  PhingFile $f
192
     * @param  boolean $write
193
     * @return bool
194
     */
195 841
    public function checkAccess(PhingFile $f, $write = false)
196
    {
197
        // we clear stat cache, its expensive to look up from scratch,
198
        // but we need to be sure
199 841
        @clearstatcache();
1 ignored issue
show
Bug introduced by
Are you sure the usage of clearstatcache() is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Security Best Practice introduced by
It seems like you do not handle an error condition for clearstatcache(). 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

199
        /** @scrutinizer ignore-unhandled */ @clearstatcache();

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...
200
201
202
        // Shouldn't this be $f->GetAbsolutePath() ?
203
        // And why doesn't GetAbsolutePath() work?
204
205 841
        $strPath = (string) $f->getPath();
206
207
        // FIXME
208
        // if file object does denote a file that yet not existst
209
        // path rights are checked
210 841
        if (!@file_exists($strPath) && !is_dir($strPath)) {
211 214
            $strPath = $f->getParent();
212 214
            if ($strPath === null || !is_dir($strPath)) {
213 51
                $strPath = Phing::getProperty("user.dir");
214
            }
215
            //$strPath = dirname($strPath);
216
        }
217
218 841
        if (!$write) {
219 841
            return (bool) @is_readable($strPath);
220
        }
221
222 157
        return (bool) @is_writable($strPath);
223
    }
224
225
    /**
226
     * Whether file can be deleted.
227
     *
228
     * @param  PhingFile $f
229
     * @return boolean
230
     */
231
    public function canDelete(PhingFile $f)
232
    {
233
        clearstatcache();
234
        $dir = dirname($f->getAbsolutePath());
235
236
        return @is_writable($dir);
237
    }
238
239
    /**
240
     * Return the time at which the file or directory denoted by the given
241
     * abstract pathname was last modified, or zero if it does not exist or
242
     * some other I/O error occurs.
243
     *
244
     * @param  PhingFile $f
245
     * @return int
246
     * @throws IOException
247
     */
248 43
    public function getLastModifiedTime(PhingFile $f)
249
    {
250 43
        if (!$f->exists()) {
251 6
            return 0;
252
        }
253
254 43
        @clearstatcache();
1 ignored issue
show
Bug introduced by
Are you sure the usage of clearstatcache() is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Security Best Practice introduced by
It seems like you do not handle an error condition for clearstatcache(). 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

254
        /** @scrutinizer ignore-unhandled */ @clearstatcache();

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...
255 43
        error_clear_last();
256 43
        $strPath = (string) $f->getPath();
257
258 43
        if (@is_link($strPath)) {
259 2
            $stats = @lstat($strPath);
260
261 2
            if (!isset($stats['mtime'])) {
262
                $mtime = false;
263
            } else {
264 2
                $mtime = $stats['mtime'];
265
            }
266
        } else {
267 43
            $mtime = @filemtime($strPath);
268
        }
269
270 43
        if (false === $mtime) {
271
            $lastError = error_get_last();
272
            $errormsg = $lastError['message'] ?? 'unknown error';
273
            $msg = "FileSystem::getLastModifiedTime() FAILED. Can not get modified time of $strPath. $errormsg";
274
            throw new IOException($msg);
275
        }
276
277 43
        return (int) $mtime;
278
    }
279
280
    /**
281
     * Return the length in bytes of the file denoted by the given abstract
282
     * pathname, or zero if it does not exist, is a directory, or some other
283
     * I/O error occurs.
284
     *
285
     * @param  PhingFile $f
286
     * @throws IOException
287
     * @return int
288
     */
289 15
    public function getLength(PhingFile $f)
290
    {
291 15
        error_clear_last();
292 15
        $strPath = (string) $f->getAbsolutePath();
293 15
        $fs = filesize((string) $strPath);
294 15
        if ($fs !== false) {
295 15
            return $fs;
296
        }
297
298
        $lastError = error_get_last();
299
        $errormsg = $lastError['message'] ?? 'unknown error';
300
        $msg = "FileSystem::Read() FAILED. Cannot get filesize of $strPath. $errormsg";
301
        throw new IOException($msg);
302
    }
303
304
    /* -- File operations -- */
305
306
    /**
307
     * Create a new empty file with the given pathname.  Return
308
     * true if the file was created and false if a
309
     * file or directory with the given pathname already exists.  Throw an
310
     * IOException if an I/O error occurs.
311
     *
312
     * @param  string $strPathname Path of the file to be created.
313
     * @throws IOException
314
     * @return boolean
315
     */
316 64
    public function createNewFile($strPathname)
317
    {
318 64
        if (@file_exists($strPathname)) {
319
            return false;
320
        }
321
322
        // Create new file
323 64
        $fp = @fopen($strPathname, "w");
324 64
        if ($fp === false) {
325 1
            $error = error_get_last();
326 1
            throw new IOException(
327 1
                "The file \"$strPathname\" could not be created: " . $error['message'] ?? 'unknown error'
328
            );
329
        }
330 63
        @fclose($fp);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition for fclose(). 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

330
        /** @scrutinizer ignore-unhandled */ @fclose($fp);

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...
331
332 63
        return true;
333
    }
334
335
    /**
336
     * Delete the file or directory denoted by the given abstract pathname,
337
     * returning true if and only if the operation succeeds.
338
     *
339
     * @param  PhingFile $f
340
     * @param  boolean $recursive
341
     * @throws IOException
342
     */
343 163
    public function delete(PhingFile $f, $recursive = false)
344
    {
345 163
        if ($f->isDirectory()) {
346 137
            $this->rmdir($f->getPath(), $recursive);
347
        } else {
348 130
            $this->unlink($f->getPath());
349
        }
350 163
    }
351
352
    /**
353
     * Arrange for the file or directory denoted by the given abstract
354
     * pathname to be deleted when Phing::shutdown is called, returning
355
     * true if and only if the operation succeeds.
356
     *
357
     * @param  PhingFile $f
358
     * @throws IOException
359
     */
360
    public function deleteOnExit(PhingFile $f)
361
    {
362
        self::$filesToDelete[] = $f;
363
    }
364
365 1
    public static function deleteFilesOnExit()
366
    {
367 1
        foreach (self::$filesToDelete as $file) {
368
            $file->delete();
369
        }
370 1
    }
371
372
    /**
373
     * Create a new directory denoted by the given abstract pathname,
374
     * returning true if and only if the operation succeeds.
375
     *
376
     * The behaviour is the same as of "mkdir" command in Linux.
377
     * Without $mode argument, the directory is created with permissions
378
     * corresponding to the umask setting.
379
     * If $mode argument is specified, umask setting is ignored and
380
     * the permissions are set according to the $mode argument using chmod().
381
     *
382
     * @param  PhingFile $f
383
     * @param  int|null $mode
384
     * @return boolean
385
     */
386 137
    public function createDirectory(&$f, $mode = null)
387
    {
388 137
        if ($mode === null) {
389 133
            $return = @mkdir($f->getAbsolutePath());
390
        } else {
391
            // If the $mode is specified, mkdir() is called with the $mode
392
            // argument so that the new directory does not temporarily have
393
            // higher permissions than it should before chmod() is called.
394 12
            $return = @mkdir($f->getAbsolutePath(), $mode);
395 12
            if ($return) {
396 12
                chmod($f->getAbsolutePath(), $mode);
397
            }
398
        }
399
400 137
        return $return;
401
    }
402
403
    /**
404
     * Rename the file or directory denoted by the first abstract pathname to
405
     * the second abstract pathname, returning true if and only if
406
     * the operation succeeds.
407
     *
408
     * @param  PhingFile $f1 abstract source file
409
     * @param  PhingFile $f2 abstract destination file
410
     * @return void
411
     * @throws IOException if rename cannot be performed
412
     */
413 1
    public function rename(PhingFile $f1, PhingFile $f2)
414
    {
415 1
        error_clear_last();
416
        // get the canonical paths of the file to rename
417 1
        $src = $f1->getAbsolutePath();
418 1
        $dest = $f2->getAbsolutePath();
419 1
        if (false === @rename($src, $dest)) {
420
            $lastError = error_get_last();
421
            $errormsg = $lastError['message'] ?? 'unknown error';
422
            $msg = "Rename FAILED. Cannot rename $src to $dest. $errormsg";
423
            throw new IOException($msg);
424
        }
425 1
    }
426
427
    /**
428
     * Set the last-modified time of the file or directory denoted by the
429
     * given abstract pathname returning true if and only if the
430
     * operation succeeds.
431
     *
432
     * @param  PhingFile $f
433
     * @param  int $time
434
     * @return void
435
     * @throws IOException
436
     */
437 72
    public function setLastModifiedTime(PhingFile $f, $time)
438
    {
439 72
        error_clear_last();
440 72
        $path = $f->getPath();
441 72
        $success = @touch($path, $time);
442 72
        if (!$success) {
443
            $lastError = error_get_last();
444
            $errormsg = $lastError['message'] ?? 'unknown error';
445
            throw new IOException("Could not touch '" . $path . "' due to: $errormsg");
446
        }
447 72
    }
448
449
    /* -- Basic infrastructure -- */
450
451
    /**
452
     * Compare two abstract pathnames lexicographically.
453
     *
454
     * @param  PhingFile $f1
455
     * @param  PhingFile $f2
456
     * @throws IOException
457
     * @return int
458
     */
459
    public function compare(PhingFile $f1, PhingFile $f2)
460
    {
461
        throw new IOException("compare() not implemented by local fs driver");
462
    }
463
464
    /**
465
     * Copy a file.
466
     *
467
     * @param PhingFile $src Source path and name file to copy.
468
     * @param PhingFile $dest Destination path and name of new file.
469
     *
470
     * @return void
471
     *
472
     * @throws IOException if file cannot be copied.
473
     */
474 11
    public function copy(PhingFile $src, PhingFile $dest)
475
    {
476
        // Recursively copy a directory
477 11
        if ($src->isDirectory()) {
478
            $this->copyr($src->getAbsolutePath(), $dest->getAbsolutePath());
479
        }
480
481 11
        $srcPath = $src->getAbsolutePath();
482 11
        $destPath = $dest->getAbsolutePath();
483
484 11
        error_clear_last();
485 11
        if (false === @copy($srcPath, $destPath)) { // Copy FAILED. Log and return err.
486
            // Add error from php to end of log message. $errormsg.
487
            $lastError = error_get_last();
488
            $errormsg = $lastError['message'] ?? 'unknown error';
489
            $msg = "FileSystem::copy() FAILED. Cannot copy $srcPath to $destPath. $errormsg";
490
            throw new IOException($msg);
491
        }
492
493 11
        $dest->setMode($src->getMode());
494 11
    }
495
496
    /**
497
     * Copy a file, or recursively copy a folder and its contents
498
     *
499
     * @author  Aidan Lister <[email protected]>
500
     * @version 1.0.1
501
     * @link    http://aidanlister.com/repos/v/function.copyr.php
502
     *
503
     * @param string $source Source path
504
     * @param string $dest Destination path
505
     *
506
     * @return bool   Returns TRUE on success, FALSE on failure
507
     */
508
    public function copyr($source, $dest)
509
    {
510
        // Check for symlinks
511
        if (is_link($source)) {
512
            return symlink(readlink($source), $dest);
513
        }
514
515
        // Simple copy for a file
516
        if (is_file($source)) {
517
            return copy($source, $dest);
518
        }
519
520
        // Make destination directory
521
        if (!is_dir($dest) && !mkdir($dest) && !is_dir($dest)) {
522
            return false;
523
        }
524
525
        // Loop through the folder
526
        $dir = dir($source);
527
        while (false !== $entry = $dir->read()) {
528
            // Skip pointers
529
            if ($entry == '.' || $entry == '..') {
530
                continue;
531
            }
532
533
            // Deep copy directories
534
            $this->copyr("$source/$entry", "$dest/$entry");
535
        }
536
537
        // Clean up
538
        $dir->close();
539
540
        return true;
541
    }
542
543
    /**
544
     * Change the ownership on a file or directory.
545
     *
546
     * @param string $pathname Path and name of file or directory.
547
     * @param string $user The user name or number of the file or directory. See http://us.php.net/chown
548
     *
549
     * @return void
550
     *
551
     * @throws IOException if operation failed.
552
     */
553
    public function chown($pathname, $user)
554
    {
555
        error_clear_last();
556
        if (false === @chown($pathname, $user)) { // FAILED.
557
            $lastError = error_get_last();
558
            $errormsg = $lastError['message'] ?? 'unknown error';
559
            $msg = "FileSystem::chown() FAILED. Cannot chown $pathname. User $user." . (isset($errormsg) ? ' ' . $errormsg : "");
560
            throw new IOException($msg);
561
        }
562
    }
563
564
    /**
565
     * Change the group on a file or directory.
566
     *
567
     * @param string $pathname Path and name of file or directory.
568
     * @param string $group The group of the file or directory. See http://us.php.net/chgrp
569
     *
570
     * @return void
571
     * @throws IOException if operation failed.
572
     */
573
    public function chgrp($pathname, $group)
574
    {
575
        error_clear_last();
576
        if (false === @chgrp($pathname, $group)) { // FAILED.
577
            $lastError = error_get_last();
578
            $errormsg = $lastError['message'] ?? 'unknown error';
579
            $msg = "FileSystem::chgrp() FAILED. Cannot chown $pathname. Group $group." . (isset($errormsg) ? ' ' . $errormsg : "");
580
            throw new IOException($msg);
581
        }
582
    }
583
584
    /**
585
     * Change the permissions on a file or directory.
586
     *
587
     * @param string $pathname Path and name of file or directory.
588
     * @param int $mode The mode (permissions) of the file or
589
     *                         directory. If using octal add leading 0. eg. 0777.
590
     *                         Mode is affected by the umask system setting.
591
     *
592
     * @return void
593
     * @throws IOException if operation failed.
594
     */
595 51
    public function chmod($pathname, $mode)
596
    {
597 51
        error_clear_last();
598 51
        $str_mode = decoct($mode); // Show octal in messages.
599 51
        if (false === @chmod($pathname, $mode)) { // FAILED.
600
            $lastError = error_get_last();
601
            $errormsg = $lastError['message'] ?? 'unknown error';
602
            $msg = "FileSystem::chmod() FAILED. Cannot chmod $pathname. Mode $str_mode." . (isset($errormsg) ? ' ' . $errormsg : "");
603
            throw new IOException($msg);
604
        }
605 51
    }
606
607
    /**
608
     * Locks a file and throws an Exception if this is not possible.
609
     *
610
     * @param  PhingFile $f
611
     * @return void
612
     * @throws IOException
613
     */
614
    public function lock(PhingFile $f)
615
    {
616
        $filename = $f->getPath();
617
        $fp = @fopen($filename, "w");
618
        $result = @flock($fp, LOCK_EX);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of flock() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

618
        $result = @flock(/** @scrutinizer ignore-type */ $fp, LOCK_EX);
Loading history...
619
        @fclose($fp);
1 ignored issue
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

619
        @fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
Security Best Practice introduced by
It seems like you do not handle an error condition for fclose(). 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

619
        /** @scrutinizer ignore-unhandled */ @fclose($fp);

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...
620
        if (!$result) {
621
            throw new IOException("Could not lock file '$filename'");
622
        }
623
    }
624
625
    /**
626
     * Unlocks a file and throws an IO Error if this is not possible.
627
     *
628
     * @param  PhingFile $f
629
     * @throws IOException
630
     * @return void
631
     */
632
    public function unlock(PhingFile $f)
633
    {
634
        $filename = $f->getPath();
635
        $fp = @fopen($filename, "w");
636
        $result = @flock($fp, LOCK_UN);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of flock() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

636
        $result = @flock(/** @scrutinizer ignore-type */ $fp, LOCK_UN);
Loading history...
637
        fclose($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

637
        fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
638
        if (!$result) {
639
            throw new IOException("Could not unlock file '$filename'");
640
        }
641
    }
642
643
    /**
644
     * Delete a file.
645
     *
646
     * @param string $file Path and/or name of file to delete.
647
     *
648
     * @return void
649
     * @throws IOException - if an error is encountered.
650
     */
651 133
    public function unlink($file)
652
    {
653 133
        error_clear_last();
654 133
        if (false === @unlink($file)) {
655
            $lastError = error_get_last();
656
            $errormsg = $lastError['message'] ?? 'unknown error';
657
            $msg = "FileSystem::unlink() FAILED. Cannot unlink '$file'. $errormsg";
658
            throw new IOException($msg);
659
        }
660 133
    }
661
662
    /**
663
     * Symbolically link a file to another name.
664
     *
665
     * Currently symlink is not implemented on Windows. Don't use if the application is to be portable.
666
     *
667
     * @param  string $target Path and/or name of file to link.
668
     * @param  string $link Path and/or name of link to be created.
669
     * @throws IOException
670
     * @return void
671
     */
672 15
    public function symlink($target, $link)
673
    {
674
675 15
        error_clear_last();
676
        // If Windows OS then symlink() will report it is not supported in
677
        // the build. Use this error instead of checking for Windows as the OS.
678
679 15
        if (false === @symlink($target, $link)) {
680
            $lastError = error_get_last();
681
            $errormsg = $lastError['message'] ?? 'unknown error';
682
            // Add error from php to end of log message.
683
            $msg = "FileSystem::Symlink() FAILED. Cannot symlink '$target' to '$link'. $errormsg";
684
            throw new IOException($msg);
685
        }
686 15
    }
687
688
    /**
689
     * Set the modification and access time on a file to the present time.
690
     *
691
     * @param  string $file Path and/or name of file to touch.
692
     * @param  int $time
693
     * @throws Exception
694
     * @return void
695
     */
696
    public function touch($file, $time = null)
697
    {
698
        error_clear_last();
699
        if (null === $time) {
700
            $error = @touch($file);
701
        } else {
702
            $error = @touch($file, $time);
703
        }
704
705
        if (false === $error) { // FAILED.
706
            $lastError = error_get_last();
707
            $errormsg = $lastError['message'] ?? 'unknown error';
708
            // Add error from php to end of log message.
709
            $msg = "FileSystem::touch() FAILED. Cannot touch '$file'. $errormsg";
710
            throw new Exception($msg);
711
        }
712
    }
713
714
    /**
715
     * Delete an empty directory OR a directory and all of its contents.
716
     *
717
     * @param string $dir Path and/or name of directory to delete.
718
     * @param bool $children False: don't delete directory contents.
719
     *                         True: delete directory contents.
720
     *
721
     * @throws Exception
722
     *
723
     * @return void
724
     */
725 137
    public function rmdir($dir, $children = false)
726
    {
727 137
        error_clear_last();
728
729
        // If children=FALSE only delete dir if empty.
730 137
        if (false === $children) {
731 136
            if (false === @rmdir($dir)) { // FAILED.
732
                $lastError = error_get_last();
733
                $errormsg = $lastError['message'] ?? 'unknown error';
734
                // Add error from php to end of log message.
735
                $msg = "FileSystem::rmdir() FAILED. Cannot rmdir $dir. $errormsg";
736
                throw new Exception($msg);
737
            }
738
        } else { // delete contents and dir.
739 2
            $handle = @opendir($dir);
740 2
            $lastError = error_get_last();
741 2
            $errormsg = $lastError['message'] ?? 'unknown error';
742
743 2
            if (false === $handle) { // Error.
744
                $msg = "FileSystem::rmdir() FAILED. Cannot opendir() $dir. $errormsg";
745
                throw new Exception($msg);
746
            }
747
            // Read from handle.
748
            // Don't error on readdir().
749 2
            while (false !== ($entry = @readdir($handle))) {
750 2
                if ($entry != '.' && $entry != '..') {
751
                    // Only add / if it isn't already the last char.
752
                    // This ONLY serves the purpose of making the Logger
753
                    // output look nice:)
754
755
                    if (strpos(strrev($dir), DIRECTORY_SEPARATOR) === 0) { // there is a /
756
                        $next_entry = $dir . $entry;
757
                    } else { // no /
758
                        $next_entry = $dir . DIRECTORY_SEPARATOR . $entry;
759
                    }
760
761
                    // NOTE: As of php 4.1.1 is_dir doesn't return FALSE it
762
                    // returns 0. So use == not ===.
763
764
                    // Don't error on is_dir()
765
                    if (false == @is_dir($next_entry)) { // Is file.
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
766
                        try {
767
                            $this->unlink($next_entry); // Delete.
768
                        } catch (Exception $e) {
769
                            $msg = "FileSystem::Rmdir() FAILED. Cannot FileSystem::Unlink() $next_entry. " . $e->getMessage();
770
                            throw new Exception($msg);
771
                        }
772
                    } else { // Is directory.
773
                        try {
774
                            $this->rmdir($next_entry, true); // Delete
775
                        } catch (Exception $e) {
776
                            $msg = "FileSystem::rmdir() FAILED. Cannot FileSystem::rmdir() $next_entry. " . $e->getMessage();
777
                            throw new Exception($msg);
778
                        }
779
                    }
780
                }
781
            }
782
783
            // Don't error on closedir()
784 2
            @closedir($handle);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition for closedir(). 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

784
            /** @scrutinizer ignore-unhandled */ @closedir($handle);

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...
Bug introduced by
Are you sure the usage of closedir($handle) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
785
786 2
            error_clear_last();
787 2
            if (false === @rmdir($dir)) { // FAILED.
788
                // Add error from php to end of log message.
789
                $lastError = error_get_last();
790
                $errormsg = $lastError['message'] ?? 'unknown error';
791
                $msg = "FileSystem::rmdir() FAILED. Cannot rmdir $dir. $errormsg";
792
                throw new Exception($msg);
793
            }
794
        }
795 137
    }
796
797
    /**
798
     * Set the umask for file and directory creation.
799
     *
800
     * @param    Int $mode
801
     * @throws   Exception
802
     * @internal param Int $mode . Permissions usually in ocatal. Use leading 0 for
803
     *                    octal. Number between 0 and 0777.
804
     *
805
     * @return void
806
     */
807
    public function umask($mode)
808
    {
809
        error_clear_last();
810
        // CONSIDERME:
811
        // Throw a warning if mode is 0. PHP converts illegal octal numbers to
812
        // 0 so 0 might not be what the user intended.
813
814
        $str_mode = decoct($mode); // Show octal in messages.
0 ignored issues
show
Unused Code introduced by
The assignment to $str_mode is dead and can be removed.
Loading history...
815
816
        if (false === @umask($mode)) { // FAILED.
817
            $lastError = error_get_last();
818
            $errormsg = $lastError['message'] ?? 'unknown error';
819
            // Add error from php to end of log message.
820
            $msg = "FileSystem::Umask() FAILED. Value $mode. $errormsg";
821
            throw new Exception($msg);
822
        }
823
    }
824
825
    /**
826
     * Compare the modified time of two files.
827
     *
828
     * @param string $file1 Path and name of file1.
829
     * @param string $file2 Path and name of file2.
830
     *
831
     * @return int  1 if file1 is newer.
832
     *              -1 if file2 is newer.
833
     *              0 if files have the same time.
834
     *              Err object on failure.
835
     *
836
     * @throws Exception - if cannot get modified time of either file.
837
     */
838
    public function compareMTimes($file1, $file2)
839
    {
840
        $mtime1 = filemtime($file1);
841
        $mtime2 = filemtime($file2);
842
843
        if ($mtime1 === false) { // FAILED. Log and return err.
844
            // Add error from php to end of log message.
845
            $msg = "FileSystem::compareMTimes() FAILED. Cannot can not get modified time of $file1.";
846
            throw new Exception($msg);
847
        }
848
849
        if ($mtime2 === false) { // FAILED. Log and return err.
850
            // Add error from php to end of log message.
851
            $msg = "FileSystem::compareMTimes() FAILED. Cannot can not get modified time of $file2.";
852
            throw new Exception($msg);
853
        }
854
855
        // Worked. Log and return compare.
856
        // Compare mtimes.
857
        if ($mtime1 == $mtime2) {
858
            return 0;
859
        }
860
861
        return ($mtime1 < $mtime2) ? -1 : 1; // end compare
862
    }
863
864
    /**
865
     * returns the contents of a directory in an array
866
     *
867
     * @param  PhingFile $f
868
     * @return string[]
869
     */
870 193
    public function listContents(PhingFile $f)
871
    {
872 193
        return array_keys(
873 193
            iterator_to_array(
874 193
                new FilesystemIterator(
875 193
                    $f->getAbsolutePath(),
876 193
                    FilesystemIterator::KEY_AS_FILENAME
877
                )
878
            )
879
        );
880
    }
881
882
    /**
883
     * PHP implementation of the 'which' command.
884
     *
885
     * Used to retrieve/determine the full path for a command.
886
     *
887
     * @param string $executable Executable file to search for
888
     * @param mixed $fallback Default to fallback to.
889
     *
890
     * @return string Full path for the specified executable/command.
891
     */
892 30
    public function which($executable, $fallback = false)
893
    {
894 30
        if (is_string($executable)) {
0 ignored issues
show
introduced by
The condition is_string($executable) is always true.
Loading history...
895 29
            if (trim($executable) === '') {
896
                return $fallback;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $fallback could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
897
            }
898
        } else {
899 1
            return $fallback;
900
        }
901 29
        if (basename($executable) === $executable) {
902 29
            $path = getenv("PATH");
903
        } else {
904
            $path = dirname($executable);
905
        }
906 29
        $dirSeparator = $this->getSeparator();
907 29
        $pathSeparator = $this->getPathSeparator();
908 29
        $elements = explode($pathSeparator, $path);
909 29
        $amount = count($elements);
910 29
        $fstype = Phing::getProperty('host.fstype');
911 29
        switch ($fstype) {
912 29
            case 'UNIX':
913 29
                for ($count = 0; $count < $amount; ++$count) {
914 29
                    $file = $elements[$count] . $dirSeparator . $executable;
915 29
                    if (file_exists($file) && is_executable($file)) {
916 27
                        return $file;
917
                    }
918
                }
919 4
                break;
920
            case 'WINDOWS':
921
                $exts = getenv('PATHEXT');
922
                if ($exts === false) {
923
                    $exts = ['.exe', '.bat', '.cmd', '.com'];
924
                } else {
925
                    $exts = explode($pathSeparator, $exts);
926
                }
927
                for ($count = 0; $count < $amount; $count++) {
928
                    foreach ($exts as $ext) {
929
                        $file = $elements[$count] . $dirSeparator . $executable . $ext;
930
                        // Not all of the extensions above need to be set executable on Windows for them to be executed.
931
                        // I'm sure there's a joke here somewhere.
932
                        if (file_exists($file)) {
933
                            return $file;
934
                        }
935
                    }
936
                }
937
                break;
938
        }
939 4
        if (file_exists($executable) && is_executable($executable)) {
940
            return $executable;
941
        }
942 4
        return $fallback;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $fallback could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
943
    }
944
}
945