Version 1
[yaffs-website] / vendor / mikey179 / vfsStream / src / main / php / org / bovigo / vfs / vfsStreamWrapper.php
diff --git a/vendor/mikey179/vfsStream/src/main/php/org/bovigo/vfs/vfsStreamWrapper.php b/vendor/mikey179/vfsStream/src/main/php/org/bovigo/vfs/vfsStreamWrapper.php
new file mode 100644 (file)
index 0000000..368b2fb
--- /dev/null
@@ -0,0 +1,1012 @@
+<?php
+/**
+ * This file is part of vfsStream.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @package  org\bovigo\vfs
+ */
+namespace org\bovigo\vfs;
+/**
+ * Stream wrapper to mock file system requests.
+ */
+class vfsStreamWrapper
+{
+    /**
+     * open file for reading
+     */
+    const READ                   = 'r';
+    /**
+     * truncate file
+     */
+    const TRUNCATE               = 'w';
+    /**
+     * set file pointer to end, append new data
+     */
+    const APPEND                 = 'a';
+    /**
+     * set file pointer to start, overwrite existing data
+     */
+    const WRITE                  = 'x';
+    /**
+     * set file pointer to start, overwrite existing data; or create file if
+     * does not exist
+     */
+    const WRITE_NEW              = 'c';
+    /**
+     * file mode: read only
+     */
+    const READONLY               = 0;
+    /**
+     * file mode: write only
+     */
+    const WRITEONLY              = 1;
+    /**
+     * file mode: read and write
+     */
+    const ALL                    = 2;
+    /**
+     * switch whether class has already been registered as stream wrapper or not
+     *
+     * @type  bool
+     */
+    protected static $registered = false;
+    /**
+     * root content
+     *
+     * @type  vfsStreamContent
+     */
+    protected static $root;
+    /**
+     * disk space quota
+     *
+     * @type  Quota
+     */
+    private static $quota;
+    /**
+     * file mode: read only, write only, all
+     *
+     * @type  int
+     */
+    protected $mode;
+    /**
+     * shortcut to file container
+     *
+     * @type  vfsStreamFile
+     */
+    protected $content;
+    /**
+     * shortcut to directory container
+     *
+     * @type  vfsStreamDirectory
+     */
+    protected $dir;
+    /**
+     * shortcut to directory container iterator
+     *
+     * @type  vfsStreamDirectory
+     */
+    protected $dirIterator;
+
+    /**
+     * method to register the stream wrapper
+     *
+     * Please be aware that a call to this method will reset the root element
+     * to null.
+     * If the stream is already registered the method returns silently. If there
+     * is already another stream wrapper registered for the scheme used by
+     * vfsStream a vfsStreamException will be thrown.
+     *
+     * @throws  vfsStreamException
+     */
+    public static function register()
+    {
+        self::$root  = null;
+        self::$quota = Quota::unlimited();
+        if (true === self::$registered) {
+            return;
+        }
+
+        if (@stream_wrapper_register(vfsStream::SCHEME, __CLASS__) === false) {
+            throw new vfsStreamException('A handler has already been registered for the ' . vfsStream::SCHEME . ' protocol.');
+        }
+
+        self::$registered = true;
+    }
+
+    /**
+     * Unregisters a previously registered URL wrapper for the vfs scheme.
+     *
+     * If this stream wrapper wasn't registered, the method returns silently.
+     *
+     * If unregistering fails, or if the URL wrapper for vfs:// was not
+     * registered with this class, a vfsStreamException will be thrown.
+     *
+     * @throws vfsStreamException
+     * @since  1.6.0
+     */
+    public static function unregister()
+    {
+        if (!self::$registered) {
+            if (in_array(vfsStream::SCHEME, stream_get_wrappers())) {
+                throw new vfsStreamException('The URL wrapper for the protocol ' . vfsStream::SCHEME . ' was not registered with this version of vfsStream.');
+            }
+            return;
+        }
+
+        if (!@stream_wrapper_unregister(vfsStream::SCHEME)) {
+            throw new vfsStreamException('Failed to unregister the URL wrapper for the ' . vfsStream::SCHEME . ' protocol.');
+        }
+
+        self::$registered = false;
+    }
+
+    /**
+     * sets the root content
+     *
+     * @param   vfsStreamContainer  $root
+     * @return  vfsStreamContainer
+     */
+    public static function setRoot(vfsStreamContainer $root)
+    {
+        self::$root = $root;
+        clearstatcache();
+        return self::$root;
+    }
+
+    /**
+     * returns the root content
+     *
+     * @return  vfsStreamContainer
+     */
+    public static function getRoot()
+    {
+        return self::$root;
+    }
+
+    /**
+     * sets quota for disk space
+     *
+     * @param  Quota  $quota
+     * @since  1.1.0
+     */
+    public static function setQuota(Quota $quota)
+    {
+        self::$quota = $quota;
+    }
+
+    /**
+     * returns content for given path
+     *
+     * @param   string  $path
+     * @return  vfsStreamContent
+     */
+    protected function getContent($path)
+    {
+        if (null === self::$root) {
+            return null;
+        }
+
+        if (self::$root->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);
+    }
+}