diff -u b/core/lib/Drupal/Component/PhpStorage/FileStorage.php b/core/lib/Drupal/Component/PhpStorage/FileStorage.php --- b/core/lib/Drupal/Component/PhpStorage/FileStorage.php +++ b/core/lib/Drupal/Component/PhpStorage/FileStorage.php @@ -53,14 +53,36 @@ */ public function save($name, $code) { $path = $this->getFullPath($name); - $dir = dirname($path); - if (!file_exists($dir)) { - mkdir($dir, 0777, TRUE); - } + $this->ensureDirectory(dirname($path)); return (bool) file_put_contents($path, $code); } /** + * Ensures the root directory exists and has the right permissions. + * + * @param string $directory + * The directory path. + */ + protected function ensureDirectory($directory = NULL, $permission = 0777) { + if (!file_exists($directory)) { + // mkdir() obeys umask() so we need to mkdir() and chhmod() manually. + $parts = explode('/', $directory); + $path = ''; + $delimiter = ''; + do { + $part = array_shift($parts); + $path .= $delimiter . $part; + $delimiter = '/'; + // For absolute paths the first part will be empty. + if ($part && !file_exists($path)) { + mkdir($path); + chmod($path, $permission); + } + } while ($parts); + } + } + + /** * Implements Drupal\Component\PhpStorage\PhpStorageInterface::delete(). */ public function delete($name) { diff -u b/core/lib/Drupal/Component/PhpStorage/MTimeProtectedFastFileStorage.php b/core/lib/Drupal/Component/PhpStorage/MTimeProtectedFastFileStorage.php --- b/core/lib/Drupal/Component/PhpStorage/MTimeProtectedFastFileStorage.php +++ b/core/lib/Drupal/Component/PhpStorage/MTimeProtectedFastFileStorage.php @@ -87,6 +87,8 @@ if (!@file_put_contents($temporary_path, $data)) { return FALSE; } + // The file will not be chmod() in the future so this is the final + // permission. chmod($temporary_path, 0444); // Prepare a directory dedicated for just this file. Ensure it has a current @@ -114,7 +116,7 @@ $previous_mtime = $mtime; // If the directory permissions can't be modified then check whether // it's a valid holding directory already, if not, abort. - if (!@chmod($directory, 0777) && $this->isHolder($directory)) { + if (!@chmod($directory, 0777) && !$this->isHolder($directory)) { throw new \Exception(sprintf("%s can not be used safely for saving.", $directory)); } // Reset the file back in the temporary location if this is not the first @@ -152,12 +154,10 @@ } /** - * Ensures the root directory exists and has correct permissions. + * {@inheritdoc} */ - protected function ensureDirectory() { - if (!file_exists($this->directory)) { - mkdir($this->directory, 0777, TRUE); - } + protected function ensureDirectory($directory = NULL, $permission = 0777) { + parent::ensureDirectory($this->directory); $htaccess_path = $this->directory . '/.htaccess'; if (!file_exists($htaccess_path) && file_put_contents($htaccess_path, self::HTACCESS)) { @chmod($htaccess_path, 0444); @@ -231,8 +231,8 @@ } catch (\UnexpectedValueException $e) { // If the directory has 0333 permissions, we presume it's a holder and - // invalidate it with a touch(). If this doesn't succeed, give up. - if ($this->isHolder($path) && @touch($path)) { + // try to invalidate it. If this doesn't succeed, give up. + if ($this->isHolder($path) && $this->invalidate($path)) { return TRUE; } throw $e; @@ -240,6 +240,26 @@ } /** + * Tries to invalidate a holder directory. + * + * @param $path + * The path to the holder directory. + * + * @return bool + * TRUE on success. + */ + protected function invalidate($path) { + $mtime = filemtime($path); + $return = @touch($path); + if ($return && $this->getUncachedMTime($path) == $mtime) { + sleep(1); + @touch($path); + $return = $this->getUncachedMTime($path) == $mtime; + } + return $return; + } + + /** * Checks whether the directory is a valid holding directory. * * @param $directory