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\PluginEvents;
use Composer\Util\ConfigValidator;
use Composer\Util\IniHelper;
use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem;
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
$errors = array();
$warnings = array();
$iniPath = php_ini_loaded_file();
$displayIniMessage = false;
if ($iniPath) {
$iniMessage = PHP_EOL.PHP_EOL.'The php.ini used by your command-line PHP is: ' . $iniPath;
} else {
$iniMessage = PHP_EOL.PHP_EOL.'A php.ini file does not exist. You will have to create one.';
}
$iniMessage = PHP_EOL.PHP_EOL.IniHelper::getMessage();
$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')) {

@ -12,6 +12,8 @@
namespace Composer\DependencyResolver;
use Composer\Util\IniHelper;
/**
* @author Nils Adermann <naderman@naderman.de>
*/
@ -58,21 +60,13 @@ class SolverProblemsException extends \RuntimeException
private function createExtensionHint()
{
$paths = array();
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)));
}
$paths = IniHelper::getAll();
if (count($paths) === 0) {
if (count($paths) === 1 && empty($paths[0])) {
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 .= "\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\Cache;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Util\IniHelper;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem;
@ -55,13 +56,7 @@ class RarDownloader extends ArchiveDownloader
if (!class_exists('RarArchive')) {
// php.ini path is added to the error message to help users find the correct file
$iniPath = php_ini_loaded_file();
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.';
}
$iniMessage = IniHelper::getMessage();
$error = "Could not decompress the archive, enable the PHP rar extension or install unrar.\n"
. $iniMessage . "\n" . $processError;

@ -16,6 +16,7 @@ use Composer\Config;
use Composer\Cache;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Package\PackageInterface;
use Composer\Util\IniHelper;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem;
@ -49,14 +50,7 @@ class ZipDownloader extends ArchiveDownloader
if (!class_exists('ZipArchive') && !self::$hasSystemUnzip) {
// php.ini path is added to the error message to help users find the correct file
$iniPath = php_ini_loaded_file();
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.';
}
$iniMessage = IniHelper::getMessage();
$error = "The zip extension and unzip command are both missing, skipping.\n" . $iniMessage;
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;
use Composer\Util\IniHelper;
use Symfony\Component\Console\Output\OutputInterface;
/**
@ -25,6 +26,7 @@ class XdebugHandler
private $output;
private $loaded;
private $envScanDir;
private $tmpIni;
/**
* Constructor
@ -41,7 +43,8 @@ class XdebugHandler
*
* 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
* 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
* 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
*/
protected function restart($command)
{
passthru($command, $exitCode);
if (!empty($this->tmpIni)) {
@unlink($this->tmpIni);
}
exit($exitCode);
}
@ -113,82 +121,75 @@ class XdebugHandler
*/
private function prepareRestart(&$command)
{
$iniFiles = array();
if ($loadedIni = php_ini_loaded_file()) {
$iniFiles[] = $loadedIni;
}
$additional = $this->getAdditionalInis($iniFiles, $replace);
$tmpIni = $this->writeTmpIni($iniFiles, $replace);
$this->tmpIni = '';
$iniPaths = IniHelper::getAll();
$files = $this->getWorkingSet($iniPaths, $replace);
if (false !== $tmpIni) {
$command = $this->getCommand($tmpIni);
return $this->setEnvironment($additional);
if ($this->writeTmpIni($files, $replace)) {
$command = $this->getCommand();
return $this->setEnvironment($iniPaths);
}
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
* non-Windows platforms the filename is prefixed with the username to
* avoid any multi-user conflict. Windows always uses the user temp dir.
* The filename is passed as the -c option when the process restarts.
*
* @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)
{
if (empty($iniFiles)) {
// Unlikely, maybe xdebug was loaded through a command line option.
return '';
return true;
}
if (function_exists('posix_getpwuid')) {
$user = posix_getpwuid(posix_getuid());
if (!$this->tmpIni = tempnam(sys_get_temp_dir(), '')) {
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) {
$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 bool $replace Whether we need to modify the files
* @param array $iniPaths Locations used by the current prcoess
* @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;
$result = array();
if ($scanned = php_ini_scanned_files()) {
$list = explode(',', $scanned);
if (empty($iniPaths[0])) {
// There is no loaded ini
array_shift($iniPaths);
}
foreach ($list as $file) {
$file = trim($file);
if (preg_match('/xdebug.ini$/', $file)) {
// Skip the file, no need for regex replacing
$replace = false;
} else {
$iniFiles[] = $file;
}
foreach ($iniPaths as $file) {
if (preg_match('/xdebug.ini$/', $file)) {
// Skip the file, no need for regex replacing
$replace = false;
} else {
$result[] = $file;
}
}
return !empty($scanned);
return $result;
}
/**
@ -201,9 +202,8 @@ class XdebugHandler
*/
private function getIniData($iniFile, $replace)
{
$data = str_repeat(PHP_EOL, 3);
$data .= sprintf('; %s%s', $iniFile, PHP_EOL);
$contents = file_get_contents($iniFile);
$data = PHP_EOL;
if ($replace) {
// Comment out xdebug config
@ -219,13 +219,11 @@ class XdebugHandler
/**
* Returns the restart command line
*
* @param string $tmpIni The temporary ini file location
*
* @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']));
return implode(' ', array_map(array($this, 'escape'), $params));
@ -234,12 +232,25 @@ class XdebugHandler
/**
* 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
*/
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);
if (false !== $this->envScanDir) {
@ -247,10 +258,6 @@ class XdebugHandler
$args[] = $this->envScanDir;
}
if ($additional && !putenv('PHP_INI_SCAN_DIR=')) {
return false;
}
return putenv(self::ENV_ALLOW.'='.implode('|', $args));
}
@ -317,29 +324,4 @@ class XdebugHandler
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;
use Composer\Test\Mock\XdebugHandlerMock;
use Composer\Util\IniHelper;
/**
* @author John Stevenson <john-stevenson@blueyonder.co.uk>
@ -22,8 +23,7 @@ use Composer\Test\Mock\XdebugHandlerMock;
*/
class XdebugHandlerTest extends \PHPUnit_Framework_TestCase
{
public static $envAllow;
public static $envIniScanDir;
public static $env = array();
public function testRestartWhenLoaded()
{
@ -32,6 +32,7 @@ class XdebugHandlerTest extends \PHPUnit_Framework_TestCase
$xdebug = new XdebugHandlerMock($loaded);
$xdebug->check();
$this->assertTrue($xdebug->restarted);
$this->assertInternalType('string', getenv(IniHelper::ENV_ORIGINAL));
}
public function testNoRestartWhenNotLoaded()
@ -41,6 +42,7 @@ class XdebugHandlerTest extends \PHPUnit_Framework_TestCase
$xdebug = new XdebugHandlerMock($loaded);
$xdebug->check();
$this->assertFalse($xdebug->restarted);
$this->assertFalse(getenv(IniHelper::ENV_ORIGINAL));
}
public function testNoRestartWhenLoadedAndAllowed()
@ -106,28 +108,35 @@ class XdebugHandlerTest extends \PHPUnit_Framework_TestCase
public static function setUpBeforeClass()
{
self::$envAllow = getenv(XdebugHandlerMock::ENV_ALLOW);
self::$envIniScanDir = getenv('PHP_INI_SCAN_DIR');
// Save current state
$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()
{
if (false !== self::$envAllow) {
putenv(XdebugHandlerMock::ENV_ALLOW.'='.self::$envAllow);
} else {
putenv(XdebugHandlerMock::ENV_ALLOW);
}
if (false !== self::$envIniScanDir) {
putenv('PHP_INI_SCAN_DIR='.self::$envIniScanDir);
} else {
putenv('PHP_INI_SCAN_DIR');
// Restore original state
foreach (self::$env as $name => $value) {
if (false !== $value) {
putenv($name.'='.$value);
} else {
putenv($name);
}
}
}
protected function setUp()
{
// Ensure env is unset
putenv(XdebugHandlerMock::ENV_ALLOW);
putenv('PHP_INI_SCAN_DIR');
putenv(IniHelper::ENV_ORIGINAL);
}
}

Loading…
Cancel
Save