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 <charlie@tizac.com>
34
 * @author Hans Lellelid <hans@xmpl.org>
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 1
    public static function getFileSystem()
81
    {
82 1
        if (self::$fs === null) {
83 1
            switch (Phing::getProperty('host.fstype')) {
84 1
                case 'UNIX':
85 1
                    self::$fs = new UnixFileSystem();
86 1
                    break;
87 1
                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 1
        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 1
    public function canonicalize($strPath)
177
    {
178 1
        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 1
    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 1
        @clearstatcache();
200

201

202
        // Shouldn't this be $f->GetAbsolutePath() ?
203
        // And why doesn't GetAbsolutePath() work?
204

205 1
        $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 1
        if (!@file_exists($strPath) && !is_dir($strPath)) {
211 1
            $strPath = $f->getParent();
212 1
            if ($strPath === null || !is_dir($strPath)) {
213 1
                $strPath = Phing::getProperty("user.dir");
214
            }
215
            //$strPath = dirname($strPath);
216
        }
217

218 1
        if (!$write) {
219 1
            return (bool) @is_readable($strPath);
220
        }
221

222 1
        return (bool) @is_writable($strPath);
223
    }
224

225
    /**
226
     * Whether file can be deleted.
227
     *
228
     * @param  PhingFile $f
229
     * @return boolean
230
     */
231 0
    public function canDelete(PhingFile $f)
232
    {
233 0
        clearstatcache();
234 0
        $dir = dirname($f->getAbsolutePath());
235

236 0
        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 1
    public function getLastModifiedTime(PhingFile $f)
249
    {
250 1
        if (!$f->exists()) {
251 1
            return 0;
252
        }
253

254 1
        @clearstatcache();
255 1
        error_clear_last();
256 1
        $strPath = (string) $f->getPath();
257

258 1
        if (@is_link($strPath)) {
259 1
            $stats = @lstat($strPath);
260

261 1
            if (!isset($stats['mtime'])) {
262 0
                $mtime = false;
263
            } else {
264 1
                $mtime = $stats['mtime'];
265
            }
266
        } else {
267 1
            $mtime = @filemtime($strPath);
268
        }
269

270 1
        if (false === $mtime) {
271 0
            $lastError = error_get_last();
272 0
            $errormsg = $lastError['message'] ?? 'unknown error';
273 0
            $msg = "FileSystem::getLastModifiedTime() FAILED. Can not get modified time of $strPath. $errormsg";
274 0
            throw new IOException($msg);
275
        }
276

277 1
        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 1
    public function getLength(PhingFile $f)
290
    {
291 1
        error_clear_last();
292 1
        $strPath = (string) $f->getAbsolutePath();
293 1
        $fs = filesize((string) $strPath);
294 1
        if ($fs !== false) {
295 1
            return $fs;
296
        }
297

298 0
        $lastError = error_get_last();
299 0
        $errormsg = $lastError['message'] ?? 'unknown error';
300 0
        $msg = "FileSystem::Read() FAILED. Cannot get filesize of $strPath. $errormsg";
301 0
        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 1
    public function createNewFile($strPathname)
317
    {
318 1
        if (@file_exists($strPathname)) {
319 0
            return false;
320
        }
321

322
        // Create new file
323 1
        $fp = @fopen($strPathname, "w");
324 1
        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 1
        @fclose($fp);
331

332 1
        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 1
    public function delete(PhingFile $f, $recursive = false)
344
    {
345 1
        if ($f->isDirectory()) {
346 1
            $this->rmdir($f->getPath(), $recursive);
347
        } else {
348 1
            $this->unlink($f->getPath());
349
        }
350
    }
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 0
    public function deleteOnExit(PhingFile $f)
361
    {
362 0
        self::$filesToDelete[] = $f;
363
    }
364

365 1
    public static function deleteFilesOnExit()
366
    {
367 1
        foreach (self::$filesToDelete as $file) {
368 0
            $file->delete();
369
        }
370
    }
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 1
    public function createDirectory(&$f, $mode = null)
387
    {
388 1
        if ($mode === null) {
389 1
            $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 1
            $return = @mkdir($f->getAbsolutePath(), $mode);
395 1
            if ($return) {
396 1
                chmod($f->getAbsolutePath(), $mode);
397
            }
398
        }
399

400 1
        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 0
            $lastError = error_get_last();
421 0
            $errormsg = $lastError['message'] ?? 'unknown error';
422 0
            $msg = "Rename FAILED. Cannot rename $src to $dest. $errormsg";
423 0
            throw new IOException($msg);
424
        }
425
    }
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 1
    public function setLastModifiedTime(PhingFile $f, $time)
438
    {
439 1
        error_clear_last();
440 1
        $path = $f->getPath();
441 1
        $success = @touch($path, $time);
442 1
        if (!$success) {
443 0
            $lastError = error_get_last();
444 0
            $errormsg = $lastError['message'] ?? 'unknown error';
445 0
            throw new IOException("Could not touch '" . $path . "' due to: $errormsg");
446
        }
447
    }
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 0
    public function compare(PhingFile $f1, PhingFile $f2)
460
    {
461 0
        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 1
    public function copy(PhingFile $src, PhingFile $dest)
475
    {
476
        // Recursively copy a directory
477 1
        if ($src->isDirectory()) {
478 0
            $this->copyr($src->getAbsolutePath(), $dest->getAbsolutePath());
479
        }
480

481 1
        $srcPath = $src->getAbsolutePath();
482 1
        $destPath = $dest->getAbsolutePath();
483

484 1
        error_clear_last();
485 1
        if (false === @copy($srcPath, $destPath)) { // Copy FAILED. Log and return err.
486
            // Add error from php to end of log message. $errormsg.
487 0
            $lastError = error_get_last();
488 0
            $errormsg = $lastError['message'] ?? 'unknown error';
489 0
            $msg = "FileSystem::copy() FAILED. Cannot copy $srcPath to $destPath. $errormsg";
490 0
            throw new IOException($msg);
491
        }
492

493 1
        $dest->setMode($src->getMode());
494
    }
495

496
    /**
497
     * Copy a file, or recursively copy a folder and its contents
498
     *
499
     * @author  Aidan Lister <aidan@php.net>
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 0
    public function copyr($source, $dest)
509
    {
510
        // Check for symlinks
511 0
        if (is_link($source)) {
512 0
            return symlink(readlink($source), $dest);
513
        }
514

515
        // Simple copy for a file
516 0
        if (is_file($source)) {
517 0
            return copy($source, $dest);
518
        }
519

520
        // Make destination directory
521 0
        if (!is_dir($dest) && !mkdir($dest) && !is_dir($dest)) {
522 0
            return false;
523
        }
524

525
        // Loop through the folder
526 0
        $dir = dir($source);
527 0
        while (false !== $entry = $dir->read()) {
528
            // Skip pointers
529 0
            if ($entry == '.' || $entry == '..') {
530 0
                continue;
531
            }
532

533
            // Deep copy directories
534 0
            $this->copyr("$source/$entry", "$dest/$entry");
535
        }
536

537
        // Clean up
538 0
        $dir->close();
539

540 0
        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 0
    public function chown($pathname, $user)
554
    {
555 0
        error_clear_last();
556 0
        if (false === @chown($pathname, $user)) { // FAILED.
557 0
            $lastError = error_get_last();
558 0
            $errormsg = $lastError['message'] ?? 'unknown error';
559 0
            $msg = "FileSystem::chown() FAILED. Cannot chown $pathname. User $user." . (isset($errormsg) ? ' ' . $errormsg : "");
560 0
            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 0
    public function chgrp($pathname, $group)
574
    {
575 0
        error_clear_last();
576 0
        if (false === @chgrp($pathname, $group)) { // FAILED.
577 0
            $lastError = error_get_last();
578 0
            $errormsg = $lastError['message'] ?? 'unknown error';
579 0
            $msg = "FileSystem::chgrp() FAILED. Cannot chown $pathname. Group $group." . (isset($errormsg) ? ' ' . $errormsg : "");
580 0
            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 1
    public function chmod($pathname, $mode)
596
    {
597 1
        error_clear_last();
598 1
        $str_mode = decoct($mode); // Show octal in messages.
599 1
        if (false === @chmod($pathname, $mode)) { // FAILED.
600 0
            $lastError = error_get_last();
601 0
            $errormsg = $lastError['message'] ?? 'unknown error';
602 0
            $msg = "FileSystem::chmod() FAILED. Cannot chmod $pathname. Mode $str_mode." . (isset($errormsg) ? ' ' . $errormsg : "");
603 0
            throw new IOException($msg);
604
        }
605
    }
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 0
    public function lock(PhingFile $f)
615
    {
616 0
        $filename = $f->getPath();
617 0
        $fp = @fopen($filename, "w");
618 0
        $result = @flock($fp, LOCK_EX);
619 0
        @fclose($fp);
620 0
        if (!$result) {
621 0
            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 0
    public function unlock(PhingFile $f)
633
    {
634 0
        $filename = $f->getPath();
635 0
        $fp = @fopen($filename, "w");
636 0
        $result = @flock($fp, LOCK_UN);
637 0
        fclose($fp);
638 0
        if (!$result) {
639 0
            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 1
    public function unlink($file)
652
    {
653 1
        error_clear_last();
654 1
        if (false === @unlink($file)) {
655 0
            $lastError = error_get_last();
656 0
            $errormsg = $lastError['message'] ?? 'unknown error';
657 0
            $msg = "FileSystem::unlink() FAILED. Cannot unlink '$file'. $errormsg";
658 0
            throw new IOException($msg);
659
        }
660
    }
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 1
    public function symlink($target, $link)
673
    {
674

675 1
        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 1
        if (false === @symlink($target, $link)) {
680 0
            $lastError = error_get_last();
681 0
            $errormsg = $lastError['message'] ?? 'unknown error';
682
            // Add error from php to end of log message.
683 0
            $msg = "FileSystem::Symlink() FAILED. Cannot symlink '$target' to '$link'. $errormsg";
684 0
            throw new IOException($msg);
685
        }
686
    }
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 0
    public function touch($file, $time = null)
697
    {
698 0
        error_clear_last();
699 0
        if (null === $time) {
700 0
            $error = @touch($file);
701
        } else {
702 0
            $error = @touch($file, $time);
703
        }
704

705 0
        if (false === $error) { // FAILED.
706 0
            $lastError = error_get_last();
707 0
            $errormsg = $lastError['message'] ?? 'unknown error';
708
            // Add error from php to end of log message.
709 0
            $msg = "FileSystem::touch() FAILED. Cannot touch '$file'. $errormsg";
710 0
            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 1
    public function rmdir($dir, $children = false)
726
    {
727 1
        error_clear_last();
728

729
        // If children=FALSE only delete dir if empty.
730 1
        if (false === $children) {
731 1
            if (false === @rmdir($dir)) { // FAILED.
732 0
                $lastError = error_get_last();
733 0
                $errormsg = $lastError['message'] ?? 'unknown error';
734
                // Add error from php to end of log message.
735 0
                $msg = "FileSystem::rmdir() FAILED. Cannot rmdir $dir. $errormsg";
736 0
                throw new Exception($msg);
737
            }
738
        } else { // delete contents and dir.
739 1
            $handle = @opendir($dir);
740 1
            $lastError = error_get_last();
741 1
            $errormsg = $lastError['message'] ?? 'unknown error';
742

743 1
            if (false === $handle) { // Error.
744 0
                $msg = "FileSystem::rmdir() FAILED. Cannot opendir() $dir. $errormsg";
745 0
                throw new Exception($msg);
746
            }
747
            // Read from handle.
748
            // Don't error on readdir().
749 1
            while (false !== ($entry = @readdir($handle))) {
750 1
                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 0
                    if (strpos(strrev($dir), DIRECTORY_SEPARATOR) === 0) { // there is a /
756 0
                        $next_entry = $dir . $entry;
757
                    } else { // no /
758 0
                        $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 0
                    if (false == @is_dir($next_entry)) { // Is file.
766
                        try {
767 0
                            $this->unlink($next_entry); // Delete.
768 0
                        } catch (Exception $e) {
769 0
                            $msg = "FileSystem::Rmdir() FAILED. Cannot FileSystem::Unlink() $next_entry. " . $e->getMessage();
770 0
                            throw new Exception($msg);
771
                        }
772
                    } else { // Is directory.
773
                        try {
774 0
                            $this->rmdir($next_entry, true); // Delete
775 0
                        } catch (Exception $e) {
776 0
                            $msg = "FileSystem::rmdir() FAILED. Cannot FileSystem::rmdir() $next_entry. " . $e->getMessage();
777 0
                            throw new Exception($msg);
778
                        }
779
                    }
780
                }
781
            }
782

783
            // Don't error on closedir()
784 1
            @closedir($handle);
785

786 1
            error_clear_last();
787 1
            if (false === @rmdir($dir)) { // FAILED.
788
                // Add error from php to end of log message.
789 0
                $lastError = error_get_last();
790 0
                $errormsg = $lastError['message'] ?? 'unknown error';
791 0
                $msg = "FileSystem::rmdir() FAILED. Cannot rmdir $dir. $errormsg";
792 0
                throw new Exception($msg);
793
            }
794
        }
795
    }
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 0
    public function umask($mode)
808
    {
809 0
        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 0
        $str_mode = decoct($mode); // Show octal in messages.
815

816 0
        if (false === @umask($mode)) { // FAILED.
817 0
            $lastError = error_get_last();
818 0
            $errormsg = $lastError['message'] ?? 'unknown error';
819
            // Add error from php to end of log message.
820 0
            $msg = "FileSystem::Umask() FAILED. Value $mode. $errormsg";
821 0
            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 0
    public function compareMTimes($file1, $file2)
839
    {
840 0
        $mtime1 = filemtime($file1);
841 0
        $mtime2 = filemtime($file2);
842

843 0
        if ($mtime1 === false) { // FAILED. Log and return err.
844
            // Add error from php to end of log message.
845 0
            $msg = "FileSystem::compareMTimes() FAILED. Cannot can not get modified time of $file1.";
846 0
            throw new Exception($msg);
847
        }
848

849 0
        if ($mtime2 === false) { // FAILED. Log and return err.
850
            // Add error from php to end of log message.
851 0
            $msg = "FileSystem::compareMTimes() FAILED. Cannot can not get modified time of $file2.";
852 0
            throw new Exception($msg);
853
        }
854

855
        // Worked. Log and return compare.
856
        // Compare mtimes.
857 0
        if ($mtime1 == $mtime2) {
858 0
            return 0;
859
        }
860

861 0
        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 1
    public function listContents(PhingFile $f)
871
    {
872 1
        return array_keys(
873 1
            iterator_to_array(
874 1
                new FilesystemIterator(
875 1
                    $f->getAbsolutePath(),
876 1
                    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 1
    public function which($executable, $fallback = false)
893
    {
894 1
        if (is_string($executable)) {
895 1
            if (trim($executable) === '') {
896 0
                return $fallback;
897
            }
898
        } else {
899 1
            return $fallback;
900
        }
901 1
        if (basename($executable) === $executable) {
902 1
            $path = getenv("PATH");
903
        } else {
904 0
            $path = dirname($executable);
905
        }
906 1
        $dirSeparator = $this->getSeparator();
907 1
        $pathSeparator = $this->getPathSeparator();
908 1
        $elements = explode($pathSeparator, $path);
909 1
        $amount = count($elements);
910 1
        $fstype = Phing::getProperty('host.fstype');
911 1
        switch ($fstype) {
912 1
            case 'UNIX':
913 1
                for ($count = 0; $count < $amount; ++$count) {
914 1
                    $file = $elements[$count] . $dirSeparator . $executable;
915 1
                    if (file_exists($file) && is_executable($file)) {
916 1
                        return $file;
917
                    }
918
                }
919 1
                break;
920 0
            case 'WINDOWS':
921 0
                $exts = getenv('PATHEXT');
922 0
                if ($exts === false) {
923 0
                    $exts = ['.exe', '.bat', '.cmd', '.com'];
924
                } else {
925 0
                    $exts = explode($pathSeparator, $exts);
926
                }
927 0
                for ($count = 0; $count < $amount; $count++) {
928 0
                    foreach ($exts as $ext) {
929 0
                        $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 0
                        if (file_exists($file)) {
933 0
                            return $file;
934
                        }
935
                    }
936
                }
937 0
                break;
938
        }
939 1
        if (file_exists($executable) && is_executable($executable)) {
940 0
            return $executable;
941
        }
942 1
        return $fallback;
943
    }
944
}

Read our documentation on viewing source code .

Loading