diff --git a/core/core.services.yml b/core/core.services.yml
index 6ab79570bad8f135801d58f8b40933a7c8fa08dc..29a734ae62246191160f24290693b305bb7130e9 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -189,6 +189,9 @@ services:
     factory_class: Drupal\Core\Database\Database
     factory_method: getConnection
     arguments: [default]
+  file_system:
+    class: Drupal\Core\File\FileSystem
+    arguments: ['@stream_wrapper_manager', '@settings', '@logger.channel.file']
   form_builder:
     class: Drupal\Core\Form\FormBuilder
     arguments: ['@form_validator', '@form_submitter', '@form_cache', '@module_handler', '@event_dispatcher', '@request_stack', '@class_resolver', '@theme.manager', '@?csrf_token']
@@ -236,6 +239,11 @@ services:
   logger.channel.cron:
     parent: logger.channel_base
     arguments: ['cron']
+  logger.channel.file:
+    class: Drupal\Core\Logger\LoggerChannel
+    factory_method: get
+    factory_service: logger.factory
+    arguments: ['file']
   logger.channel.form:
     parent: logger.channel_base
     arguments: ['form']
diff --git a/core/includes/file.inc b/core/includes/file.inc
index d47fbc44f054c1930a4ae6311c39b155c1bc9a6e..034a997459d4eeccb633865eba3899db18ac02bc 100644
--- a/core/includes/file.inc
+++ b/core/includes/file.inc
@@ -10,20 +10,26 @@
 use Drupal\Component\PhpStorage\FileStorage;
 use Drupal\Component\Utility\Bytes;
 use Drupal\Component\Utility\String;
-use Drupal\Core\Site\Settings;
+use Drupal\Core\File\FileSystem;
 use Drupal\Core\StreamWrapper\PublicStream;
 use Drupal\Core\StreamWrapper\StreamWrapperInterface;
 use Drupal\Core\StreamWrapper\PrivateStream;
 
 /**
  * Default mode for new directories. See drupal_chmod().
+ *
+ * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
+ *   Use \Drupal\Core\File\FileSystem::CHMOD_DIRECTORY.
  */
-const FILE_CHMOD_DIRECTORY = 0775;
+const FILE_CHMOD_DIRECTORY = FileSystem::CHMOD_DIRECTORY;
 
 /**
  * Default mode for new files. See drupal_chmod().
+ *
+ * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
+ *   Use \Drupal\Core\File\FileSystem::CHMOD_FILE.
  */
-const FILE_CHMOD_FILE = 0664;
+const FILE_CHMOD_FILE = FileSystem::CHMOD_FILE;
 
 /**
  * @defgroup file File interface
@@ -129,40 +135,21 @@ function file_stream_wrapper_get_class($scheme) {
 /**
  * Returns the scheme of a URI (e.g. a stream).
  *
- * @param string $uri
- *   A stream, referenced as "scheme://target"  or "data:target".
- *
- * @return string
- *   A string containing the name of the scheme, or FALSE if none. For example,
- *   the URI "public://example.txt" would return "public".
- *
- * @see file_uri_target()
+ * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
+ *   Use \Drupal\Core\File\FileSystem::uriScheme().
  */
 function file_uri_scheme($uri) {
-  if (preg_match('/^([\w\-]+):\/\/|^(data):/', $uri, $matches)) {
-    // The scheme will always be the last element in the matches array.
-    return array_pop($matches);
-  }
-
-  return FALSE;
+  return \Drupal::service('file_system')->uriScheme($uri);
 }
 
 /**
  * Checks that the scheme of a stream URI is valid.
  *
- * Confirms that there is a registered stream handler for the provided scheme
- * and that it is callable. This is useful if you want to confirm a valid
- * scheme without creating a new instance of the registered handler.
- *
- * @param string $scheme
- *   A URI scheme, a stream is referenced as "scheme://target".
- *
- * @return bool
- *   Returns TRUE if the string is the name of a validated stream,
- *   or FALSE if the scheme does not have a registered handler.
+ * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
+ *   Use \Drupal\Core\File\FileSystem::validScheme().
  */
 function file_stream_wrapper_valid_scheme($scheme) {
-  return $scheme && class_exists(file_stream_wrapper_get_class($scheme));
+  return \Drupal::service('file_system')->validScheme($scheme);
 }
 
 
@@ -957,38 +944,11 @@ function file_unmanaged_delete_recursive($path, $callback = NULL) {
 /**
  * Moves an uploaded file to a new location.
  *
- * PHP's move_uploaded_file() does not properly support streams if open_basedir
- * is enabled, so this function fills that gap.
- *
- * Compatibility: normal paths and stream wrappers.
- *
- * @param $filename
- *   The filename of the uploaded file.
- * @param $uri
- *   A string containing the destination URI of the file.
- *
- * @return
- *   TRUE on success, or FALSE on failure.
- *
- * @see move_uploaded_file()
- * @see http://drupal.org/node/515192
- * @ingroup php_wrappers
+ * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
+ *   Use \Drupal\Core\File\FileSystem::moveUploadedFile().
  */
 function drupal_move_uploaded_file($filename, $uri) {
-  $result = @move_uploaded_file($filename, $uri);
-  // PHP's move_uploaded_file() does not properly support streams if
-  // open_basedir is enabled so if the move failed, try finding a real path and
-  // retry the move operation.
-  if (!$result) {
-    if ($realpath = drupal_realpath($uri)) {
-      $result = move_uploaded_file($filename, $realpath);
-    }
-    else {
-      $result = move_uploaded_file($filename, $uri);
-    }
-  }
-
-  return $result;
+  return \Drupal::service('file_system')->moveUploadedFile($filename, $uri);
 }
 
 /**
@@ -1179,338 +1139,82 @@ function file_get_mimetype($uri, $mapping = NULL) {
 /**
  * Sets the permissions on a file or directory.
  *
- * This function will use the file_chmod_directory and
- * file_chmod_file settings for the default modes for directories
- * and uploaded/generated files. By default these will give everyone read access
- * so that users accessing the files with a user account without the webserver
- * group (e.g. via FTP) can read these files, and give group write permissions
- * so webserver group members (e.g. a vhost account) can alter files uploaded
- * and owned by the webserver.
- *
- * PHP's chmod does not support stream wrappers so we use our wrapper
- * implementation which interfaces with chmod() by default. Contrib wrappers
- * may override this behavior in their implementations as needed.
- *
- * @param $uri
- *   A string containing a URI file, or directory path.
- * @param $mode
- *   Integer value for the permissions. Consult PHP chmod() documentation for
- *   more information.
- *
- * @return bool
- *   TRUE for success, FALSE in the event of an error.
- *
- * @ingroup php_wrappers
+ * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
+ *   Use \Drupal\Core\File\FileSystem::chmod().
  */
 function drupal_chmod($uri, $mode = NULL) {
-  if (!isset($mode)) {
-    if (is_dir($uri)) {
-      $mode = Settings::get('file_chmod_directory', FILE_CHMOD_DIRECTORY);
-    }
-    else {
-      $mode = Settings::get('file_chmod_file', FILE_CHMOD_FILE);
-    }
-  }
-
-  if (@chmod($uri, $mode)) {
-    return TRUE;
-  }
-
-  \Drupal::logger('file')->error('The file permissions could not be set on %uri.', array('%uri' => $uri));
-  return FALSE;
+  return \Drupal::service('file_system')->chmod($uri, $mode);
 }
 
 /**
  * Deletes a file.
  *
- * PHP's unlink() is broken on Windows, as it can fail to remove a file
- * when it has a read-only flag set.
- *
- * @param $uri
- *   A URI or pathname.
- * @param $context
- *   Refer to http://php.net/manual/ref.stream.php
- *
- * @return
- *   Boolean TRUE on success, or FALSE on failure.
- *
- * @see unlink()
- * @ingroup php_wrappers
+ * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
+ *   Use \Drupal\Core\File\FileSystem::unlink().
  */
 function drupal_unlink($uri, $context = NULL) {
-  $scheme = file_uri_scheme($uri);
-  if (!file_stream_wrapper_valid_scheme($scheme) && (substr(PHP_OS, 0, 3) == 'WIN')) {
-    chmod($uri, 0600);
-  }
-  if ($context) {
-    return unlink($uri, $context);
-  }
-  else {
-    return unlink($uri);
-  }
+  return \Drupal::service('file_system')->unlink($uri, $context);
 }
 
 /**
  * Resolves the absolute filepath of a local URI or filepath.
  *
- * The use of drupal_realpath() is discouraged, because it does not work for
- * remote URIs. Except in rare cases, URIs should not be manually resolved.
- *
- * Only use this function if you know that the stream wrapper in the URI uses
- * the local file system, and you need to pass an absolute path to a function
- * that is incompatible with stream URIs.
- *
- * @param string $uri
- *   A stream wrapper URI or a filepath, possibly including one or more symbolic
- *   links.
- *
- * @return string|false
- *   The absolute local filepath (with no symbolic links), or FALSE on failure.
- *
- * @see \Drupal\Core\StreamWrapper\StreamWrapperInterface::realpath()
- * @see http://php.net/manual/function.realpath.php
- * @ingroup php_wrappers
+ * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
+ *   Use \Drupal\Core\File\FileSystem::realpath().
  */
 function drupal_realpath($uri) {
-  // If this URI is a stream, pass it off to the appropriate stream wrapper.
-  // Otherwise, attempt PHP's realpath. This allows use of drupal_realpath even
-  // for unmanaged files outside of the stream wrapper interface.
-  if ($wrapper = file_stream_wrapper_get_instance_by_uri($uri)) {
-    return $wrapper->realpath();
-  }
-
-  return realpath($uri);
+  return \Drupal::service('file_system')->realpath($uri);
 }
 
 /**
  * Gets the name of the directory from a given path.
  *
- * PHP's dirname() does not properly pass streams, so this function fills
- * that gap. It is backwards compatible with normal paths and will use
- * PHP's dirname() as a fallback.
- *
- * Compatibility: normal paths and stream wrappers.
- *
- * @param $uri
- *   A URI or path.
- *
- * @return
- *   A string containing the directory name.
- *
- * @see dirname()
- * @see http://drupal.org/node/515192
- * @ingroup php_wrappers
+ * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
+ *   Use \Drupal\Core\File\FileSystem::dirname().
  */
 function drupal_dirname($uri) {
-  $scheme = file_uri_scheme($uri);
-
-  if (file_stream_wrapper_valid_scheme($scheme)) {
-    return file_stream_wrapper_get_instance_by_scheme($scheme)->dirname($uri);
-  }
-  else {
-    return dirname($uri);
-  }
+  return \Drupal::service('file_system')->dirname($uri);
 }
 
 /**
  * Gets the filename from a given path.
  *
- * PHP's basename() does not properly support streams or filenames beginning
- * with a non-US-ASCII character.
- *
- * @see http://bugs.php.net/bug.php?id=37738
- * @see basename()
- *
- * @ingroup php_wrappers
+ * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
+ *   Use \Drupal\Core\File\FileSystem::basename().
  */
 function drupal_basename($uri, $suffix = NULL) {
-  $separators = '/';
-  if (DIRECTORY_SEPARATOR != '/') {
-    // For Windows OS add special separator.
-    $separators .= DIRECTORY_SEPARATOR;
-  }
-  // Remove right-most slashes when $uri points to directory.
-  $uri = rtrim($uri, $separators);
-  // Returns the trailing part of the $uri starting after one of the directory
-  // separators.
-  $filename = preg_match('@[^' . preg_quote($separators, '@') . ']+$@', $uri, $matches) ? $matches[0] : '';
-  // Cuts off a suffix from the filename.
-  if ($suffix) {
-    $filename = preg_replace('@' . preg_quote($suffix, '@') . '$@', '', $filename);
-  }
-  return $filename;
+  return \Drupal::service('file_system')->basename($uri, $suffix);
 }
 
 /**
  * Creates a directory, optionally creating missing components in the path to
  * the directory.
  *
- * When PHP's mkdir() creates a directory, the requested mode is affected by the
- * process's umask. This function overrides the umask and sets the mode
- * explicitly for all directory components created.
- *
- * @param $uri
- *   A URI or pathname.
- * @param $mode
- *   Mode given to created directories. Defaults to the directory mode
- *   configured in the Drupal installation. It must have a leading zero.
- * @param $recursive
- *   Create directories recursively, defaults to FALSE. Cannot work with a mode
- *   which denies writing or execution to the owner of the process.
- * @param $context
- *   Refer to http://php.net/manual/ref.stream.php
- *
- * @return
- *   Boolean TRUE on success, or FALSE on failure.
- *
- * @see mkdir()
- * @see http://drupal.org/node/515192
- * @ingroup php_wrappers
- *
- * @todo Update with open_basedir compatible recursion logic from
- *   \Drupal\Component\PhpStorage\FileStorage::ensureDirectory().
+ * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
+ *   Use \Drupal\Core\File\FileSystem::mkdir().
  */
 function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) {
-  if (!isset($mode)) {
-    $mode = Settings::get('file_chmod_directory', FILE_CHMOD_DIRECTORY);
-  }
-
-  // If the URI has a scheme, don't override the umask - schemes can handle this
-  // issue in their own implementation.
-  if (file_uri_scheme($uri)) {
-    return _drupal_mkdir_call($uri, $mode, $recursive, $context);
-  }
-
-  // If recursive, create each missing component of the parent directory
-  // individually and set the mode explicitly to override the umask.
-  if ($recursive) {
-    // Ensure the path is using DIRECTORY_SEPARATOR.
-    $uri = str_replace('/', DIRECTORY_SEPARATOR, $uri);
-    // Determine the components of the path.
-    $components = explode(DIRECTORY_SEPARATOR, $uri);
-    // If the filepath is absolute the first component will be empty as there
-    // will be nothing before the first slash.
-    if ($components[0] == '') {
-      $recursive_path = DIRECTORY_SEPARATOR;
-      // Get rid of the empty first component.
-      array_shift($components);
-    }
-    else {
-      $recursive_path = '';
-    }
-    // Don't handle the top-level directory in this loop.
-    array_pop($components);
-    // Create each component if necessary.
-    foreach ($components as $component) {
-      $recursive_path .= $component;
-
-      if (!file_exists($recursive_path)) {
-        if (!_drupal_mkdir_call($recursive_path, $mode, FALSE, $context)) {
-          return FALSE;
-        }
-        // Not necessary to use drupal_chmod() as there is no scheme.
-        if (!chmod($recursive_path, $mode)) {
-          return FALSE;
-        }
-      }
-
-      $recursive_path .= DIRECTORY_SEPARATOR;
-    }
-  }
-
-  // Do not check if the top-level directory already exists, as this condition
-  // must cause this function to fail.
-  if (!_drupal_mkdir_call($uri, $mode, FALSE, $context)) {
-    return FALSE;
-  }
-  // Not necessary to use drupal_chmod() as there is no scheme.
-  return chmod($uri, $mode);
-}
-
-/**
- * Helper function. Ensures we don't pass a NULL as a context resource to
- * mkdir().
- *
- * @see drupal_mkdir()
- */
-function _drupal_mkdir_call($uri, $mode, $recursive, $context) {
-  if (is_null($context)) {
-    return mkdir($uri, $mode, $recursive);
-  }
-  else {
-    return mkdir($uri, $mode, $recursive, $context);
-  }
+  return \Drupal::service('file_system')->mkdir($uri, $mode, $recursive, $context);
 }
 
 /**
  * Removes a directory.
  *
- * PHP's rmdir() is broken on Windows, as it can fail to remove a directory
- * when it has a read-only flag set.
- *
- * @param $uri
- *   A URI or pathname.
- * @param $context
- *   Refer to http://php.net/manual/ref.stream.php
- *
- * @return
- *   Boolean TRUE on success, or FALSE on failure.
- *
- * @see rmdir()
- * @ingroup php_wrappers
+ * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
+ *   Use \Drupal\Core\File\FileSystem::rmdir().
  */
 function drupal_rmdir($uri, $context = NULL) {
-  $scheme = file_uri_scheme($uri);
-  if (!file_stream_wrapper_valid_scheme($scheme) && (substr(PHP_OS, 0, 3) == 'WIN')) {
-    chmod($uri, 0700);
-  }
-  if ($context) {
-    return rmdir($uri, $context);
-  }
-  else {
-    return rmdir($uri);
-  }
+  return \Drupal::service('file_system')->rmdir($uri, $context);
 }
 
 /**
  * Creates a file with a unique filename in the specified directory.
  *
- * PHP's tempnam() does not return a URI like we want. This function
- * will return a URI if given a URI, or it will return a filepath if
- * given a filepath.
- *
- * Compatibility: normal paths and stream wrappers.
- *
- * @param $directory
- *   The directory where the temporary filename will be created.
- * @param $prefix
- *   The prefix of the generated temporary filename.
- *   Note: Windows uses only the first three characters of prefix.
- *
- * @return
- *   The new temporary filename, or FALSE on failure.
- *
- * @see tempnam()
- * @see http://drupal.org/node/515192
- * @ingroup php_wrappers
+ * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
+ *   Use \Drupal\Core\File\FileSystem::tempnam().
  */
 function drupal_tempnam($directory, $prefix) {
-  $scheme = file_uri_scheme($directory);
-
-  if (file_stream_wrapper_valid_scheme($scheme)) {
-    $wrapper = file_stream_wrapper_get_instance_by_scheme($scheme);
-
-    if ($filename = tempnam($wrapper->getDirectoryPath(), $prefix)) {
-      return $scheme . '://' . drupal_basename($filename);
-    }
-    else {
-      return FALSE;
-    }
-  }
-  else {
-    // Handle as a normal tempnam() call.
-    return tempnam($directory, $prefix);
-  }
+  return \Drupal::service('file_system')->tempnam($directory, $prefix);
 }
 
 /**
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 4191cdf001914f30ac37964fecb1ec5a033fcfe5..f98c64d53bf61785c463a88d2f70fc73ec21dc25 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -14,6 +14,7 @@
 use Drupal\Core\Installer\InstallerKernel;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Language\LanguageManager;
+use Drupal\Core\Logger\LoggerChannelFactory;
 use Drupal\Core\Site\Settings;
 use Drupal\Core\StringTranslation\Translator\FileTranslation;
 use Drupal\Core\Extension\ExtensionDiscovery;
@@ -343,8 +344,12 @@ function install_begin_request($class_loader, &$install_state) {
   // Register the stream wrapper manager.
   $container
     ->register('stream_wrapper_manager', 'Drupal\Core\StreamWrapper\StreamWrapperManager')
-    ->addArgument(new Reference('module_handler'))
     ->addMethodCall('setContainer', array(new Reference('service_container')));
+  $container
+    ->register('file_system', 'Drupal\Core\File\FileSystem')
+    ->addArgument(new Reference('stream_wrapper_manager'))
+    ->addArgument(Settings::getInstance())
+    ->addArgument((new LoggerChannelFactory())->get('file'));
 
   \Drupal::setContainer($container);
 
diff --git a/core/lib/Drupal/Core/File/FileSystem.php b/core/lib/Drupal/Core/File/FileSystem.php
new file mode 100644
index 0000000000000000000000000000000000000000..aedfdf6faaf820294728e608ebbdb6c1b4e9ea29
--- /dev/null
+++ b/core/lib/Drupal/Core/File/FileSystem.php
@@ -0,0 +1,307 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\File\FileSystem.
+ */
+
+namespace Drupal\Core\File;
+
+use Drupal\Core\Site\Settings;
+use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Provides helpers to operate on files and stream wrappers.
+ */
+class FileSystem implements FileSystemInterface {
+
+  /**
+   * Default mode for new directories. See self::chmod().
+   */
+  const CHMOD_DIRECTORY = 0775;
+
+  /**
+   * Default mode for new files. See self::chmod().
+   */
+  const CHMOD_FILE = 0664;
+
+  /**
+   * The site settings.
+   *
+   * @var \Drupal\Core\Site\Settings
+   */
+  protected $settings;
+
+  /**
+   * The file logger channel.
+   *
+   * @var \Psr\Log\LoggerInterface
+   */
+  protected $logger;
+
+  /**
+   * The stream wrapper manager.
+   *
+   * @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
+   */
+  protected $streamWrapperManager;
+
+  /**
+   * Constructs a new FileSystem.
+   *
+   * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager
+   *   The stream wrapper manager.
+   * @param \Drupal\Core\Site\Settings $settings
+   *   The site settings.
+   * @param \Psr\Log\LoggerInterface $logger
+   *   The file logger channel.
+   */
+  public function __construct(StreamWrapperManagerInterface $stream_wrapper_manager, Settings $settings, LoggerInterface $logger) {
+    $this->streamWrapperManager = $stream_wrapper_manager;
+    $this->settings = $settings;
+    $this->logger = $logger;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function moveUploadedFile($filename, $uri) {
+    $result = @move_uploaded_file($filename, $uri);
+    // PHP's move_uploaded_file() does not properly support streams if
+    // open_basedir is enabled so if the move failed, try finding a real path
+    // and retry the move operation.
+    if (!$result) {
+      if ($realpath = $this->realpath($uri)) {
+        $result = move_uploaded_file($filename, $realpath);
+      }
+      else {
+        $result = move_uploaded_file($filename, $uri);
+      }
+    }
+
+    return $result;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function chmod($uri, $mode = NULL) {
+    if (!isset($mode)) {
+      if (is_dir($uri)) {
+        $mode = $this->settings->get('file_chmod_directory', static::CHMOD_DIRECTORY);
+      }
+      else {
+        $mode = $this->settings->get('file_chmod_file', static::CHMOD_FILE);
+      }
+    }
+
+    if (@chmod($uri, $mode)) {
+      return TRUE;
+    }
+
+    $this->logger->error('The file permissions could not be set on %uri.', array('%uri' => $uri));
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function unlink($uri, $context = NULL) {
+    $scheme = $this->uriScheme($uri);
+    if (!$this->validScheme($scheme) && (substr(PHP_OS, 0, 3) == 'WIN')) {
+      chmod($uri, 0600);
+    }
+    if ($context) {
+      return unlink($uri, $context);
+    }
+    else {
+      return unlink($uri);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function realpath($uri) {
+    // If this URI is a stream, pass it off to the appropriate stream wrapper.
+    // Otherwise, attempt PHP's realpath. This allows use of this method even
+    // for unmanaged files outside of the stream wrapper interface.
+    if ($wrapper = $this->streamWrapperManager->getViaUri($uri)) {
+      return $wrapper->realpath();
+    }
+
+    return realpath($uri);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function dirname($uri) {
+    $scheme = $this->uriScheme($uri);
+
+    if ($this->validScheme($scheme)) {
+      return $this->streamWrapperManager->getViaScheme($scheme)->dirname($uri);
+    }
+    else {
+      return dirname($uri);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function basename($uri, $suffix = NULL) {
+    $separators = '/';
+    if (DIRECTORY_SEPARATOR != '/') {
+      // For Windows OS add special separator.
+      $separators .= DIRECTORY_SEPARATOR;
+    }
+    // Remove right-most slashes when $uri points to directory.
+    $uri = rtrim($uri, $separators);
+    // Returns the trailing part of the $uri starting after one of the directory
+    // separators.
+    $filename = preg_match('@[^' . preg_quote($separators, '@') . ']+$@', $uri, $matches) ? $matches[0] : '';
+    // Cuts off a suffix from the filename.
+    if ($suffix) {
+      $filename = preg_replace('@' . preg_quote($suffix, '@') . '$@', '', $filename);
+    }
+    return $filename;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) {
+    if (!isset($mode)) {
+      $mode = $this->settings->get('file_chmod_directory', static::CHMOD_DIRECTORY);
+    }
+
+    // If the URI has a scheme, don't override the umask - schemes can handle
+    // this issue in their own implementation.
+    if ($this->uriScheme($uri)) {
+      return $this->mkdirCall($uri, $mode, $recursive, $context);
+    }
+
+    // If recursive, create each missing component of the parent directory
+    // individually and set the mode explicitly to override the umask.
+    if ($recursive) {
+      // Ensure the path is using DIRECTORY_SEPARATOR.
+      $uri = str_replace('/', DIRECTORY_SEPARATOR, $uri);
+      // Determine the components of the path.
+      $components = explode(DIRECTORY_SEPARATOR, $uri);
+      // If the filepath is absolute the first component will be empty as there
+      // will be nothing before the first slash.
+      if ($components[0] == '') {
+        $recursive_path = DIRECTORY_SEPARATOR;
+        // Get rid of the empty first component.
+        array_shift($components);
+      }
+      else {
+        $recursive_path = '';
+      }
+      // Don't handle the top-level directory in this loop.
+      array_pop($components);
+      // Create each component if necessary.
+      foreach ($components as $component) {
+        $recursive_path .= $component;
+
+        if (!file_exists($recursive_path)) {
+          if (!$this->mkdirCall($recursive_path, $mode, FALSE, $context)) {
+            return FALSE;
+          }
+          // Not necessary to use self::chmod() as there is no scheme.
+          if (!chmod($recursive_path, $mode)) {
+            return FALSE;
+          }
+        }
+
+        $recursive_path .= DIRECTORY_SEPARATOR;
+      }
+    }
+
+    // Do not check if the top-level directory already exists, as this condition
+    // must cause this function to fail.
+    if (!$this->mkdirCall($uri, $mode, FALSE, $context)) {
+      return FALSE;
+    }
+    // Not necessary to use self::chmod() as there is no scheme.
+    return chmod($uri, $mode);
+  }
+
+  /**
+   * Helper function. Ensures we don't pass a NULL as a context resource to
+   * mkdir().
+   *
+   * @see self::mkdir()
+   */
+  protected function mkdirCall($uri, $mode, $recursive, $context) {
+    if (is_null($context)) {
+      return mkdir($uri, $mode, $recursive);
+    }
+    else {
+      return mkdir($uri, $mode, $recursive, $context);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rmdir($uri, $context = NULL) {
+    $scheme = $this->uriScheme($uri);
+    if (!$this->validScheme($scheme) && (substr(PHP_OS, 0, 3) == 'WIN')) {
+      chmod($uri, 0700);
+    }
+    if ($context) {
+      return rmdir($uri, $context);
+    }
+    else {
+      return rmdir($uri);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function tempnam($directory, $prefix) {
+    $scheme = $this->uriScheme($directory);
+
+    if ($this->validScheme($scheme)) {
+      $wrapper = $this->streamWrapperManager->getViaScheme($scheme);
+
+      if ($filename = tempnam($wrapper->getDirectoryPath(), $prefix)) {
+        return $scheme . '://' . static::basename($filename);
+      }
+      else {
+        return FALSE;
+      }
+    }
+    else {
+      // Handle as a normal tempnam() call.
+      return tempnam($directory, $prefix);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function uriScheme($uri) {
+    if (preg_match('/^([\w\-]+):\/\/|^(data):/', $uri, $matches)) {
+      // The scheme will always be the last element in the matches array.
+      return array_pop($matches);
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validScheme($scheme) {
+    if (!$scheme) {
+      return FALSE;
+    }
+    return class_exists($this->streamWrapperManager->getClass($scheme));
+  }
+
+}
diff --git a/core/lib/Drupal/Core/File/FileSystemInterface.php b/core/lib/Drupal/Core/File/FileSystemInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..57009039c1d01552d8e6242782930ba047246fdf
--- /dev/null
+++ b/core/lib/Drupal/Core/File/FileSystemInterface.php
@@ -0,0 +1,245 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\File\FileSystemInterface.
+ */
+
+namespace Drupal\Core\File;
+
+/**
+ * Provides an interface for helpers that operate on files and stream wrappers.
+ */
+interface FileSystemInterface {
+
+  /**
+   * Moves an uploaded file to a new location.
+   *
+   * PHP's move_uploaded_file() does not properly support streams if
+   * open_basedir is enabled, so this function fills that gap.
+   *
+   * Compatibility: normal paths and stream wrappers.
+   *
+   * @param string $filename
+   *   The filename of the uploaded file.
+   * @param string $uri
+   *   A string containing the destination URI of the file.
+   *
+   * @return bool
+   *   TRUE on success, or FALSE on failure.
+   *
+   * @see move_uploaded_file()
+   * @see http://drupal.org/node/515192
+   * @ingroup php_wrappers
+   */
+  public function moveUploadedFile($filename, $uri);
+
+  /**
+   * Sets the permissions on a file or directory.
+   *
+   * This function will use the file_chmod_directory and
+   * file_chmod_file settings for the default modes for directories
+   * and uploaded/generated files. By default these will give everyone read
+   * access so that users accessing the files with a user account without the
+   * webserver group (e.g. via FTP) can read these files, and give group write
+   * permissions so webserver group members (e.g. a vhost account) can alter
+   * files uploaded and owned by the webserver.
+   *
+   * PHP's chmod does not support stream wrappers so we use our wrapper
+   * implementation which interfaces with chmod() by default. Contrib wrappers
+   * may override this behavior in their implementations as needed.
+   *
+   * @param string $uri
+   *   A string containing a URI file, or directory path.
+   * @param int $mode
+   *   Integer value for the permissions. Consult PHP chmod() documentation for
+   *   more information.
+   *
+   * @return bool
+   *   TRUE for success, FALSE in the event of an error.
+   *
+   * @ingroup php_wrappers
+   */
+  public function chmod($uri, $mode = NULL);
+
+  /**
+   * Deletes a file.
+   *
+   * PHP's unlink() is broken on Windows, as it can fail to remove a file when
+   * it has a read-only flag set.
+   *
+   * @param string $uri
+   *   A URI or pathname.
+   * @param resource $context
+   *   Refer to http://php.net/manual/ref.stream.php
+   *
+   * @return bool
+   *   Boolean TRUE on success, or FALSE on failure.
+   *
+   * @see unlink()
+   * @ingroup php_wrappers
+   */
+  public function unlink($uri, $context = NULL);
+
+  /**
+   * Resolves the absolute filepath of a local URI or filepath.
+   *
+   * The use of this method is discouraged, because it does not work for
+   * remote URIs. Except in rare cases, URIs should not be manually resolved.
+   *
+   * Only use this function if you know that the stream wrapper in the URI uses
+   * the local file system, and you need to pass an absolute path to a function
+   * that is incompatible with stream URIs.
+   *
+   * @param string $uri
+   *   A stream wrapper URI or a filepath, possibly including one or more
+   *   symbolic links.
+   *
+   * @return string|false
+   *   The absolute local filepath (with no symbolic links) or FALSE on failure.
+   *
+   * @see \Drupal\Core\StreamWrapper\StreamWrapperInterface::realpath()
+   * @see http://php.net/manual/function.realpath.php
+   * @ingroup php_wrappers
+   */
+  public function realpath($uri);
+
+  /**
+   * Gets the name of the directory from a given path.
+   *
+   * PHP's dirname() does not properly pass streams, so this function fills that
+   * gap. It is backwards compatible with normal paths and will use PHP's
+   * dirname() as a fallback.
+   *
+   * Compatibility: normal paths and stream wrappers.
+   *
+   * @param string $uri
+   *   A URI or path.
+   *
+   * @return string
+   *   A string containing the directory name.
+   *
+   * @see dirname()
+   * @see http://drupal.org/node/515192
+   * @ingroup php_wrappers
+   */
+  public function dirname($uri);
+
+  /**
+   * Gets the filename from a given path.
+   *
+   * PHP's basename() does not properly support streams or filenames beginning
+   * with a non-US-ASCII character.
+   *
+   * @see http://bugs.php.net/bug.php?id=37738
+   * @see basename()
+   *
+   * @ingroup php_wrappers
+   */
+  public function basename($uri, $suffix = NULL);
+
+  /**
+   * Creates a directory, optionally creating missing components in the path to
+   * the directory.
+   *
+   * When PHP's mkdir() creates a directory, the requested mode is affected by
+   * the process's umask. This function overrides the umask and sets the mode
+   * explicitly for all directory components created.
+   *
+   * @param string $uri
+   *   A URI or pathname.
+   * @param int $mode
+   *   Mode given to created directories. Defaults to the directory mode
+   *   configured in the Drupal installation. It must have a leading zero.
+   * @param bool $recursive
+   *   Create directories recursively, defaults to FALSE. Cannot work with a
+   *   mode which denies writing or execution to the owner of the process.
+   * @param resource $context
+   *   Refer to http://php.net/manual/ref.stream.php
+   *
+   * @return bool
+   *   Boolean TRUE on success, or FALSE on failure.
+   *
+   * @see mkdir()
+   * @see http://drupal.org/node/515192
+   * @ingroup php_wrappers
+   *
+   * @todo Update with open_basedir compatible recursion logic from
+   *   \Drupal\Component\PhpStorage\FileStorage::ensureDirectory().
+   */
+  public function mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL);
+
+  /**
+   * Removes a directory.
+   *
+   * PHP's rmdir() is broken on Windows, as it can fail to remove a directory
+   * when it has a read-only flag set.
+   *
+   * @param string $uri
+   *   A URI or pathname.
+   * @param resource $context
+   *   Refer to http://php.net/manual/ref.stream.php
+   *
+   * @return bool
+   *   Boolean TRUE on success, or FALSE on failure.
+   *
+   * @see rmdir()
+   * @ingroup php_wrappers
+   */
+  public function rmdir($uri, $context = NULL);
+
+  /**
+   * Creates a file with a unique filename in the specified directory.
+   *
+   * PHP's tempnam() does not return a URI like we want. This function will
+   * return a URI if given a URI, or it will return a filepath if given a
+   * filepath.
+   *
+   * Compatibility: normal paths and stream wrappers.
+   *
+   * @param string $directory
+   *   The directory where the temporary filename will be created.
+   * @param string $prefix
+   *   The prefix of the generated temporary filename.
+   *   Note: Windows uses only the first three characters of prefix.
+   *
+   * @return string|bool
+   *   The new temporary filename, or FALSE on failure.
+   *
+   * @see tempnam()
+   * @see http://drupal.org/node/515192
+   * @ingroup php_wrappers
+   */
+  public function tempnam($directory, $prefix);
+
+  /**
+   * Returns the scheme of a URI (e.g. a stream).
+   *
+   * @param string $uri
+   *   A stream, referenced as "scheme://target" or "data:target".
+   *
+   * @return string|bool
+   *   A string containing the name of the scheme, or FALSE if none. For
+   *   example, the URI "public://example.txt" would return "public".
+   *
+   * @see file_uri_target()
+   */
+  public function uriScheme($uri);
+
+  /**
+   * Checks that the scheme of a stream URI is valid.
+   *
+   * Confirms that there is a registered stream handler for the provided scheme
+   * and that it is callable. This is useful if you want to confirm a valid
+   * scheme without creating a new instance of the registered handler.
+   *
+   * @param string $scheme
+   *   A URI scheme, a stream is referenced as "scheme://target".
+   *
+   * @return bool
+   *   Returns TRUE if the string is the name of a validated stream, or FALSE if
+   *   the scheme does not have a registered handler.
+   */
+  public function validScheme($scheme);
+
+}
diff --git a/core/lib/Drupal/Core/StreamWrapper/StreamWrapperManager.php b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperManager.php
index 101980d4055b4f7e208bcccda9e7566b338864d3..dc9f4b6c4a21fa701d71d795744fb7d3d24250fb 100644
--- a/core/lib/Drupal/Core/StreamWrapper/StreamWrapperManager.php
+++ b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperManager.php
@@ -7,7 +7,6 @@
 
 namespace Drupal\Core\StreamWrapper;
 
-use Drupal\Core\Extension\ModuleHandlerInterface;
 use Symfony\Component\DependencyInjection\ContainerAware;
 
 /**
@@ -16,7 +15,7 @@
  * @see file_get_stream_wrappers()
  * @see \Drupal\Core\StreamWrapper\StreamWrapperInterface
  */
-class StreamWrapperManager extends ContainerAware {
+class StreamWrapperManager extends ContainerAware implements StreamWrapperManagerInterface {
 
   /**
    * Contains stream wrapper info.
@@ -48,57 +47,7 @@ class StreamWrapperManager extends ContainerAware {
   protected $wrappers = array();
 
   /**
-   * Provides Drupal stream wrapper registry.
-   *
-   * A stream wrapper is an abstraction of a file system that allows Drupal to
-   * use the same set of methods to access both local files and remote
-   * resources.
-   *
-   * Provide a facility for managing and querying user-defined stream wrappers
-   * in PHP. PHP's internal stream_get_wrappers() doesn't return the class
-   * registered to handle a stream, which we need to be able to find the handler
-   * for class instantiation.
-   *
-   * If a module registers a scheme that is already registered with PHP, the
-   * existing scheme will be unregistered and replaced with the specified class.
-   *
-   * A stream is referenced as "scheme://target".
-   *
-   * The optional $filter parameter can be used to retrieve only the stream
-   * wrappers that are appropriate for particular usage. For example, this
-   * returns only stream wrappers that use local file storage:
-   *
-   * @code
-   *   $local_stream_wrappers = file_get_stream_wrappers(StreamWrapperInterface::LOCAL);
-   * @endcode
-   *
-   * The $filter parameter can only filter to types containing a particular
-   * flag. In some cases, you may want to filter to types that do not contain a
-   * particular flag. For example, you may want to retrieve all stream wrappers
-   * that are not writable, or all stream wrappers that are not local. PHP's
-   * array_diff_key() function can be used to help with this. For example, this
-   * returns only stream wrappers that do not use local file storage:
-   * @code
-   *   $remote_stream_wrappers = array_diff_key(file_get_stream_wrappers(StreamWrapperInterface::ALL), file_get_stream_wrappers(StreamWrapperInterface::LOCAL));
-   * @endcode
-   *
-   * @param int $filter
-   *   (Optional) Filters out all types except those with an on bit for each on
-   *   bit in $filter. For example, if $filter is
-   *   StreamWrapperInterface::WRITE_VISIBLE, which is equal to
-   *   (StreamWrapperInterface::READ | StreamWrapperInterface::WRITE |
-   *   StreamWrapperInterface::VISIBLE), then only stream wrappers with all
-   *   three of these bits set are returned. Defaults to
-   *   StreamWrapperInterface::ALL, which returns all registered stream
-   *   wrappers.
-   *
-   * @return array
-   *   An array keyed by scheme, with values containing an array of information
-   *   about the stream wrapper, as returned by hook_stream_wrappers(). If
-   *   $filter is omitted or set to StreamWrapperInterface::ALL, the entire
-   *   Drupal stream wrapper registry is returned. Otherwise only the stream
-   *   wrappers whose 'type' bitmask has an on bit for each bit specified in
-   *   $filter are returned.
+   * {@inheritdoc}
    */
   public function getWrappers($filter = StreamWrapperInterface::ALL) {
     if (isset($this->wrappers[$filter])) {
@@ -120,20 +69,7 @@ public function getWrappers($filter = StreamWrapperInterface::ALL) {
   }
 
   /**
-   * Returns registered stream wrapper names.
-   *
-   * @param int $filter
-   *   (Optional) Filters out all types except those with an on bit for each on
-   *   bit in $filter. For example, if $filter is
-   *   StreamWrapperInterface::WRITE_VISIBLE, which is equal to
-   *   (StreamWrapperInterface::READ | StreamWrapperInterface::WRITE |
-   *   StreamWrapperInterface::VISIBLE), then only stream wrappers with all
-   *   three of these bits set are returned. Defaults to
-   *   StreamWrapperInterface::ALL, which returns all registered stream
-   *   wrappers.
-   *
-   * @return array
-   *   Stream wrapper names, keyed by scheme.
+   * {@inheritdoc}
    */
   public function getNames($filter = StreamWrapperInterface::ALL) {
     $names = array();
@@ -145,20 +81,7 @@ public function getNames($filter = StreamWrapperInterface::ALL) {
   }
 
   /**
-   * Returns registered stream wrapper descriptions.
-   *
-   * @param int $filter
-   *   (Optional) Filters out all types except those with an on bit for each on
-   *   bit in $filter. For example, if $filter is
-   *   StreamWrapperInterface::WRITE_VISIBLE, which is equal to
-   *   (StreamWrapperInterface::READ | StreamWrapperInterface::WRITE |
-   *   StreamWrapperInterface::VISIBLE), then only stream wrappers with all
-   *   three of these bits set are returned. Defaults to
-   *   StreamWrapperInterface::ALL, which returns all registered stream
-   *   wrappers.
-   *
-   * @return array
-   *   Stream wrapper descriptions, keyed by scheme.
+   * {@inheritdoc}
    */
   public function getDescriptions($filter = StreamWrapperInterface::ALL) {
     $descriptions = array();
@@ -170,26 +93,14 @@ public function getDescriptions($filter = StreamWrapperInterface::ALL) {
   }
 
   /**
-   * Returns a stream wrapper via scheme.
-   *
-   * @param string $scheme
-   *   The scheme of the stream wrapper.
-   *
-   * @return \Drupal\Core\StreamWrapper\StreamWrapperInterface|bool
-   *   A stream wrapper object, or false if the scheme is not available.
+   * {@inheritdoc}
    */
   public function getViaScheme($scheme) {
     return $this->getWrapper($scheme, $scheme . '://');
   }
 
   /**
-   * Returns a stream wrapper via URI.
-   *
-   * @param string $uri
-   *   The URI of the stream wrapper.
-   *
-   * @return \Drupal\Core\StreamWrapper\StreamWrapperInterface|bool
-   *   A stream wrapper object, or false if the scheme is not available.
+   * {@inheritdoc}
    */
   public function getViaUri($uri) {
     $scheme = file_uri_scheme($uri);
@@ -197,13 +108,7 @@ public function getViaUri($uri) {
   }
 
   /**
-   * Returns the stream wrapper class.
-   *
-   * @param string $scheme
-   *   The stream wrapper scheme.
-   *
-   * @return string|bool
-   *   The stream wrapper class, or false if the scheme does not exist.
+   * {@inheritdoc}
    */
   public function getClass($scheme) {
     if (isset($this->info[$scheme])) {
@@ -283,14 +188,7 @@ public function unregister() {
   }
 
   /**
-   * Registers stream wrapper with PHP.
-   *
-   * @param string $scheme
-   *   The scheme of the stream wrapper.
-   * @param string $class
-   *   The class of the stream wrapper.
-   * @param int $type
-   *   The type of the stream wrapper.
+   * {@inheritdoc}
    */
   public function registerWrapper($scheme, $class, $type) {
     if (in_array($scheme, stream_get_wrappers(), TRUE)) {
diff --git a/core/lib/Drupal/Core/StreamWrapper/StreamWrapperManagerInterface.php b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperManagerInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..34e5f814427ec24b265d7f9feafb9e0a622072a7
--- /dev/null
+++ b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperManagerInterface.php
@@ -0,0 +1,159 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface.
+ */
+
+namespace Drupal\Core\StreamWrapper;
+
+/**
+ * Provides a StreamWrapper manager.
+ *
+ * @see file_get_stream_wrappers()
+ * @see \Drupal\Core\StreamWrapper\StreamWrapperInterface
+ */
+interface StreamWrapperManagerInterface {
+
+  /**
+   * Provides Drupal stream wrapper registry.
+   *
+   * A stream wrapper is an abstraction of a file system that allows Drupal to
+   * use the same set of methods to access both local files and remote
+   * resources.
+   *
+   * Provide a facility for managing and querying user-defined stream wrappers
+   * in PHP. PHP's internal stream_get_wrappers() doesn't return the class
+   * registered to handle a stream, which we need to be able to find the
+   * handler
+   * for class instantiation.
+   *
+   * If a module registers a scheme that is already registered with PHP, the
+   * existing scheme will be unregistered and replaced with the specified
+   * class.
+   *
+   * A stream is referenced as "scheme://target".
+   *
+   * The optional $filter parameter can be used to retrieve only the stream
+   * wrappers that are appropriate for particular usage. For example, this
+   * returns only stream wrappers that use local file storage:
+   *
+   * @code
+   *   $local_stream_wrappers =
+   *   file_get_stream_wrappers(StreamWrapperInterface::LOCAL);
+   * @endcode
+   *
+   * The $filter parameter can only filter to types containing a particular
+   * flag. In some cases, you may want to filter to types that do not contain a
+   * particular flag. For example, you may want to retrieve all stream wrappers
+   * that are not writable, or all stream wrappers that are not local. PHP's
+   * array_diff_key() function can be used to help with this. For example, this
+   * returns only stream wrappers that do not use local file storage:
+   * @code
+   *   $remote_stream_wrappers =
+   *   array_diff_key(file_get_stream_wrappers(StreamWrapperInterface::ALL),
+   *   file_get_stream_wrappers(StreamWrapperInterface::LOCAL));
+   * @endcode
+   *
+   * @param int $filter
+   *   (Optional) Filters out all types except those with an on bit for each on
+   *   bit in $filter. For example, if $filter is
+   *   StreamWrapperInterface::WRITE_VISIBLE, which is equal to
+   *   (StreamWrapperInterface::READ | StreamWrapperInterface::WRITE |
+   *   StreamWrapperInterface::VISIBLE), then only stream wrappers with all
+   *   three of these bits set are returned. Defaults to
+   *   StreamWrapperInterface::ALL, which returns all registered stream
+   *   wrappers.
+   *
+   * @return array
+   *   An array keyed by scheme, with values containing an array of information
+   *   about the stream wrapper, as returned by hook_stream_wrappers(). If
+   *   $filter is omitted or set to StreamWrapperInterface::ALL, the entire
+   *   Drupal stream wrapper registry is returned. Otherwise only the stream
+   *   wrappers whose 'type' bitmask has an on bit for each bit specified in
+   *   $filter are returned.
+   */
+  public function getWrappers($filter = StreamWrapperInterface::ALL);
+
+  /**
+   * Returns registered stream wrapper names.
+   *
+   * @param int $filter
+   *   (Optional) Filters out all types except those with an on bit for each on
+   *   bit in $filter. For example, if $filter is
+   *   StreamWrapperInterface::WRITE_VISIBLE, which is equal to
+   *   (StreamWrapperInterface::READ | StreamWrapperInterface::WRITE |
+   *   StreamWrapperInterface::VISIBLE), then only stream wrappers with all
+   *   three of these bits set are returned. Defaults to
+   *   StreamWrapperInterface::ALL, which returns all registered stream
+   *   wrappers.
+   *
+   * @return array
+   *   Stream wrapper names, keyed by scheme.
+   */
+  public function getNames($filter = StreamWrapperInterface::ALL);
+
+  /**
+   * Returns registered stream wrapper descriptions.
+   *
+   * @param int $filter
+   *   (Optional) Filters out all types except those with an on bit for each on
+   *   bit in $filter. For example, if $filter is
+   *   StreamWrapperInterface::WRITE_VISIBLE, which is equal to
+   *   (StreamWrapperInterface::READ | StreamWrapperInterface::WRITE |
+   *   StreamWrapperInterface::VISIBLE), then only stream wrappers with all
+   *   three of these bits set are returned. Defaults to
+   *   StreamWrapperInterface::ALL, which returns all registered stream
+   *   wrappers.
+   *
+   * @return array
+   *   Stream wrapper descriptions, keyed by scheme.
+   */
+  public function getDescriptions($filter = StreamWrapperInterface::ALL);
+
+  /**
+   * Returns a stream wrapper via scheme.
+   *
+   * @param string $scheme
+   *   The scheme of the stream wrapper.
+   *
+   * @return \Drupal\Core\StreamWrapper\StreamWrapperInterface|bool
+   *   A stream wrapper object, or false if the scheme is not available.
+   */
+  public function getViaScheme($scheme);
+
+  /**
+   * Returns a stream wrapper via URI.
+   *
+   * @param string $uri
+   *   The URI of the stream wrapper.
+   *
+   * @return \Drupal\Core\StreamWrapper\StreamWrapperInterface|bool
+   *   A stream wrapper object, or false if the scheme is not available.
+   */
+  public function getViaUri($uri);
+
+  /**
+   * Returns the stream wrapper class.
+   *
+   * @param string $scheme
+   *   The stream wrapper scheme.
+   *
+   * @return string|bool
+   *   The stream wrapper class, or false if the scheme does not exist.
+   */
+  public function getClass($scheme);
+
+  /**
+   * Registers stream wrapper with PHP.
+   *
+   * @param string $scheme
+   *   The scheme of the stream wrapper.
+   * @param string $class
+   *   The class of the stream wrapper.
+   * @param int $type
+   *   The type of the stream wrapper.
+   */
+  public function registerWrapper($scheme, $class, $type);
+
+}
diff --git a/core/modules/simpletest/src/KernelTestBase.php b/core/modules/simpletest/src/KernelTestBase.php
index 71be96917704238a3963d31daab7ef594130f857..188af9c38caa58a308505d0dd57d03815d9eabcc 100644
--- a/core/modules/simpletest/src/KernelTestBase.php
+++ b/core/modules/simpletest/src/KernelTestBase.php
@@ -147,9 +147,6 @@ protected function setUp() {
       $this->settingsSet('container_yamls', [$testing_services_file]);
     }
 
-    // Create and set new configuration directories.
-    $this->prepareConfigDirectories();
-
     // Add this test class as a service provider.
     // @todo Remove the indirection; implement ServiceProviderInterface instead.
     $GLOBALS['conf']['container_service_providers']['TestServiceProvider'] = 'Drupal\simpletest\TestServiceProvider';
@@ -172,6 +169,9 @@ protected function setUp() {
     // method sets additional settings.
     new Settings($settings + Settings::getAll());
 
+    // Create and set new configuration directories.
+    $this->prepareConfigDirectories();
+
     // Set the request scope.
     $this->container = $this->kernel->getContainer();
     $this->container->get('request_stack')->push($request);
diff --git a/core/modules/simpletest/src/TestBase.php b/core/modules/simpletest/src/TestBase.php
index b4901af4314e3b742570032459b69a40750d1618..6ef3e942d4c34f40f9732285a7ccd9960d890215 100644
--- a/core/modules/simpletest/src/TestBase.php
+++ b/core/modules/simpletest/src/TestBase.php
@@ -1282,6 +1282,10 @@ private function restoreEnvironment() {
     // log to pick up any fatal errors.
     simpletest_log_read($this->testId, $this->databasePrefix, get_class($this));
 
+    // Restore original dependency injection container.
+    $this->container = $this->originalContainer;
+    \Drupal::setContainer($this->originalContainer);
+
     // Delete test site directory.
     file_unmanaged_delete_recursive($this->siteDirectory, array($this, 'filePreDeleteCallback'));
 
@@ -1300,7 +1304,6 @@ private function restoreEnvironment() {
     new Settings($this->originalSettings);
 
     // Restore original statics and globals.
-    \Drupal::setContainer($this->originalContainer);
     $GLOBALS['config_directories'] = $this->originalConfigDirectories;
 
     // Re-initialize original stream wrappers of the parent site.
diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php
index b24d762942e4bc97b63de02ba045cef04ea5d7ef..0b6b770adc4c67e9e5f21befc0107fb7385c36df 100644
--- a/core/modules/simpletest/src/WebTestBase.php
+++ b/core/modules/simpletest/src/WebTestBase.php
@@ -1005,12 +1005,27 @@ protected function installParameters() {
 
     // If we only have one db driver available, we cannot set the driver.
     include_once DRUPAL_ROOT . '/core/includes/install.inc';
-    if (count(drupal_get_database_types()) == 1) {
+    if (count($this->getDatabaseTypes()) == 1) {
       unset($parameters['forms']['install_settings_form']['driver']);
     }
     return $parameters;
   }
 
+  /**
+   * Returns all supported database driver installer objects.
+   *
+   * This wraps drupal_get_database_types() for use without a current container.
+   *
+   * @return \Drupal\Core\Database\Install\Tasks[]
+   *   An array of available database driver installer objects.
+   */
+  protected function getDatabaseTypes() {
+    \Drupal::setContainer($this->originalContainer);
+    $database_types = drupal_get_database_types();
+    \Drupal::setContainer(NULL);
+    return $database_types;
+  }
+
   /**
    * Rewrites the settings.php file of the test site.
    *
diff --git a/core/modules/system/src/Form/FileSystemForm.php b/core/modules/system/src/Form/FileSystemForm.php
index 368f318d08b49d7cb26f24c45c414664a1b8a580..40bd93797a21f8951bc00fdb1dde304d13ab51be 100644
--- a/core/modules/system/src/Form/FileSystemForm.php
+++ b/core/modules/system/src/Form/FileSystemForm.php
@@ -15,7 +15,7 @@
 use Drupal\Core\StreamWrapper\PublicStream;
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\StreamWrapper\StreamWrapperInterface;
-use Drupal\Core\StreamWrapper\StreamWrapperManager;
+use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -33,7 +33,7 @@ class FileSystemForm extends ConfigFormBase {
   /**
    * The stream wrapper manager.
    *
-   * @var \Drupal\Core\StreamWrapper\StreamWrapperManager
+   * @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
    */
   protected $streamWrapperManager;
 
@@ -44,10 +44,10 @@ class FileSystemForm extends ConfigFormBase {
    *   The factory for configuration objects.
    * @param \Drupal\Core\Datetime\DateFormatter $date_formatter
    *   The date formatter service.
-   * @param \Drupal\Core\StreamWrapper\StreamWrapperManager $stream_wrapper_manager
+   * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager
    *   The stream wrapper manager.
    */
-  public function __construct(ConfigFactoryInterface $config_factory, DateFormatter $date_formatter, StreamWrapperManager $stream_wrapper_manager) {
+  public function __construct(ConfigFactoryInterface $config_factory, DateFormatter $date_formatter, StreamWrapperManagerInterface $stream_wrapper_manager) {
     parent::__construct($config_factory);
     $this->dateFormatter = $date_formatter;
     $this->streamWrapperManager = $stream_wrapper_manager;
diff --git a/core/modules/system/src/Tests/Bootstrap/GetFilenameUnitTest.php b/core/modules/system/src/Tests/Bootstrap/GetFilenameUnitTest.php
index a7bcca74ace9e7039544655eb09bad97c4a52cca..9baf5cdfd96aff1910dd90b9d6f7da6bc5f57c73 100644
--- a/core/modules/system/src/Tests/Bootstrap/GetFilenameUnitTest.php
+++ b/core/modules/system/src/Tests/Bootstrap/GetFilenameUnitTest.php
@@ -16,12 +16,34 @@
  */
 class GetFilenameUnitTest extends KernelTestBase {
 
+  /**
+   * The container used by the test, moved out of the way.
+   *
+   * @var \Symfony\Component\DependencyInjection\ContainerInterface
+   */
+  protected $previousContainer;
+
+  /**
+   * {@inheritdoc}
+   */
   protected function setUp() {
     parent::setUp();
+    // Store the previous container.
+    $this->previousContainer = $this->container;
     $this->container = NULL;
     \Drupal::setContainer(NULL);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function tearDown() {
+    parent::tearDown();
+    // Restore the previous container.
+    $this->container = $this->previousContainer;
+    \Drupal::setContainer($this->previousContainer);
+  }
+
   /**
    * Tests that drupal_get_filename() works when the file is not in database.
    */
diff --git a/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php b/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php
index af8ce27481af16cf67b8109ad820673c6e911d8a..892047810d62cc6e1e0cfe5036b14b41b2b22889 100644
--- a/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php
+++ b/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php
@@ -35,6 +35,15 @@ protected function setUp() {
     )));
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function prepareConfigDirectories() {
+    \Drupal::setContainer($this->originalContainer);
+    parent::prepareConfigDirectories();
+    \Drupal::setContainer(NULL);
+  }
+
   /**
    * Build a kernel for testings.
    *
diff --git a/core/modules/system/src/Tests/File/UnmanagedCopyTest.php b/core/modules/system/src/Tests/File/UnmanagedCopyTest.php
index 0169702ba157a617ced85046fec663999a0472da..f525c1d532528234100d5a21562c2a790a3c10f2 100644
--- a/core/modules/system/src/Tests/File/UnmanagedCopyTest.php
+++ b/core/modules/system/src/Tests/File/UnmanagedCopyTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\system\Tests\File;
 
 use Drupal\Core\Site\Settings;
+use Drupal\Core\File\FileSystem;
 
 /**
  * Tests the unmanaged file copy function.
@@ -29,7 +30,7 @@ function testNormal() {
     $this->assertEqual($new_filepath, $desired_filepath, 'Returned expected filepath.');
     $this->assertTrue(file_exists($uri), 'Original file remains.');
     $this->assertTrue(file_exists($new_filepath), 'New file exists.');
-    $this->assertFilePermissions($new_filepath, Settings::get('file_chmod_file', FILE_CHMOD_FILE));
+    $this->assertFilePermissions($new_filepath, Settings::get('file_chmod_file', FileSystem::CHMOD_FILE));
 
     // Copying with rename.
     $desired_filepath = 'public://' . $this->randomMachineName();
@@ -39,7 +40,7 @@ function testNormal() {
     $this->assertNotEqual($newer_filepath, $desired_filepath, 'Returned expected filepath.');
     $this->assertTrue(file_exists($uri), 'Original file remains.');
     $this->assertTrue(file_exists($newer_filepath), 'New file exists.');
-    $this->assertFilePermissions($newer_filepath, Settings::get('file_chmod_file', FILE_CHMOD_FILE));
+    $this->assertFilePermissions($newer_filepath, Settings::get('file_chmod_file', FileSystem::CHMOD_FILE));
 
     // TODO: test copying to a directory (rather than full directory/file path)
     // TODO: test copying normal files using normal paths (rather than only streams)
@@ -69,7 +70,7 @@ function testOverwriteSelf() {
     $this->assertNotEqual($new_filepath, $uri, 'Copied file has a new name.');
     $this->assertTrue(file_exists($uri), 'Original file exists after copying onto itself.');
     $this->assertTrue(file_exists($new_filepath), 'Copied file exists after copying onto itself.');
-    $this->assertFilePermissions($new_filepath, Settings::get('file_chmod_file', FILE_CHMOD_FILE));
+    $this->assertFilePermissions($new_filepath, Settings::get('file_chmod_file', FileSystem::CHMOD_FILE));
 
     // Copy the file onto itself without renaming fails.
     $new_filepath = file_unmanaged_copy($uri, $uri, FILE_EXISTS_ERROR);
@@ -87,6 +88,6 @@ function testOverwriteSelf() {
     $this->assertNotEqual($new_filepath, $uri, 'Copied file has a new name.');
     $this->assertTrue(file_exists($uri), 'Original file exists after copying onto itself.');
     $this->assertTrue(file_exists($new_filepath), 'Copied file exists after copying onto itself.');
-    $this->assertFilePermissions($new_filepath, Settings::get('file_chmod_file', FILE_CHMOD_FILE));
+    $this->assertFilePermissions($new_filepath, Settings::get('file_chmod_file', FileSystem::CHMOD_FILE));
   }
 }
diff --git a/core/modules/system/src/Tests/File/UnmanagedMoveTest.php b/core/modules/system/src/Tests/File/UnmanagedMoveTest.php
index e2bece8731e2d9f21d6402db7eb36df908a7c81f..ea39c545e6adccb284495f900edddc1b54410790 100644
--- a/core/modules/system/src/Tests/File/UnmanagedMoveTest.php
+++ b/core/modules/system/src/Tests/File/UnmanagedMoveTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\system\Tests\File;
 
 use Drupal\Core\Site\Settings;
+use Drupal\Core\File\FileSystem;
 
 /**
  * Tests the unmanaged file move function.
@@ -29,7 +30,7 @@ function testNormal() {
     $this->assertEqual($new_filepath, $desired_filepath, 'Returned expected filepath.');
     $this->assertTrue(file_exists($new_filepath), 'File exists at the new location.');
     $this->assertFalse(file_exists($uri), 'No file remains at the old location.');
-    $this->assertFilePermissions($new_filepath, Settings::get('file_chmod_file', FILE_CHMOD_FILE));
+    $this->assertFilePermissions($new_filepath, Settings::get('file_chmod_file', FileSystem::CHMOD_FILE));
 
     // Moving with rename.
     $desired_filepath = 'public://' . $this->randomMachineName();
@@ -40,7 +41,7 @@ function testNormal() {
     $this->assertNotEqual($newer_filepath, $desired_filepath, 'Returned expected filepath.');
     $this->assertTrue(file_exists($newer_filepath), 'File exists at the new location.');
     $this->assertFalse(file_exists($new_filepath), 'No file remains at the old location.');
-    $this->assertFilePermissions($newer_filepath, Settings::get('file_chmod_file', FILE_CHMOD_FILE));
+    $this->assertFilePermissions($newer_filepath, Settings::get('file_chmod_file', FileSystem::CHMOD_FILE));
 
     // TODO: test moving to a directory (rather than full directory/file path)
     // TODO: test creating and moving normal files (rather than streams)
diff --git a/core/modules/system/src/Tests/Routing/RouteProviderTest.php b/core/modules/system/src/Tests/Routing/RouteProviderTest.php
index be8862c6724fef08e20c0bb4ad328c9a04709dde..6ba9f9743cd12ef96e8a955407c229a6853f5f63 100644
--- a/core/modules/system/src/Tests/Routing/RouteProviderTest.php
+++ b/core/modules/system/src/Tests/Routing/RouteProviderTest.php
@@ -50,6 +50,7 @@ class RouteProviderTest extends KernelTestBase {
   protected $state;
 
   protected function setUp() {
+    parent::setUp();
     $this->fixtures = new RoutingFixtures();
     $this->routeBuilder = new NullRouteBuilder();
     $this->state = new State(new KeyValueMemoryFactory());
diff --git a/core/tests/Drupal/Tests/Core/File/FileSystemTest.php b/core/tests/Drupal/Tests/Core/File/FileSystemTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..ba39d1766d9bdf509bdb29dd392adbf68090e8a2
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/File/FileSystemTest.php
@@ -0,0 +1,183 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\File\FileSystemTest.
+ */
+
+namespace Drupal\Tests\Core\File;
+
+use Drupal\Core\File\FileSystem;
+use Drupal\Core\Site\Settings;
+use Drupal\Tests\UnitTestCase;
+use org\bovigo\vfs\vfsStream;
+
+/**
+ * @coversDefaultClass \Drupal\Core\File\FileSystem
+ *
+ * @group File
+ */
+class FileSystemTest extends UnitTestCase {
+
+  /**
+   * @var \Drupal\Core\File\FileSystem
+   */
+  protected $fileSystem;
+
+  /**
+   * The file logger channel.
+   *
+   * @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $logger;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $settings = new Settings([]);
+    $stream_wrapper_manager = $this->getMock('Drupal\Core\StreamWrapper\StreamWrapperManagerInterface');
+    $this->logger = $this->getMock('Psr\Log\LoggerInterface');
+    $this->fileSystem = new FileSystem($stream_wrapper_manager, $settings, $this->logger);
+  }
+
+  /**
+   * @covers ::chmod
+   */
+  public function testChmodFile() {
+    vfsStream::setup('dir');
+    vfsStream::create(['test.txt' => 'asdf']);
+    $uri = 'vfs://dir/test.txt';
+
+    $this->assertTrue($this->fileSystem->chmod($uri));
+    $this->assertFilePermissions(FileSystem::CHMOD_FILE, $uri);
+    $this->assertTrue($this->fileSystem->chmod($uri, 0444));
+    $this->assertFilePermissions(0444, $uri);
+  }
+
+  /**
+   * @covers ::chmod
+   */
+  public function testChmodDir() {
+    vfsStream::setup('dir');
+    vfsStream::create(['nested_dir' => []]);
+    $uri = 'vfs://dir/nested_dir';
+
+    $this->assertTrue($this->fileSystem->chmod($uri));
+    $this->assertFilePermissions(FileSystem::CHMOD_DIRECTORY, $uri);
+    $this->assertTrue($this->fileSystem->chmod($uri, 0444));
+    $this->assertFilePermissions(0444, $uri);
+  }
+
+  /**
+   * @covers ::chmod
+   */
+  public function testChmodUnsuccessful() {
+    vfsStream::setup('dir');
+    $this->logger->expects($this->once())
+      ->method('error');
+    $this->assertFalse($this->fileSystem->chmod('vfs://dir/test.txt'));
+  }
+
+  /**
+   * @covers ::unlink
+   */
+  public function testUnlink() {
+    vfsStream::setup('dir');
+    vfsStream::create(['test.txt' => 'asdf']);
+    $uri = 'vfs://dir/test.txt';
+
+    $this->fileSystem = $this->getMockBuilder('Drupal\Core\File\FileSystem')
+      ->disableOriginalConstructor()
+      ->setMethods(['validScheme'])
+      ->getMock();
+    $this->fileSystem->expects($this->once())
+      ->method('validScheme')
+      ->willReturn(TRUE);
+
+    $this->assertFileExists($uri);
+    $this->fileSystem->unlink($uri);
+    $this->assertFileNotExists($uri);
+  }
+
+  /**
+   * @covers ::basename
+   *
+   * @dataProvider providerTestBasename
+   */
+  public function testBasename($uri, $expected, $suffix = NULL) {
+    $this->assertSame($expected, $this->fileSystem->basename($uri, $suffix));
+  }
+
+  public function providerTestBasename() {
+    $data = [];
+    $data[] = [
+      'public://nested/dir',
+      'dir',
+    ];
+    $data[] = [
+      'public://dir/test.txt',
+      'test.txt',
+    ];
+    $data[] = [
+      'public://dir/test.txt',
+      'test',
+      '.txt'
+    ];
+    return $data;
+  }
+
+  /**
+   * @covers ::uriScheme
+   *
+   * @dataProvider providerTestUriScheme
+   */
+  public function testUriScheme($uri, $expected) {
+    $this->assertSame($expected, $this->fileSystem->uriScheme($uri));
+  }
+
+  public function providerTestUriScheme() {
+    $data = [];
+    $data[] = [
+      'public://filename',
+      'public',
+    ];
+    $data[] = [
+      'public://extra://',
+      'public',
+    ];
+    $data[] = [
+      'invalid',
+      FALSE,
+    ];
+    return $data;
+  }
+
+  /**
+   * Asserts that the file permissions of a given URI matches.
+   *
+   * @param int $expected_mode
+   * @param string $uri
+   * @param string $message
+   */
+  protected function assertFilePermissions($expected_mode, $uri, $message = '') {
+    // Mask out all but the last three octets.
+    $actual_mode = fileperms($uri) & 0777;
+
+    // PHP on Windows has limited support for file permissions. Usually each of
+    // "user", "group" and "other" use one octal digit (3 bits) to represent the
+    // read/write/execute bits. On Windows, chmod() ignores the "group" and
+    // "other" bits, and fileperms() returns the "user" bits in all three
+    // positions. $expected_mode is updated to reflect this.
+    if (substr(PHP_OS, 0, 3) == 'WIN') {
+      // Reset the "group" and "other" bits.
+      $expected_mode = $expected_mode & 0700;
+      // Shift the "user" bits to the "group" and "other" positions also.
+      $expected_mode = $expected_mode | $expected_mode >> 3 | $expected_mode >> 6;
+    }
+    $this->assertSame($expected_mode, $actual_mode, $message);
+  }
+
+}