. */ /** * This is an abstract class for platform specific filesystem implementations * you have to implement each method in the platform specific filesystem implementation * classes Your local filesytem implementation must extend this class. * You should also use this class as a template to write your local implementation * Some native PHP filesystem specific methods are abstracted here as well. Anyway * you _must_ always use this methods via a PhingFile object (that by nature uses the * *FileSystem drivers to access the real filesystem via this class using natives. * * FIXME: * - Error handling reduced to min fallthrough runtime excetions * more precise errorhandling is done by the PhingFile class * * @author Charlie Killian * @author Hans Lellelid * @version $Revision: 1.11 $ * @package phing.system.io */ abstract class FileSystem { /* properties for simple boolean attributes */ const BA_EXISTS = 0x01; const BA_REGULAR = 0x02; const BA_DIRECTORY = 0x04; const BA_HIDDEN = 0x08; /** Instance for getFileSystem() method. */ private static $fs; /** * Static method to return the FileSystem singelton representing * this platform's local filesystem driver. */ function getFileSystem() { if (self::$fs === null) { switch(Phing::getProperty('host.fstype')) { case 'UNIX': include_once 'phing/system/io/UnixFileSystem.php'; self::$fs = new UnixFileSystem(); break; case 'WIN32': include_once 'phing/system/io/Win32FileSystem.php'; self::$fs = new Win32FileSystem(); break; case 'WINNT': include_once 'phing/system/io/WinNTFileSystem.php'; self::$fs = new WinNTFileSystem(); break; default: throw new Exception("Host uses unsupported filesystem, unable to proceed"); } } return self::$fs; } /* -- Normalization and construction -- */ /** * Return the local filesystem's name-separator character. */ abstract function getSeparator(); /** * Return the local filesystem's path-separator character. */ abstract function getPathSeparator(); /** * Convert the given pathname string to normal form. If the string is * already in normal form then it is simply returned. */ abstract function normalize($strPath); /** * Compute the length of this pathname string's prefix. The pathname * string must be in normal form. */ abstract function prefixLength($pathname); /** * Resolve the child pathname string against the parent. * Both strings must be in normal form, and the result * will be a string in normal form. */ abstract function resolve($parent, $child); /** * Resolve the given abstract pathname into absolute form. Invoked by the * getAbsolutePath and getCanonicalPath methods in the PhingFile class. */ abstract function resolveFile(PhingFile $f); /** * Return the parent pathname string to be used when the parent-directory * argument in one of the two-argument PhingFile constructors is the empty * pathname. */ abstract function getDefaultParent(); /** * Post-process the given URI path string if necessary. This is used on * win32, e.g., to transform "/c:/foo" into "c:/foo". The path string * still has slash separators; code in the PhingFile class will translate them * after this method returns. */ abstract function fromURIPath($path); /* -- Path operations -- */ /** * Tell whether or not the given abstract pathname is absolute. */ abstract function isAbsolute(PhingFile $f); /** * canonicalize filename by checking on disk * @return mixed Canonical path or false if the file doesn't exist. */ function canonicalize($strPath) { return @realpath($strPath); } /* -- Attribute accessors -- */ /** * Return the simple boolean attributes for the file or directory denoted * by the given abstract pathname, or zero if it does not exist or some * other I/O error occurs. */ function getBooleanAttributes($f) { throw new Exception("SYSTEM ERROR method getBooleanAttributes() not implemented by fs driver"); } /** * Check whether the file or directory denoted by the given abstract * pathname may be accessed by this process. If the second argument is * false, then a check for read access is made; if the second * argument is true, then a check for write (not read-write) * access is made. Return false if access is denied or an I/O error * occurs. */ function checkAccess(PhingFile $f, $write = false) { // we clear stat cache, its expensive to look up from scratch, // but we need to be sure @clearstatcache(); // Shouldn't this be $f->GetAbsolutePath() ? // And why doesn't GetAbsolutePath() work? $strPath = (string) $f->getPath(); // FIXME // if file object does denote a file that yet not existst // path rights are checked if (!@file_exists($strPath) && !is_dir($strPath)) { $strPath = $f->getParent(); if ($strPath === null || !is_dir($strPath)) { $strPath = Phing::getProperty("user.dir"); } //$strPath = dirname($strPath); } if (!$write) { return (boolean) @is_readable($strPath); } else { return (boolean) @is_writable($strPath); } } /** * Return the time at which the file or directory denoted by the given * abstract pathname was last modified, or zero if it does not exist or * some other I/O error occurs. */ function getLastModifiedTime(PhingFile $f) { if (!$f->exists()) { return 0; } @clearstatcache(); $strPath = (string) $f->getPath(); $mtime = @filemtime($strPath); if (false === $mtime) { // FAILED. Log and return err. $msg = "FileSystem::Filemtime() FAILED. Cannot can not get modified time of $strPath. $php_errormsg"; throw new Exception($msg); } else { return (int) $mtime; } } /** * Return the length in bytes of the file denoted by the given abstract * pathname, or zero if it does not exist, is a directory, or some other * I/O error occurs. */ function getLength(PhingFile $f) { $strPath = (string) $f->getAbsolutePath(); $fs = filesize((string) $strPath); if ($fs !== false) { return $fs; } else { $msg = "FileSystem::Read() FAILED. Cannot get filesize of $strPath. $php_errormsg"; throw new Exception($msg); } } /* -- File operations -- */ /** * Create a new empty file with the given pathname. Return * true if the file was created and false if a * file or directory with the given pathname already exists. Throw an * IOException if an I/O error occurs. * * @param string Path of the file to be created. * * @throws IOException */ function createNewFile($strPathname) { if (@file_exists($strPathname)) return false; // Create new file $fp = @fopen($strPathname, "w"); if ($fp === false) { throw new IOException("The file \"$strPathname\" could not be created"); } @fclose($fp); return true; } /** * Delete the file or directory denoted by the given abstract pathname, * returning true if and only if the operation succeeds. */ function delete(PhingFile $f) { if ($f->isDirectory()) { return $this->rmdir($f->getPath()); } else { return $this->unlink($f->getPath()); } } /** * Arrange for the file or directory denoted by the given abstract * pathname to be deleted when Phing::shutdown is called, returning * true if and only if the operation succeeds. */ function deleteOnExit($f) { throw new Exception("deleteOnExit() not implemented by local fs driver"); } /** * List the elements of the directory denoted by the given abstract * pathname. Return an array of strings naming the elements of the * directory if successful; otherwise, return null. */ function listDir(PhingFile $f) { $strPath = (string) $f->getAbsolutePath(); $d = @dir($strPath); if (!$d) { return null; } $list = array(); while($entry = $d->read()) { if ($entry != "." && $entry != "..") { array_push($list, $entry); } } $d->close(); unset($d); return $list; } /** * Create a new directory denoted by the given abstract pathname, * returning true if and only if the operation succeeds. */ function createDirectory(&$f) { return @mkdir($f->getAbsolutePath(),0755); } /** * Rename the file or directory denoted by the first abstract pathname to * the second abstract pathname, returning true if and only if * the operation succeeds. * * @param PhingFile $f1 abstract source file * @param PhingFile $f2 abstract destination file * @return void * @throws Exception if rename cannot be performed */ function rename(PhingFile $f1, PhingFile $f2) { // get the canonical paths of the file to rename $src = $f1->getAbsolutePath(); $dest = $f2->getAbsolutePath(); if (false === @rename($src, $dest)) { $msg = "Rename FAILED. Cannot rename $src to $dest. $php_errormsg"; throw new Exception($msg); } } /** * Set the last-modified time of the file or directory denoted by the * given abstract pathname returning true if and only if the * operation succeeds. * @return void * @throws Exception */ function setLastModifiedTime(PhingFile $f, $time) { $path = $f->getPath(); $success = @touch($path, $time); if (!$success) { throw new Exception("Could not create directory due to: $php_errormsg"); } } /** * Mark the file or directory denoted by the given abstract pathname as * read-only, returning true if and only if the operation * succeeds. */ function setReadOnly($f) { throw new Exception("setReadonle() not implemented by local fs driver"); } /* -- Filesystem interface -- */ /** * List the available filesystem roots, return array of PhingFile objects */ function listRoots() { throw new Exception("SYSTEM ERROR [listRoots() not implemented by local fs driver]"); } /* -- Basic infrastructure -- */ /** * Compare two abstract pathnames lexicographically. */ function compare($f1, $f2) { throw new Exception("SYSTEM ERROR [compare() not implemented by local fs driver]"); } /** * Copy a file. * * @param PhingFile $src Source path and name file to copy. * @param PhingFile $dest Destination path and name of new file. * * @return void * @throws Exception if file cannot be copied. */ function copy(PhingFile $src, PhingFile $dest) { global $php_errormsg; $srcPath = $src->getAbsolutePath(); $destPath = $dest->getAbsolutePath(); if (false === @copy($srcPath, $destPath)) { // Copy FAILED. Log and return err. // Add error from php to end of log message. $php_errormsg. $msg = "FileSystem::copy() FAILED. Cannot copy $srcPath to $destPath. $php_errormsg"; throw new Exception($msg); } try { $dest->setMode($src->getMode()); } catch(Exception $exc) { // [MA] does chmod returns an error on systems that do not support it ? // eat it up for now. } } /** * Change the permissions on a file or directory. * * @param pathname String. Path and name of file or directory. * @param mode Int. The mode (permissions) of the file or * directory. If using octal add leading 0. eg. 0777. * Mode is affected by the umask system setting. * * @return void * @throws Exception if operation failed. */ function chmod($pathname, $mode) { $str_mode = decoct($mode); // Show octal in messages. if (false === @chmod($pathname, $mode)) {// FAILED. $msg = "FileSystem::chmod() FAILED. Cannot chmod $pathname. Mode $str_mode. $php_errormsg"; throw new Exception($msg); } } /** * Locks a file and throws an Exception if this is not possible. * @return void * @throws Exception */ function lock(PhingFile $f) { $filename = $f->getPath(); $fp = @fopen($filename, "w"); $result = @flock($fp, LOCK_EX); @fclose($fp); if (!$result) { throw new Exception("Could not lock file '$filename'"); } } /** * Unlocks a file and throws an IO Error if this is not possible. * * @throws Exception * @return void */ function unlock(PhingFile $f) { $filename = $f->getPath(); $fp = @fopen($filename, "w"); $result = @flock($fp, LOCK_UN); fclose($fp); if (!$result) { throw new Exception("Could not unlock file '$filename'"); } } /** * Delete a file. * * @param file String. Path and/or name of file to delete. * * @return void * @throws Exception - if an error is encountered. */ function unlink($file) { global $php_errormsg; if (false === @unlink($file)) { $msg = "FileSystem::unlink() FAILED. Cannot unlink '$file'. $php_errormsg"; throw new Exception($msg); } } /** * Symbolically link a file to another name. * * Currently symlink is not implemented on Windows. Don't use if the application is to be portable. * * @param string $target Path and/or name of file to link. * @param string $link Path and/or name of link to be created. * @return void */ function symlink($target, $link) { // If Windows OS then symlink() will report it is not supported in // the build. Use this error instead of checking for Windows as the OS. if (false === @symlink($target, $link)) { // Add error from php to end of log message. $php_errormsg. $msg = "FileSystem::Symlink() FAILED. Cannot symlink '$target' to '$link'. $php_errormsg"; throw new Exception($msg); } } /** * Set the modification and access time on a file to the present time. * * @param string $file Path and/or name of file to touch. * @param int $time * @return void */ function touch($file, $time = null) { global $php_errormsg; if (null === $time) { $error = @touch($file); } else { $error = @touch($file, $time); } if (false === $error) { // FAILED. // Add error from php to end of log message. $php_errormsg. $msg = "FileSystem::touch() FAILED. Cannot touch '$file'. $php_errormsg"; throw new Exception($msg); } } /** * Delete an empty directory OR a directory and all of its contents. * * @param dir String. Path and/or name of directory to delete. * @param children Boolean. False: don't delete directory contents. * True: delete directory contents. * * @return void */ function rmdir($dir, $children = false) { global $php_errormsg; // If children=FALSE only delete dir if empty. if (false === $children) { if (false === @rmdir($dir)) { // FAILED. // Add error from php to end of log message. $php_errormsg. $msg = "FileSystem::rmdir() FAILED. Cannot rmdir $dir. $php_errormsg"; throw new Exception($msg); } } else { // delete contents and dir. $handle = @opendir($dir); if (false === $handle) { // Error. $msg = "FileSystem::rmdir() FAILED. Cannot opendir() $dir. $php_errormsg"; throw new Exception($msg); } else { // Read from handle. // Don't error on readdir(). while (false !== ($entry = @readdir($handle))) { if ($entry != '.' && $entry != '..') { // Only add / if it isn't already the last char. // This ONLY serves the purpose of making the Logger // output look nice:) if (strpos(strrev($dir), DIRECTORY_SEPARATOR) === 0) {// there is a / $next_entry = $dir . $entry; } else { // no / $next_entry = $dir . DIRECTORY_SEPARATOR . $entry; } // NOTE: As of php 4.1.1 is_dir doesn't return FALSE it // returns 0. So use == not ===. // Don't error on is_dir() if (false == @is_dir($next_entry)) { // Is file. try { self::unlink($next_entry); // Delete. } catch (Exception $e) { $msg = "FileSystem::Rmdir() FAILED. Cannot FileSystem::Unlink() $next_entry. ". $e->getMessage(); throw new Exception($msg); } } else { // Is directory. try { self::rmdir($next_entry, true); // Delete } catch (Exception $e) { $msg = "FileSystem::rmdir() FAILED. Cannot FileSystem::rmdir() $next_entry. ". $e->getMessage(); throw new Exception($msg); } } // end is_dir else } // end .. if } // end while } // end handle if // Don't error on closedir() @closedir($handle); if (false === @rmdir($dir)) { // FAILED. // Add error from php to end of log message. $php_errormsg. $msg = "FileSystem::rmdir() FAILED. Cannot rmdir $dir. $php_errormsg"; throw new Exception($msg); } } } /** * Set the umask for file and directory creation. * * @param mode Int. Permissions ususally in ocatal. Use leading 0 for * octal. Number between 0 and 0777. * * @return void * @throws Exception if there is an error performing operation. */ function umask($mode) { global $php_errormsg; // CONSIDERME: // Throw a warning if mode is 0. PHP converts illegal octal numbers to // 0 so 0 might not be what the user intended. $str_mode = decoct($mode); // Show octal in messages. if (false === @umask($mode)) { // FAILED. // Add error from php to end of log message. $php_errormsg. $msg = "FileSystem::Umask() FAILED. Value $mode. $php_errormsg"; throw new Exception($msg); } } /** * Compare the modified time of two files. * * @param file1 String. Path and name of file1. * @param file2 String. Path and name of file2. * * @return Int. 1 if file1 is newer. * -1 if file2 is newer. * 0 if files have the same time. * Err object on failure. * * @throws Exception - if cannot get modified time of either file. */ function compareMTimes($file1, $file2) { $mtime1 = filemtime($file1); $mtime2 = filemtime($file2); if ($mtime1 === false) { // FAILED. Log and return err. // Add error from php to end of log message. $php_errormsg. $msg = "FileSystem::compareMTimes() FAILED. Cannot can not get modified time of $file1."; throw new Exception($msg); } elseif ($mtime2 === false) { // FAILED. Log and return err. // Add error from php to end of log message. $php_errormsg. $msg = "FileSystem::compareMTimes() FAILED. Cannot can not get modified time of $file2."; throw new Exception($msg); } else { // Worked. Log and return compare. // Compare mtimes. if ($mtime1 == $mtime2) { return 0; } else { return ($mtime1 < $mtime2) ? -1 : 1; } // end compare } } }