 core/includes/file.inc                             |   69 ++++++++++++++++++--
 core/lib/Drupal/Core/StreamWrapper/LocalStream.php |    4 +-
 .../lib/Drupal/system/Tests/File/DirectoryTest.php |   28 ++++++++
 3 files changed, 95 insertions(+), 6 deletions(-)

diff --git a/core/includes/file.inc b/core/includes/file.inc
index f1c6ac9..db16e85 100644
--- a/core/includes/file.inc
+++ b/core/includes/file.inc
@@ -1719,9 +1719,11 @@ function drupal_basename($uri, $suffix = NULL) {
  * @param $uri
  *   A URI or pathname.
  * @param $mode
- *   By default the Drupal mode is used.
+ *   By default the Drupal mode is used. It must have a leading zero. Also note
+ *   that umask() is ignored.
  * @param $recursive
- *   Default to FALSE.
+ *   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
  *
@@ -1737,12 +1739,71 @@ function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) {
     $mode = variable_get('file_chmod_directory', 0775);
   }
 
-  if (!isset($context)) {
-    return mkdir($uri, $mode, $recursive);
+  // Schemes must implement their own recursive mkdir().
+  if (file_uri_scheme($uri)) {
+    return _drupal_mkdir_call($uri, $mode, $recursive, $context);
   }
   else {
+    return _drupal_mkdir_local($uri, $mode, $recursive, $context);
+  }
+}
+
+/**
+ * Ensures we don't pass a NULL as a context resource to mkdir().
+ */
+function _drupal_mkdir_call($uri, $mode, $recursive, $context) {
+  if (isset($context)) {
     return mkdir($uri, $mode, $recursive, $context);
   }
+  else {
+    return mkdir($uri, $mode, $recursive);
+  }
+}
+
+/**
+ * Creates one or more directories from a local path.
+ *
+ * @param string $localpath
+ *   A local pathname.
+ * @param integer $mode
+ *   The mode will be passed only to new directories in the path.
+ * @param boolean $recursive
+ *   Whether or not directories should be created recursively.
+ * @param resource $context
+ *   Refer to http://php.net/manual/ref.stream.php
+ *
+ * @return boolean
+ *   TRUE on success, or FALSE on failure.
+ */
+function _drupal_mkdir_local($localpath, $mode, $recursive, $context = NULL) {
+  // If not recursive, PHP's mkdir() can handle it.
+  if (!$recursive) {
+    return _drupal_mkdir_call($localpath, $mode, FALSE, $context);
+  }
+
+  $directories = explode(DIRECTORY_SEPARATOR, $localpath);
+  $directory_path = '';
+  foreach ($directories as $directory) {
+    $directory_path .= $directory;
+
+    if (!file_exists($directory_path)) {
+      if (!_drupal_mkdir_call($directory_path, $mode, FALSE, $context)) {
+        return FALSE;
+      }
+      // We can't rely on mkdir() setting the mode recursively because mkdir
+      // (correctly) obeys umask() but we can't run umask() because it is set
+      // on a process level:
+      // @see http://php.net/manual/en/function.umask.php#105150
+      // Therefore, sadly, we need to chmod after the dirs are created.
+      if (!chmod($directory_path, $mode)) {
+        return FALSE;
+      }
+    }
+
+    $directory_path .= DIRECTORY_SEPARATOR;
+  }
+
+  return TRUE;
 }
 
 /**
diff --git a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
index 24b8938..570175e 100644
--- a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
+++ b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
@@ -425,10 +425,10 @@ abstract class LocalStream implements StreamWrapperInterface {
       $localpath = $this->getLocalPath($uri);
     }
     if ($options & STREAM_REPORT_ERRORS) {
-      return mkdir($localpath, $mode, $recursive);
+      return _drupal_mkdir_local($localpath, $mode, $recursive);
     }
     else {
-      return @mkdir($localpath, $mode, $recursive);
+      return @_drupal_mkdir_local($localpath, $mode, $recursive);
     }
   }
 
diff --git a/core/modules/system/lib/Drupal/system/Tests/File/DirectoryTest.php b/core/modules/system/lib/Drupal/system/Tests/File/DirectoryTest.php
index 5d57bdf..7b95da8 100644
--- a/core/modules/system/lib/Drupal/system/Tests/File/DirectoryTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/File/DirectoryTest.php
@@ -20,6 +20,34 @@ class DirectoryTest extends FileTestBase {
   }
 
   /**
+   * Test local directory handling functions.
+   */
+  function testFileCheckLocalDirectoryHandling() {
+    $directory = conf_path() . '/files';
+
+    // Check a new recursively created local directory for correct file system
+    // permissions.
+    $parent = $this->randomName();
+    $child = $this->randomName();
+
+    // Files directory already exists.
+    $this->assertTrue(is_dir($directory), t('Files directory already exists.'), 'File');
+
+    // Create the directories.
+    $parent_path = $directory . DIRECTORY_SEPARATOR . $parent;
+    $child_path = $parent_path . DIRECTORY_SEPARATOR . $child;
+    $this->assertTrue(drupal_mkdir($child_path, 0777, TRUE), t('No error reported when creating new local directories.'), 'File');
+
+    // Ensure new directories also exist.
+    $this->assertTrue(is_dir($parent_path), t('New parent directory actually exists.'), 'File');
+    $this->assertTrue(is_dir($child_path), t('New child directory actually exists.'), 'File');
+
+    // Check that new directory permissions were set properly.
+    $this->assertDirectoryPermissions($parent_path, 0777);
+    $this->assertDirectoryPermissions($child_path, 0777);
+  }
+
+  /**
    * Test directory handling functions.
    */
   function testFileCheckDirectoryHandling() {
