addDescription( "Execute the MySQL client binary. " . "Non-option arguments will be passed through to mysql." ); $this->addOption( 'write', 'Connect to the master database', false, false ); $this->addOption( 'group', 'Specify query group', false, false ); $this->addOption( 'host', 'Connect to a specific MySQL server', false, true ); $this->addOption( 'list-hosts', 'List the available DB hosts', false, false ); $this->addOption( 'cluster', 'Use an external cluster by name', false, true ); $this->addOption( 'wikidb', 'The database wiki ID to use if not the current one', false, true ); // Fake argument for help message $this->addArg( '-- mysql_option ...', 'Options to pass to mysql', false ); } public function execute() { $dbName = $this->getOption( 'wikidb', false ); $lbf = MediaWikiServices::getInstance()->getDBLoadBalancerFactory(); if ( $this->hasOption( 'cluster' ) ) { try { $lb = $lbf->getExternalLB( $this->getOption( 'cluster' ) ); } catch ( InvalidArgumentException $e ) { $this->error( "Error: invalid cluster" ); exit( 1 ); } } else { $lb = $lbf->getMainLB( $dbName ); } if ( $this->hasOption( 'list-hosts' ) ) { $serverCount = $lb->getServerCount(); for ( $index = 0; $index < $serverCount; ++$index ) { echo $lb->getServerName( $index ) . "\n"; } exit( 0 ); } if ( $this->hasOption( 'host' ) ) { $host = $this->getOption( 'host' ); $serverCount = $lb->getServerCount(); for ( $index = 0; $index < $serverCount; ++$index ) { if ( $lb->getServerName( $index ) === $host ) { break; } } if ( $index >= $serverCount ) { $this->error( "Error: Host not configured: \"$host\"" ); exit( 1 ); } } elseif ( $this->hasOption( 'write' ) ) { $index = $lb->getWriterIndex(); } else { $group = $this->getOption( 'group', false ); $index = $lb->getReaderIndex( $group, $dbName ); if ( $index === false ) { $this->error( "Error: unable to get reader index" ); exit( 1 ); } } if ( $lb->getServerType( $index ) !== 'mysql' ) { $this->error( "Error: this script only works with MySQL/MariaDB" ); exit( 1 ); } $status = $this->runMysql( $lb->getServerInfo( $index ), $dbName ); exit( $status ); } /** * Run the mysql client for the given server info * * @param array $info * @param string|false $dbName The DB name, or false to use the main wiki DB * * @return int The desired exit status */ private function runMysql( $info, $dbName ) { // Write the password to an option file to avoid disclosing it to other // processes running on the system $tmpFile = TempFSFile::factory( 'mw-mysql', 'ini' ); chmod( $tmpFile->getPath(), 0600 ); file_put_contents( $tmpFile->getPath(), "[client]\npassword={$info['password']}\n" ); // stdin/stdout need to be the actual file descriptors rather than // PHP's pipe wrappers so that mysql can use them as an interactive // terminal. $desc = [ 0 => STDIN, 1 => STDOUT, 2 => STDERR, ]; // Split host and port as in DatabaseMysqli::mysqlConnect() $realServer = $info['host']; $hostAndPort = IP::splitHostAndPort( $realServer ); $socket = false; $port = false; if ( $hostAndPort ) { $realServer = $hostAndPort[0]; if ( $hostAndPort[1] ) { $port = $hostAndPort[1]; } } elseif ( substr_count( $realServer, ':' ) == 1 ) { // If we have a colon and something that's not a port number // inside the hostname, assume it's the socket location list( $realServer, $socket ) = explode( ':', $realServer, 2 ); } if ( $dbName === false ) { $dbName = $info['dbname']; } $args = [ 'mysql', "--defaults-extra-file={$tmpFile->getPath()}", "--user={$info['user']}", "--database={$dbName}", ]; if ( $socket !== false ) { $args[] = "--socket={$socket}"; } else { $args[] = "--host={$realServer}"; } if ( $port !== false ) { $args[] = "--port={$port}"; } $args = array_merge( $args, $this->mArgs ); // Ignore SIGINT if possible, otherwise the wrapper terminates when the user presses // ctrl-C to kill a query. if ( function_exists( 'pcntl_signal' ) ) { pcntl_signal( SIGINT, SIG_IGN ); } $pipes = []; $proc = proc_open( Shell::escape( $args ), $desc, $pipes ); if ( $proc === false ) { $this->error( "Unable to execute mysql" ); return 1; } $ret = proc_close( $proc ); if ( $ret === -1 ) { $this->error( "proc_close() returned -1" ); return 1; } return $ret; } } $maintClass = MysqlMaintenance::class; require_once RUN_MAINTENANCE_IF_MAIN;