Merge pull request #5882 from johnstevenson/xdebug-handler-ini

Use random name for tmp ini and delete after use
main
Jordi Boggiano 8 years ago committed by GitHub
commit f1eb787013

@ -19,6 +19,7 @@ use Composer\Downloader\TransportException;
use Composer\Plugin\CommandEvent; use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents; use Composer\Plugin\PluginEvents;
use Composer\Util\ConfigValidator; use Composer\Util\ConfigValidator;
use Composer\Util\IniHelper;
use Composer\Util\ProcessExecutor; use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem; use Composer\Util\RemoteFilesystem;
use Composer\Util\StreamContextFactory; use Composer\Util\StreamContextFactory;
@ -436,14 +437,9 @@ EOT
// code below taken from getcomposer.org/installer, any changes should be made there and replicated here // code below taken from getcomposer.org/installer, any changes should be made there and replicated here
$errors = array(); $errors = array();
$warnings = array(); $warnings = array();
$iniPath = php_ini_loaded_file();
$displayIniMessage = false; $displayIniMessage = false;
if ($iniPath) {
$iniMessage = PHP_EOL.PHP_EOL.'The php.ini used by your command-line PHP is: ' . $iniPath; $iniMessage = PHP_EOL.PHP_EOL.IniHelper::getMessage();
} else {
$iniMessage = PHP_EOL.PHP_EOL.'A php.ini file does not exist. You will have to create one.';
}
$iniMessage .= PHP_EOL.'If you can not modify the ini file, you can also run `php -d option=value` to modify ini values on the fly. You can use -d multiple times.'; $iniMessage .= PHP_EOL.'If you can not modify the ini file, you can also run `php -d option=value` to modify ini values on the fly. You can use -d multiple times.';
if (!function_exists('json_decode')) { if (!function_exists('json_decode')) {

@ -12,6 +12,8 @@
namespace Composer\DependencyResolver; namespace Composer\DependencyResolver;
use Composer\Util\IniHelper;
/** /**
* @author Nils Adermann <naderman@naderman.de> * @author Nils Adermann <naderman@naderman.de>
*/ */
@ -58,21 +60,13 @@ class SolverProblemsException extends \RuntimeException
private function createExtensionHint() private function createExtensionHint()
{ {
$paths = array(); $paths = IniHelper::getAll();
if (($iniPath = php_ini_loaded_file()) !== false) {
$paths[] = $iniPath;
}
if (!defined('HHVM_VERSION') && $additionalIniPaths = php_ini_scanned_files()) {
$paths = array_merge($paths, array_map("trim", explode(",", $additionalIniPaths)));
}
if (count($paths) === 0) { if (count($paths) === 1 && empty($paths[0])) {
return ''; return '';
} }
$text = "\n To enable extensions, verify that they are enabled in those .ini files:\n - "; $text = "\n To enable extensions, verify that they are enabled in your .ini files:\n - ";
$text .= implode("\n - ", $paths); $text .= implode("\n - ", $paths);
$text .= "\n You can also run `php --ini` inside terminal to see which files are used by PHP in CLI mode."; $text .= "\n You can also run `php --ini` inside terminal to see which files are used by PHP in CLI mode.";

@ -15,6 +15,7 @@ namespace Composer\Downloader;
use Composer\Config; use Composer\Config;
use Composer\Cache; use Composer\Cache;
use Composer\EventDispatcher\EventDispatcher; use Composer\EventDispatcher\EventDispatcher;
use Composer\Util\IniHelper;
use Composer\Util\Platform; use Composer\Util\Platform;
use Composer\Util\ProcessExecutor; use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem; use Composer\Util\RemoteFilesystem;
@ -55,13 +56,7 @@ class RarDownloader extends ArchiveDownloader
if (!class_exists('RarArchive')) { if (!class_exists('RarArchive')) {
// php.ini path is added to the error message to help users find the correct file // php.ini path is added to the error message to help users find the correct file
$iniPath = php_ini_loaded_file(); $iniMessage = IniHelper::getMessage();
if ($iniPath) {
$iniMessage = 'The php.ini used by your command-line PHP is: ' . $iniPath;
} else {
$iniMessage = 'A php.ini file does not exist. You will have to create one.';
}
$error = "Could not decompress the archive, enable the PHP rar extension or install unrar.\n" $error = "Could not decompress the archive, enable the PHP rar extension or install unrar.\n"
. $iniMessage . "\n" . $processError; . $iniMessage . "\n" . $processError;

@ -16,6 +16,7 @@ use Composer\Config;
use Composer\Cache; use Composer\Cache;
use Composer\EventDispatcher\EventDispatcher; use Composer\EventDispatcher\EventDispatcher;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\Util\IniHelper;
use Composer\Util\Platform; use Composer\Util\Platform;
use Composer\Util\ProcessExecutor; use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem; use Composer\Util\RemoteFilesystem;
@ -49,14 +50,7 @@ class ZipDownloader extends ArchiveDownloader
if (!class_exists('ZipArchive') && !self::$hasSystemUnzip) { if (!class_exists('ZipArchive') && !self::$hasSystemUnzip) {
// php.ini path is added to the error message to help users find the correct file // php.ini path is added to the error message to help users find the correct file
$iniPath = php_ini_loaded_file(); $iniMessage = IniHelper::getMessage();
if ($iniPath) {
$iniMessage = 'The php.ini used by your command-line PHP is: ' . $iniPath;
} else {
$iniMessage = 'A php.ini file does not exist. You will have to create one.';
}
$error = "The zip extension and unzip command are both missing, skipping.\n" . $iniMessage; $error = "The zip extension and unzip command are both missing, skipping.\n" . $iniMessage;
throw new \RuntimeException($error); throw new \RuntimeException($error);

@ -0,0 +1,64 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Util;
/**
* Provides ini file location functions that work with and without a restart.
* When the process has restarted it uses a tmp ini and stores the original
* ini locations in an environment variable.
*
* @author John Stevenson <john-stevenson@blueyonder.co.uk>
*/
class IniHelper
{
const ENV_ORIGINAL = 'COMPOSER_ORIGINAL_INIS';
/**
* Returns an array of php.ini locations with at least one entry
*
* The equivalent of calling php_ini_loaded_file then php_ini_scanned_files.
* The loaded ini location is the first entry and may be empty.
* @return array
*/
public static function getAll()
{
if ($env = strval(getenv(self::ENV_ORIGINAL))) {
return explode(PATH_SEPARATOR, $env);
}
$paths = array(strval(php_ini_loaded_file()));
if ($scanned = php_ini_scanned_files()) {
$paths = array_merge($paths, array_map('trim', explode(',', $scanned)));
}
return $paths;
}
/**
* Describes the location of the loaded php.ini file
*
* @return string
*/
public static function getMessage()
{
$paths = self::getAll();
if (empty($paths[0])) {
return 'A php.ini file does not exist. You will have to create one.';
}
return 'The php.ini used by your command-line PHP is: '.$paths[0];
}
}

@ -12,6 +12,7 @@
namespace Composer; namespace Composer;
use Composer\Util\IniHelper;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
/** /**
@ -25,6 +26,7 @@ class XdebugHandler
private $output; private $output;
private $loaded; private $loaded;
private $envScanDir; private $envScanDir;
private $tmpIni;
/** /**
* Constructor * Constructor
@ -41,7 +43,8 @@ class XdebugHandler
* *
* If so, then a tmp ini is created with the xdebug ini entry commented out. * If so, then a tmp ini is created with the xdebug ini entry commented out.
* If additional inis have been loaded, these are combined into the tmp ini * If additional inis have been loaded, these are combined into the tmp ini
* and PHP_INI_SCAN_DIR is set to an empty value. * and PHP_INI_SCAN_DIR is set to an empty value. Current ini locations are
* are stored in COMPOSER_ORIGINAL_INIS, for use in the restarted process.
* *
* This behaviour can be disabled by setting the COMPOSER_ALLOW_XDEBUG * This behaviour can be disabled by setting the COMPOSER_ALLOW_XDEBUG
* environment variable to 1. This variable is used internally so that the * environment variable to 1. This variable is used internally so that the
@ -73,13 +76,18 @@ class XdebugHandler
} }
/** /**
* Executes the restarted command * Executes the restarted command then deletes the tmp ini
* *
* @param string $command * @param string $command
*/ */
protected function restart($command) protected function restart($command)
{ {
passthru($command, $exitCode); passthru($command, $exitCode);
if (!empty($this->tmpIni)) {
@unlink($this->tmpIni);
}
exit($exitCode); exit($exitCode);
} }
@ -113,82 +121,75 @@ class XdebugHandler
*/ */
private function prepareRestart(&$command) private function prepareRestart(&$command)
{ {
$iniFiles = array(); $this->tmpIni = '';
if ($loadedIni = php_ini_loaded_file()) { $iniPaths = IniHelper::getAll();
$iniFiles[] = $loadedIni; $files = $this->getWorkingSet($iniPaths, $replace);
}
$additional = $this->getAdditionalInis($iniFiles, $replace);
$tmpIni = $this->writeTmpIni($iniFiles, $replace);
if (false !== $tmpIni) { if ($this->writeTmpIni($files, $replace)) {
$command = $this->getCommand($tmpIni); $command = $this->getCommand();
return $this->setEnvironment($additional); return $this->setEnvironment($iniPaths);
} }
return false; return false;
} }
/** /**
* Writes the tmp ini file and returns its filename * Returns true if the tmp ini file was written
* *
* The filename is passed as the -c option when the process restarts. On * The filename is passed as the -c option when the process restarts.
* non-Windows platforms the filename is prefixed with the username to
* avoid any multi-user conflict. Windows always uses the user temp dir.
* *
* @param array $iniFiles The php.ini locations * @param array $iniFiles The php.ini locations
* @param bool $replace Whether we need to modify the files * @param bool $replace Whether the files need modifying
* *
* @return bool|string False if the tmp ini could not be created * @return bool
*/ */
private function writeTmpIni(array $iniFiles, $replace) private function writeTmpIni(array $iniFiles, $replace)
{ {
if (empty($iniFiles)) { if (empty($iniFiles)) {
// Unlikely, maybe xdebug was loaded through a command line option. // Unlikely, maybe xdebug was loaded through a command line option.
return ''; return true;
} }
if (function_exists('posix_getpwuid')) { if (!$this->tmpIni = tempnam(sys_get_temp_dir(), '')) {
$user = posix_getpwuid(posix_getuid()); return false;
} }
$prefix = !empty($user) ? $user['name'].'-' : '';
$tmpIni = sys_get_temp_dir().'/'.$prefix.'composer-php.ini';
$content = $this->getIniHeader($iniFiles); $content = '';
foreach ($iniFiles as $file) { foreach ($iniFiles as $file) {
$content .= $this->getIniData($file, $replace); $content .= $this->getIniData($file, $replace);
} }
return @file_put_contents($tmpIni, $content) ? $tmpIni : false; return @file_put_contents($this->tmpIni, $content);
} }
/** /**
* Returns true if additional inis were loaded * Returns an array of ini files to use
* *
* @param array $iniFiles Populated by method * @param array $iniPaths Locations used by the current prcoess
* @param bool $replace Whether we need to modify the files * @param null|bool $replace Whether the files need modifying, set by method
* *
* @return bool * @return array
*/ */
private function getAdditionalInis(array &$iniFiles, &$replace) private function getWorkingSet(array $iniPaths, &$replace)
{ {
$replace = true; $replace = true;
$result = array();
if ($scanned = php_ini_scanned_files()) { if (empty($iniPaths[0])) {
$list = explode(',', $scanned); // There is no loaded ini
array_shift($iniPaths);
}
foreach ($list as $file) { foreach ($iniPaths as $file) {
$file = trim($file); if (preg_match('/xdebug.ini$/', $file)) {
if (preg_match('/xdebug.ini$/', $file)) { // Skip the file, no need for regex replacing
// Skip the file, no need for regex replacing $replace = false;
$replace = false; } else {
} else { $result[] = $file;
$iniFiles[] = $file;
}
} }
} }
return !empty($scanned); return $result;
} }
/** /**
@ -201,9 +202,8 @@ class XdebugHandler
*/ */
private function getIniData($iniFile, $replace) private function getIniData($iniFile, $replace)
{ {
$data = str_repeat(PHP_EOL, 3);
$data .= sprintf('; %s%s', $iniFile, PHP_EOL);
$contents = file_get_contents($iniFile); $contents = file_get_contents($iniFile);
$data = PHP_EOL;
if ($replace) { if ($replace) {
// Comment out xdebug config // Comment out xdebug config
@ -219,13 +219,11 @@ class XdebugHandler
/** /**
* Returns the restart command line * Returns the restart command line
* *
* @param string $tmpIni The temporary ini file location
*
* @return string * @return string
*/ */
private function getCommand($tmpIni) private function getCommand()
{ {
$phpArgs = array(PHP_BINARY, '-c', $tmpIni); $phpArgs = array(PHP_BINARY, '-c', $this->tmpIni);
$params = array_merge($phpArgs, $this->getScriptArgs($_SERVER['argv'])); $params = array_merge($phpArgs, $this->getScriptArgs($_SERVER['argv']));
return implode(' ', array_map(array($this, 'escape'), $params)); return implode(' ', array_map(array($this, 'escape'), $params));
@ -234,12 +232,25 @@ class XdebugHandler
/** /**
* Returns true if the restart environment variables were set * Returns true if the restart environment variables were set
* *
* @param bool $additional Whether additional inis were loaded * @param array $iniPaths Locations used by the current prcoess
* *
* @return bool * @return bool
*/ */
private function setEnvironment($additional) private function setEnvironment(array $iniPaths)
{ {
// Set scan dir to an empty value if additional ini files were used
$additional = count($iniPaths) > 1;
if ($additional && !putenv('PHP_INI_SCAN_DIR=')) {
return false;
}
// Make original inis available to restarted process
if (!putenv(IniHelper::ENV_ORIGINAL.'='.implode(PATH_SEPARATOR, $iniPaths))) {
return false;
}
// Flag restarted process and save env scan dir state
$args = array(self::RESTART_ID); $args = array(self::RESTART_ID);
if (false !== $this->envScanDir) { if (false !== $this->envScanDir) {
@ -247,10 +258,6 @@ class XdebugHandler
$args[] = $this->envScanDir; $args[] = $this->envScanDir;
} }
if ($additional && !putenv('PHP_INI_SCAN_DIR=')) {
return false;
}
return putenv(self::ENV_ALLOW.'='.implode('|', $args)); return putenv(self::ENV_ALLOW.'='.implode('|', $args));
} }
@ -317,29 +324,4 @@ class XdebugHandler
return $arg; return $arg;
} }
/**
* Returns the location of the original ini data used.
*
* @param array $iniFiles loaded php.ini locations
*
* @return string
*/
private function getIniHeader($iniFiles)
{
$ini = implode(PHP_EOL.'; ', $iniFiles);
$header = <<<EOD
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; This file was automatically generated by Composer and can now be deleted.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; It is a modified copy of your php.ini configuration, found at:
; {$ini}
; Make any changes there because this data will not be used again.
EOD;
$header .= str_repeat(PHP_EOL, 50);
return $header;
}
} }

@ -0,0 +1,68 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Test\Util;
use Composer\Util\IniHelper;
/**
* @author John Stevenson <john-stevenson@blueyonder.co.uk>
*/
class IniHelperTest extends \PHPUnit_Framework_TestCase
{
public static $envOriginal;
public function testWithLoadedIni()
{
$paths = array(
'loaded.ini',
);
$this->setEnv($paths);
$this->assertContains('loaded.ini', IniHelper::getMessage());
$this->assertEquals($paths, IniHelper::getAll());
}
public function testWithoutLoadedIni()
{
$paths = array(
'',
'one.ini',
'two.ini',
);
$this->setEnv($paths);
$this->assertContains('does not exist', IniHelper::getMessage());
$this->assertEquals($paths, IniHelper::getAll());
}
public static function setUpBeforeClass()
{
// Save current state
self::$envOriginal = getenv(IniHelper::ENV_ORIGINAL);
}
public static function tearDownAfterClass()
{
// Restore original state
if (false !== self::$envOriginal) {
putenv(IniHelper::ENV_ORIGINAL.'='.self::$envOriginal);
} else {
putenv(IniHelper::ENV_ORIGINAL);
}
}
protected function setEnv(array $paths)
{
putenv(IniHelper::ENV_ORIGINAL.'='.implode(PATH_SEPARATOR, $paths));
}
}

@ -13,6 +13,7 @@
namespace Composer\Test; namespace Composer\Test;
use Composer\Test\Mock\XdebugHandlerMock; use Composer\Test\Mock\XdebugHandlerMock;
use Composer\Util\IniHelper;
/** /**
* @author John Stevenson <john-stevenson@blueyonder.co.uk> * @author John Stevenson <john-stevenson@blueyonder.co.uk>
@ -22,8 +23,7 @@ use Composer\Test\Mock\XdebugHandlerMock;
*/ */
class XdebugHandlerTest extends \PHPUnit_Framework_TestCase class XdebugHandlerTest extends \PHPUnit_Framework_TestCase
{ {
public static $envAllow; public static $env = array();
public static $envIniScanDir;
public function testRestartWhenLoaded() public function testRestartWhenLoaded()
{ {
@ -32,6 +32,7 @@ class XdebugHandlerTest extends \PHPUnit_Framework_TestCase
$xdebug = new XdebugHandlerMock($loaded); $xdebug = new XdebugHandlerMock($loaded);
$xdebug->check(); $xdebug->check();
$this->assertTrue($xdebug->restarted); $this->assertTrue($xdebug->restarted);
$this->assertInternalType('string', getenv(IniHelper::ENV_ORIGINAL));
} }
public function testNoRestartWhenNotLoaded() public function testNoRestartWhenNotLoaded()
@ -41,6 +42,7 @@ class XdebugHandlerTest extends \PHPUnit_Framework_TestCase
$xdebug = new XdebugHandlerMock($loaded); $xdebug = new XdebugHandlerMock($loaded);
$xdebug->check(); $xdebug->check();
$this->assertFalse($xdebug->restarted); $this->assertFalse($xdebug->restarted);
$this->assertFalse(getenv(IniHelper::ENV_ORIGINAL));
} }
public function testNoRestartWhenLoadedAndAllowed() public function testNoRestartWhenLoadedAndAllowed()
@ -106,28 +108,35 @@ class XdebugHandlerTest extends \PHPUnit_Framework_TestCase
public static function setUpBeforeClass() public static function setUpBeforeClass()
{ {
self::$envAllow = getenv(XdebugHandlerMock::ENV_ALLOW); // Save current state
self::$envIniScanDir = getenv('PHP_INI_SCAN_DIR'); $names = array(
XdebugHandlerMock::ENV_ALLOW,
'PHP_INI_SCAN_DIR',
IniHelper::ENV_ORIGINAL,
);
foreach ($names as $name) {
self::$env[$name] = getenv($name);
}
} }
public static function tearDownAfterClass() public static function tearDownAfterClass()
{ {
if (false !== self::$envAllow) { // Restore original state
putenv(XdebugHandlerMock::ENV_ALLOW.'='.self::$envAllow); foreach (self::$env as $name => $value) {
} else { if (false !== $value) {
putenv(XdebugHandlerMock::ENV_ALLOW); putenv($name.'='.$value);
} } else {
putenv($name);
if (false !== self::$envIniScanDir) { }
putenv('PHP_INI_SCAN_DIR='.self::$envIniScanDir);
} else {
putenv('PHP_INI_SCAN_DIR');
} }
} }
protected function setUp() protected function setUp()
{ {
// Ensure env is unset
putenv(XdebugHandlerMock::ENV_ALLOW); putenv(XdebugHandlerMock::ENV_ALLOW);
putenv('PHP_INI_SCAN_DIR'); putenv('PHP_INI_SCAN_DIR');
putenv(IniHelper::ENV_ORIGINAL);
} }
} }

Loading…
Cancel
Save