Skip to content

Commit

Permalink
bug #27236 [Filesystem] Fix usages of error_get_last() (nicolas-grekas)
Browse files Browse the repository at this point in the history
This PR was merged into the 2.7 branch.

Discussion
----------

[Filesystem] Fix usages of error_get_last()

| Q             | A
| ------------- | ---
| Branch?       | 2.7
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

Same as #27232 for 2.7.
When a userland error handler doesn't return `false`, `error_get_last()` is not updated, so we cannot see the real last error, but the previous one.

See https://3v4l.org/Smmt7

Commits
-------

9d015c7 [Filesystem] Fix usages of error_get_last()
  • Loading branch information
fabpot committed May 14, 2018
2 parents 8072eed + 9d015c7 commit 15b03a8
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 38 deletions.
5 changes: 2 additions & 3 deletions src/Symfony/Component/Console/Command/Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,11 @@ public function run(InputInterface $input, OutputInterface $output)

if (null !== $this->processTitle) {
if (function_exists('cli_set_process_title')) {
if (false === @cli_set_process_title($this->processTitle)) {
if (!@cli_set_process_title($this->processTitle)) {
if ('Darwin' === PHP_OS) {
$output->writeln('<comment>Running "cli_get_process_title" as an unprivileged user is not supported on MacOS.</comment>');
} else {
$error = error_get_last();
trigger_error($error['message'], E_USER_WARNING);
cli_set_process_title($this->processTitle);
}
}
} elseif (function_exists('setproctitle')) {
Expand Down
64 changes: 42 additions & 22 deletions src/Symfony/Component/Filesystem/Filesystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
*/
class Filesystem
{
private static $lastError;

/**
* Copies a file.
*
Expand Down Expand Up @@ -95,12 +97,11 @@ public function mkdir($dirs, $mode = 0777)
continue;
}

if (true !== @mkdir($dir, $mode, true)) {
$error = error_get_last();
if (!self::box('mkdir', $dir, $mode, true)) {
if (!is_dir($dir)) {
// The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one
if ($error) {
throw new IOException(sprintf('Failed to create "%s": %s.', $dir, $error['message']), 0, null, $dir);
if (self::$lastError) {
throw new IOException(sprintf('Failed to create "%s": %s.', $dir, self::$lastError), 0, null, $dir);
}
throw new IOException(sprintf('Failed to create "%s"', $dir), 0, null, $dir);
}
Expand Down Expand Up @@ -169,20 +170,17 @@ public function remove($files)
foreach ($files as $file) {
if (is_link($file)) {
// See https://bugs.php.net/52176
if (!@(unlink($file) || '\\' !== DIRECTORY_SEPARATOR || rmdir($file)) && file_exists($file)) {
$error = error_get_last();
throw new IOException(sprintf('Failed to remove symlink "%s": %s.', $file, $error['message']));
if (!(self::box('unlink', $file) || '\\' !== DIRECTORY_SEPARATOR || self::box('rmdir', $file)) && file_exists($file)) {
throw new IOException(sprintf('Failed to remove symlink "%s": %s.', $file, self::$lastError));
}
} elseif (is_dir($file)) {
$this->remove(new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS));

if (!@rmdir($file) && file_exists($file)) {
$error = error_get_last();
throw new IOException(sprintf('Failed to remove directory "%s": %s.', $file, $error['message']));
if (!self::box('rmdir', $file) && file_exists($file)) {
throw new IOException(sprintf('Failed to remove directory "%s": %s.', $file, self::$lastError));
}
} elseif (!@unlink($file) && file_exists($file)) {
$error = error_get_last();
throw new IOException(sprintf('Failed to remove file "%s": %s.', $file, $error['message']));
} elseif (!self::box('unlink', $file) && file_exists($file)) {
throw new IOException(sprintf('Failed to remove file "%s": %s.', $file, self::$lastError));
}
}
}
Expand Down Expand Up @@ -336,19 +334,16 @@ public function symlink($originDir, $targetDir, $copyOnWindows = false)

$this->mkdir(dirname($targetDir));

$ok = false;
if (is_link($targetDir)) {
if (readlink($targetDir) != $originDir) {
$this->remove($targetDir);
} else {
$ok = true;
if (readlink($targetDir) === $originDir) {
return;
}
$this->remove($targetDir);
}

if (!$ok && true !== @symlink($originDir, $targetDir)) {
$report = error_get_last();
if (is_array($report)) {
if ('\\' === DIRECTORY_SEPARATOR && false !== strpos($report['message'], 'error code(1314)')) {
if (!self::box('symlink', $originDir, $targetDir)) {
if (null !== self::$lastError) {
if ('\\' === DIRECTORY_SEPARATOR && false !== strpos(self::$lastError, 'error code(1314)')) {
throw new IOException('Unable to create symlink due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', 0, null, $targetDir);
}
}
Expand Down Expand Up @@ -580,4 +575,29 @@ private function toIterator($files)

return $files;
}

private static function box($func)
{
self::$lastError = null;
\set_error_handler(__CLASS__.'::handleError');
try {
$result = \call_user_func_array($func, \array_slice(\func_get_args(), 1));
\restore_error_handler();

return $result;
} catch (\Throwable $e) {
} catch (\Exception $e) {
}
\restore_error_handler();

throw $e;
}

/**
* @internal
*/
public static function handleError($type, $msg)
{
self::$lastError = $msg;
}
}
7 changes: 3 additions & 4 deletions src/Symfony/Component/Finder/SplFileInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,11 @@ public function getRelativePathname()
*/
public function getContents()
{
$level = error_reporting(0);
set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
$content = file_get_contents($this->getPathname());
error_reporting($level);
restore_error_handler();
if (false === $content) {
$error = error_get_last();
throw new \RuntimeException($error['message']);
throw new \RuntimeException($error);
}

return $content;
Expand Down
8 changes: 5 additions & 3 deletions src/Symfony/Component/HttpFoundation/File/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,11 @@ public function move($directory, $name = null)
{
$target = $this->getTargetFile($directory, $name);

if (!@rename($this->getPathname(), $target)) {
$error = error_get_last();
throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message'])));
set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
$renamed = rename($this->getPathname(), $target);
restore_error_handler();
if (!$renamed) {
throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error)));
}

@chmod($target, 0666 & ~umask());
Expand Down
8 changes: 5 additions & 3 deletions src/Symfony/Component/HttpFoundation/File/UploadedFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,11 @@ public function move($directory, $name = null)

$target = $this->getTargetFile($directory, $name);

if (!@move_uploaded_file($this->getPathname(), $target)) {
$error = error_get_last();
throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message'])));
set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
$moved = move_uploaded_file($this->getPathname(), $target);
restore_error_handler();
if (!$moved) {
throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error)));
}

@chmod($target, 0666 & ~umask());
Expand Down
14 changes: 12 additions & 2 deletions src/Symfony/Component/Process/Pipes/AbstractPipes.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ abstract class AbstractPipes implements PipesInterface
private $inputBuffer = '';
private $input;
private $blocked = true;
private $lastError;

/**
* @param resource|null $input
Expand Down Expand Up @@ -56,10 +57,11 @@ public function close()
*/
protected function hasSystemCallBeenInterrupted()
{
$lastError = error_get_last();
$lastError = $this->lastError;
$this->lastError = null;

// stream_select returns false when the `select` system call is interrupted by an incoming signal
return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call');
return null !== $lastError && false !== stripos($lastError, 'interrupted system call');
}

/**
Expand Down Expand Up @@ -137,4 +139,12 @@ protected function write()
return array($this->pipes[0]);
}
}

/**
* @internal
*/
public function handleError($type, $msg)
{
$this->lastError = $msg;
}
}
5 changes: 4 additions & 1 deletion src/Symfony/Component/Process/Pipes/UnixPipes.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ public function readAndWrite($blocking, $close = false)
unset($r[0]);

// let's have a look if something changed in streams
if (($r || $w) && false === @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
set_error_handler(array($this, 'handleError'));
if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
restore_error_handler();
// if a system call has been interrupted, forget about it, let's try again
// otherwise, an error occurred, let's reset pipes
if (!$this->hasSystemCallBeenInterrupted()) {
Expand All @@ -108,6 +110,7 @@ public function readAndWrite($blocking, $close = false)

return $read;
}
restore_error_handler();

foreach ($r as $pipe) {
// prior PHP 5.4 the array passed to stream_select is modified and
Expand Down

0 comments on commit 15b03a8

Please sign in to comment.