1 | <?php |
||
2 | |||
3 | /** |
||
4 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||
5 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||
6 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||
7 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||
8 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||
9 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||
10 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||
11 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||
12 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||
13 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||
14 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||
15 | * |
||
16 | * This software consists of voluntary contributions made by many individuals |
||
17 | * and is licensed under the LGPL. For more information please see |
||
18 | * <http://phing.info>. |
||
19 | */ |
||
20 | |||
21 | namespace Phing\Io; |
||
22 | |||
23 | use Exception; |
||
24 | use FilesystemIterator; |
||
25 | use Phing\Phing; |
||
26 | |||
27 | /** |
||
28 | * This is an abstract class for platform specific filesystem implementations |
||
29 | * you have to implement each method in the platform specific filesystem implementation |
||
30 | * classes Your local filesytem implementation must extend this class. |
||
31 | * You should also use this class as a template to write your local implementation |
||
32 | * Some native PHP filesystem specific methods are abstracted here as well. Anyway |
||
33 | * you _must_ always use this methods via a PhingFile object (that by nature uses the |
||
34 | * *FileSystem drivers to access the real filesystem via this class using natives. |
||
35 | * |
||
36 | * FIXME: |
||
37 | * - Error handling reduced to min fallthrough runtime exceptions |
||
38 | * more precise errorhandling is done by the PhingFile class |
||
39 | * |
||
40 | * @author Charlie Killian <[email protected]> |
||
41 | * @author Hans Lellelid <[email protected]> |
||
42 | */ |
||
43 | abstract class FileSystem |
||
44 | { |
||
45 | /** |
||
46 | * @var int |
||
47 | */ |
||
48 | public const BA_EXISTS = 0x01; |
||
49 | |||
50 | /** |
||
51 | * @var int |
||
52 | */ |
||
53 | public const BA_REGULAR = 0x02; |
||
54 | |||
55 | /** |
||
56 | * @var int |
||
57 | */ |
||
58 | public const BA_DIRECTORY = 0x04; |
||
59 | |||
60 | /** |
||
61 | * @var int |
||
62 | */ |
||
63 | public const BA_HIDDEN = 0x08; |
||
64 | |||
65 | /** |
||
66 | * Instance for getFileSystem() method. |
||
67 | * |
||
68 | * @var FileSystem |
||
69 | */ |
||
70 | private static $fs; |
||
71 | |||
72 | /** |
||
73 | * @var File[] |
||
74 | */ |
||
75 | private static $filesToDelete = []; |
||
76 | |||
77 | /** |
||
78 | * Static method to return the FileSystem singelton representing |
||
79 | * this platform's local filesystem driver. |
||
80 | * |
||
81 | * @throws IOException |
||
82 | * |
||
83 | * @return FileSystem |
||
84 | */ |
||
85 | 913 | public static function getFileSystem() |
|
86 | { |
||
87 | 913 | if (null === self::$fs) { |
|
88 | 8 | switch (Phing::getProperty('host.fstype')) { |
|
89 | 8 | case 'UNIX': |
|
90 | 6 | self::$fs = new UnixFileSystem(); |
|
91 | |||
92 | 6 | break; |
|
93 | |||
94 | 2 | case 'WINDOWS': |
|
95 | 1 | self::$fs = new WindowsFileSystem(); |
|
96 | |||
97 | 1 | break; |
|
98 | |||
99 | default: |
||
100 | 1 | throw new IOException('Host uses unsupported filesystem, unable to proceed'); |
|
101 | } |
||
102 | } |
||
103 | |||
104 | 912 | return self::$fs; |
|
105 | } |
||
106 | |||
107 | // -- Normalization and construction -- |
||
108 | |||
109 | /** |
||
110 | * Return the local filesystem's name-separator character. |
||
111 | */ |
||
112 | abstract public function getSeparator(); |
||
113 | |||
114 | /** |
||
115 | * Return the local filesystem's path-separator character. |
||
116 | */ |
||
117 | abstract public function getPathSeparator(); |
||
118 | |||
119 | /** |
||
120 | * Convert the given pathname string to normal form. If the string is |
||
121 | * already in normal form then it is simply returned. |
||
122 | * |
||
123 | * @param string $strPath |
||
124 | */ |
||
125 | abstract public function normalize($strPath); |
||
126 | |||
127 | /** |
||
128 | * Compute the length of this pathname string's prefix. The pathname |
||
129 | * string must be in normal form. |
||
130 | * |
||
131 | * @param string $pathname |
||
132 | */ |
||
133 | abstract public function prefixLength($pathname); |
||
134 | |||
135 | /** |
||
136 | * Resolve the child pathname string against the parent. |
||
137 | * Both strings must be in normal form, and the result |
||
138 | * will be a string in normal form. |
||
139 | * |
||
140 | * @param string $parent |
||
141 | * @param string $child |
||
142 | */ |
||
143 | abstract public function resolve($parent, $child); |
||
144 | |||
145 | /** |
||
146 | * Resolve the given abstract pathname into absolute form. Invoked by the |
||
147 | * getAbsolutePath and getCanonicalPath methods in the PhingFile class. |
||
148 | */ |
||
149 | abstract public function resolveFile(File $f); |
||
150 | |||
151 | /** |
||
152 | * Return the parent pathname string to be used when the parent-directory |
||
153 | * argument in one of the two-argument PhingFile constructors is the empty |
||
154 | * pathname. |
||
155 | */ |
||
156 | abstract public function getDefaultParent(); |
||
157 | |||
158 | /** |
||
159 | * Post-process the given URI path string if necessary. This is used on |
||
160 | * win32, e.g., to transform "/c:/foo" into "c:/foo". The path string |
||
161 | * still has slash separators; code in the PhingFile class will translate them |
||
162 | * after this method returns. |
||
163 | * |
||
164 | * @param string $path |
||
165 | */ |
||
166 | abstract public function fromURIPath($path); |
||
167 | |||
168 | // -- Path operations -- |
||
169 | |||
170 | /** |
||
171 | * Tell whether or not the given abstract pathname is absolute. |
||
172 | */ |
||
173 | abstract public function isAbsolute(File $f); |
||
174 | |||
175 | /** |
||
176 | * canonicalize filename by checking on disk. |
||
177 | * |
||
178 | * @param string $strPath |
||
179 | * |
||
180 | * @return mixed canonical path or false if the file doesn't exist |
||
181 | */ |
||
182 | 875 | public function canonicalize($strPath) |
|
183 | { |
||
184 | 875 | return @realpath($strPath); |
|
185 | } |
||
186 | |||
187 | // -- Attribute accessors -- |
||
188 | |||
189 | /** |
||
190 | * Check whether the file or directory denoted by the given abstract |
||
191 | * pathname may be accessed by this process. If the second argument is |
||
192 | * false, then a check for read access is made; if the second |
||
193 | * argument is true, then a check for write (not read-write) |
||
194 | * access is made. Return false if access is denied or an I/O error |
||
195 | * occurs. |
||
196 | * |
||
197 | * @param bool $write |
||
198 | * |
||
199 | * @return bool |
||
200 | */ |
||
201 | 884 | public function checkAccess(File $f, $write = false) |
|
202 | { |
||
203 | // we clear stat cache, its expensive to look up from scratch, |
||
204 | // but we need to be sure |
||
205 | 884 | @clearstatcache(); |
|
206 | |||
207 | // Shouldn't this be $f->GetAbsolutePath() ? |
||
208 | // And why doesn't GetAbsolutePath() work? |
||
209 | |||
210 | 884 | $strPath = (string) $f->getPath(); |
|
211 | |||
212 | // FIXME |
||
213 | // if file object does denote a file that yet not existst |
||
214 | // path rights are checked |
||
215 | 884 | if (!@file_exists($strPath) && !is_dir($strPath)) { |
|
216 | 256 | $strPath = $f->getParent(); |
|
217 | 256 | if (null === $strPath || !is_dir($strPath)) { |
|
218 | 52 | $strPath = Phing::getProperty('user.dir'); |
|
219 | } |
||
220 | //$strPath = dirname($strPath); |
||
221 | } |
||
222 | |||
223 | 884 | if (!$write) { |
|
224 | 884 | return (bool) @is_readable($strPath); |
|
225 | } |
||
226 | |||
227 | 196 | return (bool) @is_writable($strPath); |
|
228 | } |
||
229 | |||
230 | /** |
||
231 | * Whether file can be deleted. |
||
232 | * |
||
233 | * @return bool |
||
234 | */ |
||
235 | public function canDelete(File $f) |
||
236 | { |
||
237 | clearstatcache(); |
||
238 | $dir = dirname($f->getAbsolutePath()); |
||
239 | |||
240 | return @is_writable($dir); |
||
241 | } |
||
242 | |||
243 | /** |
||
244 | * Return the time at which the file or directory denoted by the given |
||
245 | * abstract pathname was last modified, or zero if it does not exist or |
||
246 | * some other I/O error occurs. |
||
247 | * |
||
248 | * @throws IOException |
||
249 | * |
||
250 | * @return int |
||
251 | */ |
||
252 | 55 | public function getLastModifiedTime(File $f) |
|
253 | { |
||
254 | 55 | if (!$f->exists()) { |
|
255 | 5 | return 0; |
|
256 | } |
||
257 | |||
258 | 55 | @clearstatcache(); |
|
259 | 55 | error_clear_last(); |
|
260 | 55 | $strPath = (string) $f->getPath(); |
|
261 | |||
262 | 55 | if (@is_link($strPath)) { |
|
263 | 2 | $stats = @lstat($strPath); |
|
264 | |||
265 | 2 | if (!isset($stats['mtime'])) { |
|
266 | $mtime = false; |
||
267 | } else { |
||
268 | 2 | $mtime = $stats['mtime']; |
|
269 | } |
||
270 | } else { |
||
271 | 55 | $mtime = @filemtime($strPath); |
|
272 | } |
||
273 | |||
274 | 55 | if (false === $mtime) { |
|
275 | $lastError = error_get_last(); |
||
276 | $errormsg = $lastError['message'] ?? 'unknown error'; |
||
277 | $msg = "FileSystem::getLastModifiedTime() FAILED. Can not get modified time of {$strPath}. {$errormsg}"; |
||
278 | |||
279 | throw new IOException($msg); |
||
280 | } |
||
281 | |||
282 | 55 | return (int) $mtime; |
|
283 | } |
||
284 | |||
285 | /** |
||
286 | * Return the length in bytes of the file denoted by the given abstract |
||
287 | * pathname, or zero if it does not exist, is a directory, or some other |
||
288 | * I/O error occurs. |
||
289 | * |
||
290 | * @throws IOException |
||
291 | * |
||
292 | * @return int |
||
293 | */ |
||
294 | 27 | public function getLength(File $f) |
|
295 | { |
||
296 | 27 | error_clear_last(); |
|
297 | 27 | $strPath = (string) $f->getAbsolutePath(); |
|
298 | 27 | $fs = filesize((string) $strPath); |
|
299 | 27 | if (false !== $fs) { |
|
300 | 27 | return $fs; |
|
301 | } |
||
302 | |||
303 | $lastError = error_get_last(); |
||
304 | $errormsg = $lastError['message'] ?? 'unknown error'; |
||
305 | $msg = "FileSystem::Read() FAILED. Cannot get filesize of {$strPath}. {$errormsg}"; |
||
306 | |||
307 | throw new IOException($msg); |
||
308 | } |
||
309 | |||
310 | // -- File operations -- |
||
311 | |||
312 | /** |
||
313 | * Create a new empty file with the given pathname. Return |
||
314 | * true if the file was created and false if a |
||
315 | * file or directory with the given pathname already exists. Throw an |
||
316 | * IOException if an I/O error occurs. |
||
317 | * |
||
318 | * @param string $strPathname path of the file to be created |
||
319 | * |
||
320 | * @throws IOException |
||
321 | * |
||
322 | * @return bool |
||
323 | */ |
||
324 | 80 | public function createNewFile($strPathname) |
|
325 | { |
||
326 | 80 | if (@file_exists($strPathname)) { |
|
327 | return false; |
||
328 | } |
||
329 | |||
330 | // Create new file |
||
331 | 80 | $fp = @fopen($strPathname, 'w'); |
|
332 | 80 | if (false === $fp) { |
|
333 | 1 | $error = error_get_last(); |
|
334 | |||
335 | 1 | throw new IOException( |
|
336 | 1 | "The file \"{$strPathname}\" could not be created: " . $error['message'] ?? 'unknown error' |
|
337 | ); |
||
338 | } |
||
339 | 79 | @fclose($fp); |
|
340 | |||
341 | 79 | return true; |
|
342 | } |
||
343 | |||
344 | /** |
||
345 | * Delete the file or directory denoted by the given abstract pathname, |
||
346 | * returning true if and only if the operation succeeds. |
||
347 | * |
||
348 | * @param bool $recursive |
||
349 | * |
||
350 | * @throws IOException |
||
351 | */ |
||
352 | 217 | public function delete(File $f, $recursive = false) |
|
353 | { |
||
354 | 217 | if ($f->isDirectory()) { |
|
355 | 178 | $this->rmdir($f->getPath(), $recursive); |
|
356 | } else { |
||
357 | 162 | $this->unlink($f->getPath()); |
|
358 | } |
||
359 | 217 | } |
|
360 | |||
361 | /** |
||
362 | * Arrange for the file or directory denoted by the given abstract |
||
363 | * pathname to be deleted when Phing::shutdown is called, returning |
||
364 | * true if and only if the operation succeeds. |
||
365 | * |
||
366 | * @throws IOException |
||
367 | */ |
||
368 | public function deleteOnExit(File $f) |
||
369 | { |
||
370 | self::$filesToDelete[] = $f; |
||
371 | } |
||
372 | |||
373 | 1 | public static function deleteFilesOnExit() |
|
374 | { |
||
375 | 1 | foreach (self::$filesToDelete as $file) { |
|
376 | $file->delete(); |
||
377 | } |
||
378 | 1 | } |
|
379 | |||
380 | /** |
||
381 | * Create a new directory denoted by the given abstract pathname, |
||
382 | * returning true if and only if the operation succeeds. |
||
383 | * |
||
384 | * The behaviour is the same as of "mkdir" command in Linux. |
||
385 | * Without $mode argument, the directory is created with permissions |
||
386 | * corresponding to the umask setting. |
||
387 | * If $mode argument is specified, umask setting is ignored and |
||
388 | * the permissions are set according to the $mode argument using chmod(). |
||
389 | * |
||
390 | * @param File $f |
||
391 | * @param null|int $mode |
||
392 | * |
||
393 | * @return bool |
||
394 | */ |
||
395 | 178 | public function createDirectory(&$f, $mode = null) |
|
396 | { |
||
397 | 178 | if (null === $mode) { |
|
398 | 174 | $return = @mkdir($f->getAbsolutePath()); |
|
399 | } else { |
||
400 | // If the $mode is specified, mkdir() is called with the $mode |
||
401 | // argument so that the new directory does not temporarily have |
||
402 | // higher permissions than it should before chmod() is called. |
||
403 | 12 | $return = @mkdir($f->getAbsolutePath(), $mode); |
|
404 | 12 | if ($return) { |
|
405 | 12 | chmod($f->getAbsolutePath(), $mode); |
|
406 | } |
||
407 | } |
||
408 | |||
409 | 178 | return $return; |
|
410 | } |
||
411 | |||
412 | /** |
||
413 | * Rename the file or directory denoted by the first abstract pathname to |
||
414 | * the second abstract pathname, returning true if and only if |
||
415 | * the operation succeeds. |
||
416 | * |
||
417 | * @param File $f1 abstract source file |
||
418 | * @param File $f2 abstract destination file |
||
419 | * |
||
420 | * @throws IOException if rename cannot be performed |
||
421 | */ |
||
422 | 1 | public function rename(File $f1, File $f2) |
|
423 | { |
||
424 | 1 | error_clear_last(); |
|
425 | // get the canonical paths of the file to rename |
||
426 | 1 | $src = $f1->getAbsolutePath(); |
|
427 | 1 | $dest = $f2->getAbsolutePath(); |
|
428 | 1 | if (false === @rename($src, $dest)) { |
|
429 | $lastError = error_get_last(); |
||
430 | $errormsg = $lastError['message'] ?? 'unknown error'; |
||
431 | $msg = "Rename FAILED. Cannot rename {$src} to {$dest}. {$errormsg}"; |
||
432 | |||
433 | throw new IOException($msg); |
||
434 | } |
||
435 | 1 | } |
|
436 | |||
437 | /** |
||
438 | * Set the last-modified time of the file or directory denoted by the |
||
439 | * given abstract pathname returning true if and only if the |
||
440 | * operation succeeds. |
||
441 | * |
||
442 | * @param int $time |
||
443 | * |
||
444 | * @throws IOException |
||
445 | */ |
||
446 | 78 | public function setLastModifiedTime(File $f, $time) |
|
447 | { |
||
448 | 78 | error_clear_last(); |
|
449 | 78 | $path = $f->getPath(); |
|
450 | 78 | $success = @touch($path, $time); |
|
451 | 78 | if (!$success) { |
|
452 | $lastError = error_get_last(); |
||
453 | $errormsg = $lastError['message'] ?? 'unknown error'; |
||
454 | |||
455 | throw new IOException("Could not touch '" . $path . "' due to: {$errormsg}"); |
||
456 | } |
||
457 | 78 | } |
|
458 | |||
459 | // -- Basic infrastructure -- |
||
460 | |||
461 | /** |
||
462 | * Compare two abstract pathnames lexicographically. |
||
463 | * |
||
464 | * @throws IOException |
||
465 | * |
||
466 | * @return int |
||
467 | */ |
||
468 | public function compare(File $f1, File $f2) |
||
469 | { |
||
470 | throw new IOException('compare() not implemented by local fs driver'); |
||
471 | } |
||
472 | |||
473 | /** |
||
474 | * Copy a file. |
||
475 | * |
||
476 | * @param File $src source path and name file to copy |
||
477 | * @param File $dest destination path and name of new file |
||
478 | * |
||
479 | * @throws IOException if file cannot be copied |
||
480 | */ |
||
481 | 25 | public function copy(File $src, File $dest) |
|
482 | { |
||
483 | // Recursively copy a directory |
||
484 | 25 | if ($src->isDirectory()) { |
|
485 | $this->copyr($src->getAbsolutePath(), $dest->getAbsolutePath()); |
||
486 | } |
||
487 | |||
488 | 25 | $srcPath = $src->getAbsolutePath(); |
|
489 | 25 | $destPath = $dest->getAbsolutePath(); |
|
490 | |||
491 | 25 | error_clear_last(); |
|
492 | 25 | if (false === @copy($srcPath, $destPath)) { // Copy FAILED. Log and return err. |
|
493 | // Add error from php to end of log message. $errormsg. |
||
494 | $lastError = error_get_last(); |
||
495 | $errormsg = $lastError['message'] ?? 'unknown error'; |
||
496 | $msg = "FileSystem::copy() FAILED. Cannot copy {$srcPath} to {$destPath}. {$errormsg}"; |
||
497 | |||
498 | throw new IOException($msg); |
||
499 | } |
||
500 | |||
501 | 25 | $dest->setMode($src->getMode()); |
|
502 | 25 | } |
|
503 | |||
504 | /** |
||
505 | * Copy a file, or recursively copy a folder and its contents. |
||
506 | * |
||
507 | * @param string $source Source path |
||
508 | * @param string $dest Destination path |
||
509 | * |
||
510 | * @return bool Returns TRUE on success, FALSE on failure |
||
511 | * |
||
512 | * @author Aidan Lister <[email protected]> |
||
513 | * |
||
514 | * @version 1.0.1 |
||
515 | * |
||
516 | * @see http://aidanlister.com/repos/v/function.copyr.php |
||
517 | */ |
||
518 | public function copyr($source, $dest) |
||
519 | { |
||
520 | // Check for symlinks |
||
521 | if (is_link($source)) { |
||
522 | return symlink(readlink($source), $dest); |
||
523 | } |
||
524 | |||
525 | // Simple copy for a file |
||
526 | if (is_file($source)) { |
||
527 | return copy($source, $dest); |
||
528 | } |
||
529 | |||
530 | // Make destination directory |
||
531 | if (!is_dir($dest) && !mkdir($dest) && !is_dir($dest)) { |
||
532 | return false; |
||
533 | } |
||
534 | |||
535 | // Loop through the folder |
||
536 | $dir = dir($source); |
||
537 | while (false !== $entry = $dir->read()) { |
||
538 | // Skip pointers |
||
539 | if ('.' == $entry || '..' == $entry) { |
||
540 | continue; |
||
541 | } |
||
542 | |||
543 | // Deep copy directories |
||
544 | $this->copyr("{$source}/{$entry}", "{$dest}/{$entry}"); |
||
545 | } |
||
546 | |||
547 | // Clean up |
||
548 | $dir->close(); |
||
549 | |||
550 | return true; |
||
551 | } |
||
552 | |||
553 | /** |
||
554 | * Change the ownership on a file or directory. |
||
555 | * |
||
556 | * @param string $pathname path and name of file or directory |
||
557 | * @param string $user The user name or number of the file or directory. See http://us.php.net/chown |
||
558 | * |
||
559 | * @throws IOException if operation failed |
||
560 | */ |
||
561 | public function chown($pathname, $user) |
||
562 | { |
||
563 | error_clear_last(); |
||
564 | if (false === @chown($pathname, $user)) { // FAILED. |
||
565 | $lastError = error_get_last(); |
||
566 | $errormsg = $lastError['message'] ?? 'unknown error'; |
||
567 | |||
568 | throw new IOException("FileSystem::chown() FAILED. Cannot chown {$pathname}. User {$user} {$errormsg}"); |
||
569 | } |
||
570 | } |
||
571 | |||
572 | /** |
||
573 | * Change the group on a file or directory. |
||
574 | * |
||
575 | * @param string $pathname path and name of file or directory |
||
576 | * @param string $group The group of the file or directory. See http://us.php.net/chgrp |
||
577 | * |
||
578 | * @throws IOException if operation failed |
||
579 | */ |
||
580 | public function chgrp($pathname, $group) |
||
581 | { |
||
582 | error_clear_last(); |
||
583 | if (false === @chgrp($pathname, $group)) { // FAILED. |
||
584 | $lastError = error_get_last(); |
||
585 | $errormsg = $lastError['message'] ?? 'unknown error'; |
||
586 | |||
587 | throw new IOException("FileSystem::chgrp() FAILED. Cannot chown {$pathname}. Group {$group} {$errormsg}"); |
||
588 | } |
||
589 | } |
||
590 | |||
591 | /** |
||
592 | * Change the permissions on a file or directory. |
||
593 | * |
||
594 | * @param string $pathname path and name of file or directory |
||
595 | * @param int $mode The mode (permissions) of the file or |
||
596 | * directory. If using octal add leading |
||
597 | * 0. eg. 0777. Mode is affected by the |
||
598 | * umask system setting. |
||
599 | * |
||
600 | * @throws IOException if operation failed |
||
601 | */ |
||
602 | 63 | public function chmod($pathname, $mode) |
|
603 | { |
||
604 | 63 | error_clear_last(); |
|
605 | 63 | $str_mode = decoct($mode); // Show octal in messages. |
|
606 | 63 | if (false === @chmod($pathname, $mode)) { // FAILED. |
|
607 | $lastError = error_get_last(); |
||
608 | $errormsg = $lastError['message'] ?? 'unknown error'; |
||
609 | |||
610 | throw new IOException("FileSystem::chmod() FAILED. Cannot chmod {$pathname}. Mode {$str_mode} {$errormsg}"); |
||
611 | } |
||
612 | 63 | } |
|
613 | |||
614 | /** |
||
615 | * Locks a file and throws an Exception if this is not possible. |
||
616 | * |
||
617 | * @throws IOException |
||
618 | */ |
||
619 | public function lock(File $f) |
||
620 | { |
||
621 | $filename = $f->getPath(); |
||
622 | $fp = @fopen($filename, 'w'); |
||
623 | $result = @flock($fp, LOCK_EX); |
||
624 | @fclose($fp); |
||
625 | if (!$result) { |
||
626 | throw new IOException("Could not lock file '{$filename}'"); |
||
627 | } |
||
628 | } |
||
629 | |||
630 | /** |
||
631 | * Unlocks a file and throws an IO Error if this is not possible. |
||
632 | * |
||
633 | * @throws IOException |
||
634 | */ |
||
635 | public function unlock(File $f) |
||
636 | { |
||
637 | $filename = $f->getPath(); |
||
638 | $fp = @fopen($filename, 'w'); |
||
639 | $result = @flock($fp, LOCK_UN); |
||
640 | fclose($fp); |
||
641 | if (!$result) { |
||
642 | throw new IOException("Could not unlock file '{$filename}'"); |
||
643 | } |
||
644 | } |
||
645 | |||
646 | /** |
||
647 | * Delete a file. |
||
648 | * |
||
649 | * @param string $file path and/or name of file to delete |
||
650 | * |
||
651 | * @throws IOException - if an error is encountered |
||
652 | */ |
||
653 | 165 | public function unlink($file) |
|
654 | { |
||
655 | 165 | error_clear_last(); |
|
656 | 165 | if (false === @unlink($file)) { |
|
657 | $lastError = error_get_last(); |
||
658 | $errormsg = $lastError['message'] ?? 'unknown error'; |
||
659 | $msg = "FileSystem::unlink() FAILED. Cannot unlink '{$file}'. {$errormsg}"; |
||
660 | |||
661 | throw new IOException($msg); |
||
662 | } |
||
663 | 165 | } |
|
664 | |||
665 | /** |
||
666 | * Symbolically link a file to another name. |
||
667 | * |
||
668 | * Currently symlink is not implemented on Windows. Don't use if the application is to be portable. |
||
669 | * |
||
670 | * @param string $target path and/or name of file to link |
||
671 | * @param string $link path and/or name of link to be created |
||
672 | * |
||
673 | * @throws IOException |
||
674 | */ |
||
675 | 15 | public function symlink($target, $link) |
|
676 | { |
||
677 | 15 | error_clear_last(); |
|
678 | // If Windows OS then symlink() will report it is not supported in |
||
679 | // the build. Use this error instead of checking for Windows as the OS. |
||
680 | |||
681 | 15 | if (false === @symlink($target, $link)) { |
|
682 | $lastError = error_get_last(); |
||
683 | $errormsg = $lastError['message'] ?? 'unknown error'; |
||
684 | // Add error from php to end of log message. |
||
685 | $msg = "FileSystem::Symlink() FAILED. Cannot symlink '{$target}' to '{$link}'. {$errormsg}"; |
||
686 | |||
687 | throw new IOException($msg); |
||
688 | } |
||
689 | 15 | } |
|
690 | |||
691 | /** |
||
692 | * Set the modification and access time on a file to the present time. |
||
693 | * |
||
694 | * @param string $file path and/or name of file to touch |
||
695 | * @param int $time |
||
696 | * |
||
697 | * @throws Exception |
||
698 | */ |
||
699 | public function touch($file, $time = null) |
||
700 | { |
||
701 | error_clear_last(); |
||
702 | if (null === $time) { |
||
703 | $error = @touch($file); |
||
704 | } else { |
||
705 | $error = @touch($file, $time); |
||
706 | } |
||
707 | |||
708 | if (false === $error) { // FAILED. |
||
709 | $lastError = error_get_last(); |
||
710 | $errormsg = $lastError['message'] ?? 'unknown error'; |
||
711 | // Add error from php to end of log message. |
||
712 | $msg = "FileSystem::touch() FAILED. Cannot touch '{$file}'. {$errormsg}"; |
||
713 | |||
714 | throw new Exception($msg); |
||
715 | } |
||
716 | } |
||
717 | |||
718 | /** |
||
719 | * Delete an empty directory OR a directory and all of its contents. |
||
720 | * |
||
721 | * @param string $dir path and/or name of directory to delete |
||
722 | * @param bool $children False: don't delete directory contents. |
||
723 | * True: delete directory contents. |
||
724 | * |
||
725 | * @throws Exception |
||
726 | */ |
||
727 | 178 | public function rmdir($dir, $children = false) |
|
728 | { |
||
729 | 178 | error_clear_last(); |
|
730 | |||
731 | // If children=FALSE only delete dir if empty. |
||
732 | 178 | if (false === $children) { |
|
733 | 177 | if (false === @rmdir($dir)) { // FAILED. |
|
734 | $lastError = error_get_last(); |
||
735 | $errormsg = $lastError['message'] ?? 'unknown error'; |
||
736 | // Add error from php to end of log message. |
||
737 | $msg = "FileSystem::rmdir() FAILED. Cannot rmdir {$dir}. {$errormsg}"; |
||
738 | |||
739 | 177 | throw new Exception($msg); |
|
740 | } |
||
741 | } else { // delete contents and dir. |
||
742 | 2 | $handle = @opendir($dir); |
|
743 | 2 | $lastError = error_get_last(); |
|
744 | 2 | $errormsg = $lastError['message'] ?? 'unknown error'; |
|
745 | |||
746 | 2 | if (false === $handle) { // Error. |
|
747 | $msg = "FileSystem::rmdir() FAILED. Cannot opendir() {$dir}. {$errormsg}"; |
||
748 | |||
749 | throw new Exception($msg); |
||
750 | } |
||
751 | // Read from handle. |
||
752 | // Don't error on readdir(). |
||
753 | 2 | while (false !== ($entry = @readdir($handle))) { |
|
754 | 2 | if ('.' != $entry && '..' != $entry) { |
|
755 | // Only add / if it isn't already the last char. |
||
756 | // This ONLY serves the purpose of making the Logger |
||
757 | // output look nice:) |
||
758 | |||
759 | if (0 === strpos(strrev($dir), DIRECTORY_SEPARATOR)) { // there is a / |
||
760 | $next_entry = $dir . $entry; |
||
761 | } else { // no / |
||
762 | $next_entry = $dir . DIRECTORY_SEPARATOR . $entry; |
||
763 | } |
||
764 | |||
765 | // NOTE: As of php 4.1.1 is_dir doesn't return FALSE it |
||
766 | // returns 0. So use == not ===. |
||
767 | |||
768 | // Don't error on is_dir() |
||
769 | if (false == @is_dir($next_entry)) { // Is file. |
||
770 | try { |
||
771 | $this->unlink($next_entry); // Delete. |
||
772 | } catch (Exception $e) { |
||
773 | $msg = "FileSystem::Rmdir() FAILED. Cannot FileSystem::Unlink() {$next_entry}. " . $e->getMessage(); |
||
774 | |||
775 | throw new Exception($msg); |
||
776 | } |
||
777 | } else { // Is directory. |
||
778 | try { |
||
779 | $this->rmdir($next_entry, true); // Delete |
||
780 | } catch (Exception $e) { |
||
781 | $msg = "FileSystem::rmdir() FAILED. Cannot FileSystem::rmdir() {$next_entry}. " . $e->getMessage(); |
||
782 | |||
783 | throw new Exception($msg); |
||
784 | } |
||
785 | } |
||
786 | } |
||
787 | } |
||
788 | |||
789 | // Don't error on closedir() |
||
790 | 2 | @closedir($handle); |
|
791 | |||
792 | 2 | error_clear_last(); |
|
793 | 2 | if (false === @rmdir($dir)) { // FAILED. |
|
794 | // Add error from php to end of log message. |
||
795 | $lastError = error_get_last(); |
||
796 | $errormsg = $lastError['message'] ?? 'unknown error'; |
||
797 | $msg = "FileSystem::rmdir() FAILED. Cannot rmdir {$dir}. {$errormsg}"; |
||
798 | |||
799 | throw new Exception($msg); |
||
800 | } |
||
801 | } |
||
802 | 178 | } |
|
803 | |||
804 | /** |
||
805 | * Set the umask for file and directory creation. |
||
806 | * |
||
807 | * @param int $mode |
||
808 | * |
||
809 | * @throws Exception |
||
810 | * |
||
811 | * @internal param Int $mode . Permissions usually in ocatal. Use leading 0 for |
||
812 | * octal. Number between 0 and 0777. |
||
813 | */ |
||
814 | public function umask($mode) |
||
815 | { |
||
816 | error_clear_last(); |
||
817 | // CONSIDERME: |
||
818 | // Throw a warning if mode is 0. PHP converts illegal octal numbers to |
||
819 | // 0 so 0 might not be what the user intended. |
||
820 | |||
821 | $str_mode = decoct($mode); // Show octal in messages. |
||
822 | |||
823 | if (false === @umask($mode)) { // FAILED. |
||
824 | $lastError = error_get_last(); |
||
825 | $errormsg = $lastError['message'] ?? 'unknown error'; |
||
826 | // Add error from php to end of log message. |
||
827 | $msg = "FileSystem::Umask() FAILED. Value {$str_mode}. {$errormsg}"; |
||
828 | |||
829 | throw new Exception($msg); |
||
830 | } |
||
831 | } |
||
832 | |||
833 | /** |
||
834 | * Compare the modified time of two files. |
||
835 | * |
||
836 | * @param string $file1 path and name of file1 |
||
837 | * @param string $file2 path and name of file2 |
||
838 | * |
||
839 | * @throws exception - if cannot get modified time of either file |
||
840 | * |
||
841 | * @return int 1 if file1 is newer. |
||
842 | * -1 if file2 is newer. |
||
843 | * 0 if files have the same time. |
||
844 | * Err object on failure. |
||
845 | */ |
||
846 | public function compareMTimes($file1, $file2) |
||
847 | { |
||
848 | $mtime1 = filemtime($file1); |
||
849 | $mtime2 = filemtime($file2); |
||
850 | |||
851 | if (false === $mtime1) { // FAILED. Log and return err. |
||
852 | // Add error from php to end of log message. |
||
853 | $msg = "FileSystem::compareMTimes() FAILED. Cannot can not get modified time of {$file1}."; |
||
854 | |||
855 | throw new Exception($msg); |
||
856 | } |
||
857 | |||
858 | if (false === $mtime2) { // FAILED. Log and return err. |
||
859 | // Add error from php to end of log message. |
||
860 | $msg = "FileSystem::compareMTimes() FAILED. Cannot can not get modified time of {$file2}."; |
||
861 | |||
862 | throw new Exception($msg); |
||
863 | } |
||
864 | |||
865 | // Worked. Log and return compare. |
||
866 | // Compare mtimes. |
||
867 | if ($mtime1 == $mtime2) { |
||
868 | return 0; |
||
869 | } |
||
870 | |||
871 | return ($mtime1 < $mtime2) ? -1 : 1; // end compare |
||
872 | } |
||
873 | |||
874 | /** |
||
875 | * returns the contents of a directory in an array. |
||
876 | * |
||
877 | * @return string[] |
||
878 | */ |
||
879 | 231 | public function listContents(File $f) |
|
880 | { |
||
881 | 231 | return array_map('strval', array_keys( |
|
882 | 231 | iterator_to_array( |
|
883 | 231 | new FilesystemIterator( |
|
884 | 231 | $f->getAbsolutePath(), |
|
885 | 231 | FilesystemIterator::KEY_AS_FILENAME |
|
886 | ) |
||
887 | ) |
||
888 | )); |
||
889 | } |
||
890 | |||
891 | /** |
||
892 | * PHP implementation of the 'which' command. |
||
893 | * |
||
894 | * Used to retrieve/determine the full path for a command. |
||
895 | * |
||
896 | * @param string $executable Executable file to search for |
||
897 | * @param mixed $fallback default to fallback to |
||
898 | * |
||
899 | * @return string full path for the specified executable/command |
||
900 | */ |
||
901 | 8 | public function which($executable, $fallback = false) |
|
902 | { |
||
903 | 8 | if (is_string($executable)) { |
|
0 ignored issues
–
show
introduced
by
Loading history...
|
|||
904 | 7 | if ('' === trim($executable)) { |
|
905 | 7 | return $fallback; |
|
0 ignored issues
–
show
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...
|
|||
906 | } |
||
907 | } else { |
||
908 | 1 | return $fallback; |
|
909 | } |
||
910 | 7 | if (basename($executable) === $executable) { |
|
911 | 7 | $path = getenv('PATH'); |
|
912 | } else { |
||
913 | $path = dirname($executable); |
||
914 | } |
||
915 | 7 | $dirSeparator = $this->getSeparator(); |
|
916 | 7 | $pathSeparator = $this->getPathSeparator(); |
|
917 | 7 | $elements = explode($pathSeparator, $path); |
|
918 | 7 | $amount = count($elements); |
|
919 | 7 | $fstype = Phing::getProperty('host.fstype'); |
|
920 | |||
921 | 7 | switch ($fstype) { |
|
922 | 7 | case 'UNIX': |
|
923 | 7 | for ($count = 0; $count < $amount; ++$count) { |
|
924 | 7 | $file = $elements[$count] . $dirSeparator . $executable; |
|
925 | 7 | if (file_exists($file) && is_executable($file)) { |
|
926 | 4 | return $file; |
|
927 | } |
||
928 | } |
||
929 | |||
930 | 3 | break; |
|
931 | |||
932 | case 'WINDOWS': |
||
933 | $exts = getenv('PATHEXT'); |
||
934 | if (false === $exts) { |
||
935 | $exts = ['.exe', '.bat', '.cmd', '.com']; |
||
936 | } else { |
||
937 | $exts = explode($pathSeparator, $exts); |
||
938 | } |
||
939 | for ($count = 0; $count < $amount; ++$count) { |
||
940 | foreach ($exts as $ext) { |
||
941 | $file = $elements[$count] . $dirSeparator . $executable . $ext; |
||
942 | // Not all of the extensions above need to be set executable on Windows for them to be executed. |
||
943 | // I'm sure there's a joke here somewhere. |
||
944 | if (file_exists($file)) { |
||
945 | return $file; |
||
946 | } |
||
947 | } |
||
948 | } |
||
949 | |||
950 | break; |
||
951 | } |
||
952 | 3 | if (file_exists($executable) && is_executable($executable)) { |
|
953 | return $executable; |
||
954 | } |
||
955 | |||
956 | 3 | return $fallback; |
|
0 ignored issues
–
show
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...
|
|||
957 | } |
||
958 | } |
||
959 |