 includes/file.inc                  |   67 ++++++++++++++++++++++++++++++------
 includes/stream_wrappers.inc       |    4 +--
 modules/simpletest/tests/file.test |   35 +++++++++++++++++++
 3 files changed, 94 insertions(+), 12 deletions(-)

diff --git a/includes/file.inc b/includes/file.inc
index 09a4424..3834cd6 100644
--- a/includes/file.inc
+++ b/includes/file.inc
@@ -441,8 +441,8 @@ function file_prepare_directory(&$directory, $options = FILE_MODIFY_PERMISSIONS)
   if (!is_dir($directory)) {
     // Let mkdir() recursively create directories and use the default directory
     // permissions.
-    if (($options & FILE_CREATE_DIRECTORY) && @drupal_mkdir($directory, NULL, TRUE)) {
-      return drupal_chmod($directory);
+    if ($options & FILE_CREATE_DIRECTORY) {
+      return @drupal_mkdir($directory, NULL, TRUE);
     }
     return FALSE;
   }
@@ -2309,19 +2309,21 @@ function drupal_basename($uri, $suffix = NULL) {
 }
 
 /**
- * Creates a directory using Drupal's default mode.
+ * Creates a directory, optionally creating missing components in the path to
+ * the directory.
  *
- * PHP's mkdir() does not respect Drupal's default permissions mode. If a mode
- * is not provided, this function will make sure that Drupal's is used.
- *
- * Compatibility: normal paths and stream wrappers.
+ * 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
- *   By default the Drupal mode is used.
+ *   Mode given to created directories. Defaults to the directory mode
+ *   configured in the Drupal installation. It must have a leading zero.
  * @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/en/ref.stream.php
  *
@@ -2337,7 +2339,52 @@ function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) {
     $mode = variable_get('file_chmod_directory', 0775);
   }
 
-  if (!isset($context)) {
+  // 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) {
+    $components = explode(DIRECTORY_SEPARATOR, $uri);
+    array_pop($components);
+    $recursive_path = '';
+    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 {
diff --git a/includes/stream_wrappers.inc b/includes/stream_wrappers.inc
index fa401c6..4908b29 100644
--- a/includes/stream_wrappers.inc
+++ b/includes/stream_wrappers.inc
@@ -636,10 +636,10 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface
       $localpath = $this->getLocalPath($uri);
     }
     if ($options & STREAM_REPORT_ERRORS) {
-      return mkdir($localpath, $mode, $recursive);
+      return drupal_mkdir($localpath, $mode, $recursive);
     }
     else {
-      return @mkdir($localpath, $mode, $recursive);
+      return @drupal_mkdir($localpath, $mode, $recursive);
     }
   }
 
diff --git a/modules/simpletest/tests/file.test b/modules/simpletest/tests/file.test
index ebaa0c0..1d5fa25 100644
--- a/modules/simpletest/tests/file.test
+++ b/modules/simpletest/tests/file.test
@@ -108,6 +108,7 @@ class FileTestCase extends DrupalWebTestCase {
 
     // Mask out all but the last three octets.
     $actual_mode = fileperms($filepath) & 0777;
+    $expected_mode = $expected_mode & 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
@@ -143,6 +144,7 @@ class FileTestCase extends DrupalWebTestCase {
 
     // Mask out all but the last three octets.
     $actual_mode = fileperms($directory) & 0777;
+    $expected_mode = $expected_mode & 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
@@ -912,6 +914,39 @@ class FileDirectoryTest extends FileTestCase {
   }
 
   /**
+   * 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');
+    // Make files directory writable only.
+    $old_mode = fileperms($directory);
+
+    // Create the directories.
+    $parent_path = $directory . DIRECTORY_SEPARATOR . $parent;
+    $child_path = $parent_path . DIRECTORY_SEPARATOR . $child;
+    $this->assertTrue(drupal_mkdir($child_path, 0775, 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, 0775);
+    $this->assertDirectoryPermissions($child_path, 0775);
+
+    // Check that existing directory permissions were not modified.
+    $this->assertDirectoryPermissions($directory, $old_mode);
+  }
+
+  /**
    * Test directory handling functions.
    */
   function testFileCheckDirectoryHandling() {
