Browse Source

Use random name for tmp ini and delete after use

Thanks to Patrick Rose for reporting this issue.
johnstevenson 8 years ago
parent
commit
379fb70ad9

+ 3 - 7
src/Composer/Command/DiagnoseCommand.php

@@ -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')) {

+ 5 - 11
src/Composer/DependencyResolver/SolverProblemsException.php

@@ -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.";
 

+ 2 - 7
src/Composer/Downloader/RarDownloader.php

@@ -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;

+ 2 - 8
src/Composer/Downloader/ZipDownloader.php

@@ -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);

+ 64 - 0
src/Composer/Util/IniHelper.php

@@ -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];
+    }
+}

+ 60 - 78
src/Composer/XdebugHandler.php

@@ -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;
-    }
 }

+ 68 - 0
tests/Composer/Test/Util/IniHelperTest.php

@@ -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));
+    }
+}

+ 23 - 14
tests/Composer/Test/XdebugHandlerTest.php

@@ -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->assertNotEquals(false, 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->assertEquals(false, 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);
     }
 }