mysql.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. <?php
  2. /**
  3. * Execute the MySQL client binary, connecting to the wiki's DB.
  4. * Note that this will not do table prefixing or variable substitution.
  5. * To safely run schema patches, use sql.php.
  6. *
  7. * This program is free software; you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation; either version 2 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License along
  18. * with this program; if not, write to the Free Software Foundation, Inc.,
  19. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  20. * http://www.gnu.org/copyleft/gpl.html
  21. *
  22. * @file
  23. * @ingroup Maintenance
  24. */
  25. use MediaWiki\Shell\Shell;
  26. use MediaWiki\MediaWikiServices;
  27. require_once __DIR__ . '/Maintenance.php';
  28. /**
  29. * @ingroup Maintenance
  30. */
  31. class MysqlMaintenance extends Maintenance {
  32. public function __construct() {
  33. parent::__construct();
  34. $this->addDescription( "Execute the MySQL client binary. " .
  35. "Non-option arguments will be passed through to mysql." );
  36. $this->addOption( 'write', 'Connect to the master database', false, false );
  37. $this->addOption( 'group', 'Specify query group', false, false );
  38. $this->addOption( 'host', 'Connect to a specific MySQL server', false, true );
  39. $this->addOption( 'list-hosts', 'List the available DB hosts', false, false );
  40. $this->addOption( 'cluster', 'Use an external cluster by name', false, true );
  41. $this->addOption( 'wikidb',
  42. 'The database wiki ID to use if not the current one', false, true );
  43. // Fake argument for help message
  44. $this->addArg( '-- mysql_option ...', 'Options to pass to mysql', false );
  45. }
  46. public function execute() {
  47. $dbName = $this->getOption( 'wikidb', false );
  48. $lbf = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
  49. if ( $this->hasOption( 'cluster' ) ) {
  50. try {
  51. $lb = $lbf->getExternalLB( $this->getOption( 'cluster' ) );
  52. } catch ( InvalidArgumentException $e ) {
  53. $this->error( "Error: invalid cluster" );
  54. exit( 1 );
  55. }
  56. } else {
  57. $lb = $lbf->getMainLB( $dbName );
  58. }
  59. if ( $this->hasOption( 'list-hosts' ) ) {
  60. $serverCount = $lb->getServerCount();
  61. for ( $index = 0; $index < $serverCount; ++$index ) {
  62. echo $lb->getServerName( $index ) . "\n";
  63. }
  64. exit( 0 );
  65. }
  66. if ( $this->hasOption( 'host' ) ) {
  67. $host = $this->getOption( 'host' );
  68. $serverCount = $lb->getServerCount();
  69. for ( $index = 0; $index < $serverCount; ++$index ) {
  70. $serverInfo = $lb->getServerInfo( $index );
  71. if ( $lb->getServerName( $index ) === $host ) {
  72. break;
  73. }
  74. }
  75. if ( $index >= $serverCount ) {
  76. $this->error( "Error: Host not configured: \"$host\"" );
  77. exit( 1 );
  78. }
  79. } elseif ( $this->hasOption( 'write' ) ) {
  80. $index = $lb->getWriterIndex();
  81. } else {
  82. $group = $this->getOption( 'group', false );
  83. $index = $lb->getReaderIndex( $group, $dbName );
  84. if ( $index === false ) {
  85. $this->error( "Error: unable to get reader index" );
  86. exit( 1 );
  87. }
  88. }
  89. if ( $lb->getServerType( $index ) !== 'mysql' ) {
  90. $this->error( "Error: this script only works with MySQL/MariaDB" );
  91. exit( 1 );
  92. }
  93. $status = $this->runMysql( $lb->getServerInfo( $index ), $dbName );
  94. exit( $status );
  95. }
  96. /**
  97. * Run the mysql client for the given server info
  98. *
  99. * @param array $info
  100. * @param string|false $dbName The DB name, or false to use the main wiki DB
  101. *
  102. * @return int The desired exit status
  103. */
  104. private function runMysql( $info, $dbName ) {
  105. // Write the password to an option file to avoid disclosing it to other
  106. // processes running on the system
  107. $tmpFile = TempFSFile::factory( 'mw-mysql', 'ini' );
  108. chmod( $tmpFile->getPath(), 0600 );
  109. file_put_contents( $tmpFile->getPath(), "[client]\npassword={$info['password']}\n" );
  110. // stdin/stdout need to be the actual file descriptors rather than
  111. // PHP's pipe wrappers so that mysql can use them as an interactive
  112. // terminal.
  113. $desc = [
  114. 0 => STDIN,
  115. 1 => STDOUT,
  116. 2 => STDERR,
  117. ];
  118. // Split host and port as in DatabaseMysqli::mysqlConnect()
  119. $realServer = $info['host'];
  120. $hostAndPort = IP::splitHostAndPort( $realServer );
  121. $socket = false;
  122. $port = false;
  123. if ( $hostAndPort ) {
  124. $realServer = $hostAndPort[0];
  125. if ( $hostAndPort[1] ) {
  126. $port = $hostAndPort[1];
  127. }
  128. } elseif ( substr_count( $realServer, ':' ) == 1 ) {
  129. // If we have a colon and something that's not a port number
  130. // inside the hostname, assume it's the socket location
  131. $hostAndSocket = explode( ':', $realServer, 2 );
  132. $realServer = $hostAndSocket[0];
  133. $socket = $hostAndSocket[1];
  134. }
  135. if ( $dbName === false ) {
  136. $dbName = $info['dbname'];
  137. }
  138. $args = [
  139. 'mysql',
  140. "--defaults-extra-file={$tmpFile->getPath()}",
  141. "--user={$info['user']}",
  142. "--database={$dbName}",
  143. ];
  144. if ( $socket !== false ) {
  145. $args[] = "--socket={$socket}";
  146. } else {
  147. $args[] = "--host={$realServer}";
  148. }
  149. if ( $port !== false ) {
  150. $args[] = "--port={$port}";
  151. }
  152. $args = array_merge( $args, $this->mArgs );
  153. // Ignore SIGINT if possible, otherwise the wrapper terminates when the user presses
  154. // ctrl-C to kill a query.
  155. if ( function_exists( 'pcntl_signal' ) ) {
  156. pcntl_signal( SIGINT, SIG_IGN );
  157. }
  158. $pipes = [];
  159. $proc = proc_open( Shell::escape( $args ), $desc, $pipes );
  160. if ( $proc === false ) {
  161. $this->error( "Unable to execute mysql" );
  162. return 1;
  163. }
  164. $ret = proc_close( $proc );
  165. if ( $ret === -1 ) {
  166. $this->error( "proc_close() returned -1" );
  167. return 1;
  168. }
  169. return $ret;
  170. }
  171. }
  172. $maintClass = MysqlMaintenance::class;
  173. require_once RUN_MAINTENANCE_IF_MAIN;