public function MTimeProtectedFastFileStorage::save

Implements Drupal\Component\PhpStorage\PhpStorageInterface::save().

Overrides FileStorage::save

File

drupal/core/lib/Drupal/Component/PhpStorage/MTimeProtectedFastFileStorage.php, line 75
Definition of Drupal\Component\PhpStorage\MTimeProtectedFastFileStorage.

Class

MTimeProtectedFastFileStorage
Stores PHP code in files with securely hashed names.

Namespace

Drupal\Component\PhpStorage

Code

public function save($name, $data) {
  $this
    ->ensureDirectory();

  // Write the file out to a temporary location. Prepend with a '.' to keep it
  // hidden from listings and web servers.
  $temporary_path = $this->directory . '/.' . str_replace('/', '#', $name);
  if (!@file_put_contents($temporary_path, $data)) {
    return FALSE;
  }
  chmod($temporary_path, 0400);

  // Prepare a directory dedicated for just this file. Ensure it has a current
  // mtime so that when the file (hashed on that mtime) is moved into it, the
  // mtime remains the same (unless the clock ticks to the next second during
  // the rename, in which case we'll try again).
  $directory = $this
    ->getContainingDirectoryFullPath($name);
  if (file_exists($directory)) {
    $this
      ->cleanDirectory($directory);
    touch($directory);
  }
  else {
    mkdir($directory);
  }

  // Move the file to its final place. The mtime of a directory is the time of
  // the last file create or delete in the directory. So the moving will
  // update the directory mtime. However, this update will very likely not
  // show up, because it has a coarse, one second granularity and typical
  // moves takes significantly less than that. In the unlucky case the clock
  // ticks during the move, we need to keep trying until the mtime we hashed
  // on and the updated mtime match.
  $previous_mtime = 0;
  $i = 0;
  while (($mtime = $this
    ->getUncachedMTime($directory)) && $mtime != $previous_mtime) {
    $previous_mtime = $mtime;
    chmod($directory, 0700);

    // Reset the file back in the temporary location if this is not the first
    // iteration.
    if ($i > 0) {
      rename($full_path, $temporary_path);

      // Make sure to not loop infinitely on a hopelessly slow filesystem.
      if ($i > 10) {
        $this
          ->unlink($temporary_path);
        return FALSE;
      }
    }
    $full_path = $this
      ->getFullPath($name, $directory, $mtime);
    rename($temporary_path, $full_path);

    // Leave the directory neither readable nor writable. Since the file
    // itself is not writable (set to 0400 at the beginning of this function),
    // there's no way to tamper with it without access to change permissions.
    chmod($directory, 0100);
    $i++;
  }
  return TRUE;
}