getName() === $path) { return self::$root; } if ($this->isInRoot($path) && self::$root->hasChild($path) === true) { return self::$root->getChild($path); } return null; } /** * helper method to detect whether given path is in root path * * @param string $path * @return bool */ private function isInRoot($path) { return substr($path, 0, strlen(self::$root->getName())) === self::$root->getName(); } /** * returns content for given path but only when it is of given type * * @param string $path * @param int $type * @return vfsStreamContent */ protected function getContentOfType($path, $type) { $content = $this->getContent($path); if (null !== $content && $content->getType() === $type) { return $content; } return null; } /** * splits path into its dirname and the basename * * @param string $path * @return string[] */ protected function splitPath($path) { $lastSlashPos = strrpos($path, '/'); if (false === $lastSlashPos) { return array('dirname' => '', 'basename' => $path); } return array('dirname' => substr($path, 0, $lastSlashPos), 'basename' => substr($path, $lastSlashPos + 1) ); } /** * helper method to resolve a path from /foo/bar/. to /foo/bar * * @param string $path * @return string */ protected function resolvePath($path) { $newPath = array(); foreach (explode('/', $path) as $pathPart) { if ('.' !== $pathPart) { if ('..' !== $pathPart) { $newPath[] = $pathPart; } elseif (count($newPath) > 1) { array_pop($newPath); } } } return implode('/', $newPath); } /** * open the stream * * @param string $path the path to open * @param string $mode mode for opening * @param string $options options for opening * @param string $opened_path full path that was actually opened * @return bool */ public function stream_open($path, $mode, $options, $opened_path) { $extended = ((strstr($mode, '+') !== false) ? (true) : (false)); $mode = str_replace(array('t', 'b', '+'), '', $mode); if (in_array($mode, array('r', 'w', 'a', 'x', 'c')) === false) { if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) { trigger_error('Illegal mode ' . $mode . ', use r, w, a, x or c, flavoured with t, b and/or +', E_USER_WARNING); } return false; } $this->mode = $this->calculateMode($mode, $extended); $path = $this->resolvePath(vfsStream::path($path)); $this->content = $this->getContentOfType($path, vfsStreamContent::TYPE_FILE); if (null !== $this->content) { if (self::WRITE === $mode) { if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) { trigger_error('File ' . $path . ' already exists, can not open with mode x', E_USER_WARNING); } return false; } if ( (self::TRUNCATE === $mode || self::APPEND === $mode) && $this->content->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false ) { return false; } if (self::TRUNCATE === $mode) { $this->content->openWithTruncate(); } elseif (self::APPEND === $mode) { $this->content->openForAppend(); } else { if (!$this->content->isReadable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup())) { if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) { trigger_error('Permission denied', E_USER_WARNING); } return false; } $this->content->open(); } return true; } $content = $this->createFile($path, $mode, $options); if (false === $content) { return false; } $this->content = $content; return true; } /** * creates a file at given path * * @param string $path the path to open * @param string $mode mode for opening * @param string $options options for opening * @return bool */ private function createFile($path, $mode = null, $options = null) { $names = $this->splitPath($path); if (empty($names['dirname']) === true) { if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) { trigger_error('File ' . $names['basename'] . ' does not exist', E_USER_WARNING); } return false; } $dir = $this->getContentOfType($names['dirname'], vfsStreamContent::TYPE_DIR); if (null === $dir) { if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) { trigger_error('Directory ' . $names['dirname'] . ' does not exist', E_USER_WARNING); } return false; } elseif ($dir->hasChild($names['basename']) === true) { if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) { trigger_error('Directory ' . $names['dirname'] . ' already contains a director named ' . $names['basename'], E_USER_WARNING); } return false; } if (self::READ === $mode) { if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) { trigger_error('Can not open non-existing file ' . $path . ' for reading', E_USER_WARNING); } return false; } if ($dir->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) { if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) { trigger_error('Can not create new file in non-writable path ' . $names['dirname'], E_USER_WARNING); } return false; } return vfsStream::newFile($names['basename'])->at($dir); } /** * calculates the file mode * * @param string $mode opening mode: r, w, a or x * @param bool $extended true if + was set with opening mode * @return int */ protected function calculateMode($mode, $extended) { if (true === $extended) { return self::ALL; } if (self::READ === $mode) { return self::READONLY; } return self::WRITEONLY; } /** * closes the stream * * @see https://github.com/mikey179/vfsStream/issues/40 */ public function stream_close() { $this->content->lock($this, LOCK_UN); } /** * read the stream up to $count bytes * * @param int $count amount of bytes to read * @return string */ public function stream_read($count) { if (self::WRITEONLY === $this->mode) { return ''; } if ($this->content->isReadable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) { return ''; } return $this->content->read($count); } /** * writes data into the stream * * @param string $data * @return int amount of bytes written */ public function stream_write($data) { if (self::READONLY === $this->mode) { return 0; } if ($this->content->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) { return 0; } if (self::$quota->isLimited()) { $data = substr($data, 0, self::$quota->spaceLeft(self::$root->sizeSummarized())); } return $this->content->write($data); } /** * truncates a file to a given length * * @param int $size length to truncate file to * @return bool * @since 1.1.0 */ public function stream_truncate($size) { if (self::READONLY === $this->mode) { return false; } if ($this->content->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) { return false; } if ($this->content->getType() !== vfsStreamContent::TYPE_FILE) { return false; } if (self::$quota->isLimited() && $this->content->size() < $size) { $maxSize = self::$quota->spaceLeft(self::$root->sizeSummarized()); if (0 === $maxSize) { return false; } if ($size > $maxSize) { $size = $maxSize; } } return $this->content->truncate($size); } /** * sets metadata like owner, user or permissions * * @param string $path * @param int $option * @param mixed $var * @return bool * @since 1.1.0 */ public function stream_metadata($path, $option, $var) { $path = $this->resolvePath(vfsStream::path($path)); $content = $this->getContent($path); switch ($option) { case STREAM_META_TOUCH: if (null === $content) { $content = $this->createFile($path, null, STREAM_REPORT_ERRORS); // file creation may not be allowed at provided path if (false === $content) { return false; } } $currentTime = time(); $content->lastModified(((isset($var[0])) ? ($var[0]) : ($currentTime))) ->lastAccessed(((isset($var[1])) ? ($var[1]) : ($currentTime))); return true; case STREAM_META_OWNER_NAME: return false; case STREAM_META_OWNER: if (null === $content) { return false; } return $this->doPermChange($path, $content, function() use ($content, $var) { $content->chown($var); } ); case STREAM_META_GROUP_NAME: return false; case STREAM_META_GROUP: if (null === $content) { return false; } return $this->doPermChange($path, $content, function() use ($content, $var) { $content->chgrp($var); } ); case STREAM_META_ACCESS: if (null === $content) { return false; } return $this->doPermChange($path, $content, function() use ($content, $var) { $content->chmod($var); } ); default: return false; } } /** * executes given permission change when necessary rights allow such a change * * @param string $path * @param vfsStreamAbstractContent $content * @param \Closure $change * @return bool */ private function doPermChange($path, vfsStreamAbstractContent $content, \Closure $change) { if (!$content->isOwnedByUser(vfsStream::getCurrentUser())) { return false; } if (self::$root->getName() !== $path) { $names = $this->splitPath($path); $parent = $this->getContent($names['dirname']); if (!$parent->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup())) { return false; } } $change(); return true; } /** * checks whether stream is at end of file * * @return bool */ public function stream_eof() { return $this->content->eof(); } /** * returns the current position of the stream * * @return int */ public function stream_tell() { return $this->content->getBytesRead(); } /** * seeks to the given offset * * @param int $offset * @param int $whence * @return bool */ public function stream_seek($offset, $whence) { return $this->content->seek($offset, $whence); } /** * flushes unstored data into storage * * @return bool */ public function stream_flush() { return true; } /** * returns status of stream * * @return array */ public function stream_stat() { $fileStat = array('dev' => 0, 'ino' => 0, 'mode' => $this->content->getType() | $this->content->getPermissions(), 'nlink' => 0, 'uid' => $this->content->getUser(), 'gid' => $this->content->getGroup(), 'rdev' => 0, 'size' => $this->content->size(), 'atime' => $this->content->fileatime(), 'mtime' => $this->content->filemtime(), 'ctime' => $this->content->filectime(), 'blksize' => -1, 'blocks' => -1 ); return array_merge(array_values($fileStat), $fileStat); } /** * retrieve the underlaying resource * * Please note that this method always returns false as there is no * underlaying resource to return. * * @param int $cast_as * @since 0.9.0 * @see https://github.com/mikey179/vfsStream/issues/3 * @return bool */ public function stream_cast($cast_as) { return false; } /** * set lock status for stream * * @param int $operation * @return bool * @since 0.10.0 * @see https://github.com/mikey179/vfsStream/issues/6 * @see https://github.com/mikey179/vfsStream/issues/31 * @see https://github.com/mikey179/vfsStream/issues/40 */ public function stream_lock($operation) { if ((LOCK_NB & $operation) == LOCK_NB) { $operation = $operation - LOCK_NB; } return $this->content->lock($this, $operation); } /** * sets options on the stream * * @param int $option key of option to set * @param int $arg1 * @param int $arg2 * @return bool * @since 0.10.0 * @see https://github.com/mikey179/vfsStream/issues/15 * @see http://www.php.net/manual/streamwrapper.stream-set-option.php */ public function stream_set_option($option, $arg1, $arg2) { switch ($option) { case STREAM_OPTION_BLOCKING: // break omitted case STREAM_OPTION_READ_TIMEOUT: // break omitted case STREAM_OPTION_WRITE_BUFFER: // break omitted default: // nothing to do here } return false; } /** * remove the data under the given path * * @param string $path * @return bool */ public function unlink($path) { $realPath = $this->resolvePath(vfsStream::path($path)); $content = $this->getContent($realPath); if (null === $content) { trigger_error('unlink(' . $path . '): No such file or directory', E_USER_WARNING); return false; } if ($content->getType() !== vfsStreamContent::TYPE_FILE) { trigger_error('unlink(' . $path . '): Operation not permitted', E_USER_WARNING); return false; } return $this->doUnlink($realPath); } /** * removes a path * * @param string $path * @return bool */ protected function doUnlink($path) { if (self::$root->getName() === $path) { // delete root? very brave. :) self::$root = null; clearstatcache(); return true; } $names = $this->splitPath($path); $content = $this->getContent($names['dirname']); if (!$content->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup())) { return false; } clearstatcache(); return $content->removeChild($names['basename']); } /** * rename from one path to another * * @param string $path_from * @param string $path_to * @return bool * @author Benoit Aubuchon */ public function rename($path_from, $path_to) { $srcRealPath = $this->resolvePath(vfsStream::path($path_from)); $dstRealPath = $this->resolvePath(vfsStream::path($path_to)); $srcContent = $this->getContent($srcRealPath); if (null == $srcContent) { trigger_error(' No such file or directory', E_USER_WARNING); return false; } $dstNames = $this->splitPath($dstRealPath); $dstParentContent = $this->getContent($dstNames['dirname']); if (null == $dstParentContent) { trigger_error('No such file or directory', E_USER_WARNING); return false; } if (!$dstParentContent->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup())) { trigger_error('Permission denied', E_USER_WARNING); return false; } if ($dstParentContent->getType() !== vfsStreamContent::TYPE_DIR) { trigger_error('Target is not a directory', E_USER_WARNING); return false; } // remove old source first, so we can rename later // (renaming first would lead to not being able to remove the old path) if (!$this->doUnlink($srcRealPath)) { return false; } $dstContent = $srcContent; // Renaming the filename $dstContent->rename($dstNames['basename']); // Copying to the destination $dstParentContent->addChild($dstContent); return true; } /** * creates a new directory * * @param string $path * @param int $mode * @param int $options * @return bool */ public function mkdir($path, $mode, $options) { $umask = vfsStream::umask(); if (0 < $umask) { $permissions = $mode & ~$umask; } else { $permissions = $mode; } $path = $this->resolvePath(vfsStream::path($path)); if (null !== $this->getContent($path)) { trigger_error('mkdir(): Path vfs://' . $path . ' exists', E_USER_WARNING); return false; } if (null === self::$root) { self::$root = vfsStream::newDirectory($path, $permissions); return true; } $maxDepth = count(explode('/', $path)); $names = $this->splitPath($path); $newDirs = $names['basename']; $dir = null; $i = 0; while ($dir === null && $i < $maxDepth) { $dir = $this->getContent($names['dirname']); $names = $this->splitPath($names['dirname']); if (null == $dir) { $newDirs = $names['basename'] . '/' . $newDirs; } $i++; } if (null === $dir || $dir->getType() !== vfsStreamContent::TYPE_DIR || $dir->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) { return false; } $recursive = ((STREAM_MKDIR_RECURSIVE & $options) !== 0) ? (true) : (false); if (strpos($newDirs, '/') !== false && false === $recursive) { return false; } vfsStream::newDirectory($newDirs, $permissions)->at($dir); return true; } /** * removes a directory * * @param string $path * @param int $options * @return bool * @todo consider $options with STREAM_MKDIR_RECURSIVE */ public function rmdir($path, $options) { $path = $this->resolvePath(vfsStream::path($path)); $child = $this->getContentOfType($path, vfsStreamContent::TYPE_DIR); if (null === $child) { return false; } // can only remove empty directories if (count($child->getChildren()) > 0) { return false; } if (self::$root->getName() === $path) { // delete root? very brave. :) self::$root = null; clearstatcache(); return true; } $names = $this->splitPath($path); $dir = $this->getContentOfType($names['dirname'], vfsStreamContent::TYPE_DIR); if ($dir->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) { return false; } clearstatcache(); return $dir->removeChild($child->getName()); } /** * opens a directory * * @param string $path * @param int $options * @return bool */ public function dir_opendir($path, $options) { $path = $this->resolvePath(vfsStream::path($path)); $this->dir = $this->getContentOfType($path, vfsStreamContent::TYPE_DIR); if (null === $this->dir || $this->dir->isReadable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) { return false; } $this->dirIterator = $this->dir->getIterator(); return true; } /** * reads directory contents * * @return string */ public function dir_readdir() { $dir = $this->dirIterator->current(); if (null === $dir) { return false; } $this->dirIterator->next(); return $dir->getName(); } /** * reset directory iteration * * @return bool */ public function dir_rewinddir() { return $this->dirIterator->rewind(); } /** * closes directory * * @return bool */ public function dir_closedir() { $this->dirIterator = null; return true; } /** * returns status of url * * @param string $path path of url to return status for * @param int $flags flags set by the stream API * @return array */ public function url_stat($path, $flags) { $content = $this->getContent($this->resolvePath(vfsStream::path($path))); if (null === $content) { if (($flags & STREAM_URL_STAT_QUIET) != STREAM_URL_STAT_QUIET) { trigger_error(' No such file or directory: ' . $path, E_USER_WARNING); } return false; } $fileStat = array('dev' => 0, 'ino' => 0, 'mode' => $content->getType() | $content->getPermissions(), 'nlink' => 0, 'uid' => $content->getUser(), 'gid' => $content->getGroup(), 'rdev' => 0, 'size' => $content->size(), 'atime' => $content->fileatime(), 'mtime' => $content->filemtime(), 'ctime' => $content->filectime(), 'blksize' => -1, 'blocks' => -1 ); return array_merge(array_values($fileStat), $fileStat); } }