27 require_once __DIR__ .
'/backup.inc';
98 if ( isset( $this->lb ) ) {
99 $this->lb->closeAll();
103 if ( $this->forcedDb !==
null ) {
108 if ( isset( $this->db ) && $this->db->isOpen() ) {
109 throw new MWException(
'DB is set and has not been closed by the Load Balancer' );
121 }
catch ( Exception
$e ) {
122 throw new MWException( __METHOD__ .
" rotating DB failed to obtain new load balancer (" .
$e->getMessage() .
")" );
127 $this->db = $this->lb->getConnection(
DB_SLAVE,
'dump' );
128 }
catch ( Exception
$e ) {
129 throw new MWException( __METHOD__ .
" rotating DB failed to obtain new database (" .
$e->getMessage() .
")" );
134 parent::initProgress();
135 $this->timeOfCheckpoint = $this->startTime;
141 if ( ini_get(
'display_errors' ) ) {
142 ini_set(
'display_errors',
'stderr' );
154 }
catch ( Exception
$e ) {
157 $this->
progress(
"Getting initial DB connection failed (" .
158 $e->getMessage() .
")" );
169 $input = fopen( $this->input,
"rt" );
172 if ( $this->spawnProc ) {
185 require_once
"$IP/maintenance/backupPrefetch.inc";
186 $this->prefetch =
new BaseDump( $url );
192 $this->maxTimeAllowed = intval( $val ) * 60;
194 case 'checkpointfile':
195 $this->checkpointFiles[] = $val;
213 $fileURIs = explode(
';', $param );
214 foreach ( $fileURIs
as $URI ) {
220 $newURI =
"compress.zlib://$URI";
223 $newURI =
"compress.bzip2://$URI";
226 $newURI =
"mediawiki.compress.7z://$URI";
231 $newFileURIs[] = $newURI;
233 $val = implode(
';', $newFileURIs );
241 if ( !$this->prefetch ) {
242 parent::showReport();
246 if ( $this->reporting ) {
248 $nowts = microtime(
true );
249 $deltaAll = $nowts - $this->startTime;
255 $portion = $this->
revCount / $this->maxCount;
256 $eta = $this->startTime + $deltaAll / $portion;
258 if ( $this->fetchCount ) {
263 $pageRate = $this->pageCount / $deltaAll;
264 $revRate = $this->
revCount / $deltaAll;
272 if ( $this->fetchCountLast ) {
275 $fetchRatePart =
'-';
277 $pageRatePart = $this->pageCountPart / $deltaPart;
278 $revRatePart = $this->revCountPart / $deltaPart;
281 $fetchRatePart =
'-';
285 $this->
progress( sprintf(
"%s: %s (ID %d) %d pages (%0.1f|%0.1f/sec all|curr), %d revs (%0.1f|%0.1f/sec all|curr), %0.1f%%|%0.1f%% prefetched (all|curr), ETA %s [max %d]",
286 $now,
wfWikiID(), $this->
ID, $this->pageCount, $pageRate, $pageRatePart, $this->
revCount, $revRate, $revRatePart, $fetchRate, $fetchRatePart, $etats, $this->maxCount ) );
287 $this->lastTime = $nowts;
295 $this->timeExceeded =
true;
299 if ( $this->maxTimeAllowed && ( $this->lastTime - $this->timeOfCheckpoint > $this->maxTimeAllowed ) ) {
306 if ( ( $this->checkpointFiles && ! $this->maxTimeAllowed ) ||
307 ( $this->maxTimeAllowed && !$this->checkpointFiles ) ) {
308 throw new MWException(
"Options checkpointfile and maxtime must be specified together.\n" );
310 foreach ( $this->checkpointFiles
as $checkpointFile ) {
311 $count = substr_count ( $checkpointFile,
"%s" );
313 throw new MWException(
"Option checkpointfile must contain two '%s' for substitution of first and last pageids, count is $count instead, file is $checkpointFile.\n" );
317 if ( $this->checkpointFiles ) {
318 $filenameList = (
array)$this->egress->getFilenames();
319 if ( count( $filenameList ) != count( $this->checkpointFiles ) ) {
320 throw new MWException(
"One checkpointfile must be specified for each output option, if maxtime is used.\n" );
331 $this->openElement =
false;
332 $this->atStart =
true;
334 $this->lastName =
"";
338 $parser = xml_parser_create(
"UTF-8" );
339 xml_parser_set_option(
$parser, XML_OPTION_CASE_FOLDING,
false );
341 xml_set_element_handler(
$parser,
array( &$this,
'startElement' ),
array( &$this,
'endElement' ) );
342 xml_set_character_data_handler(
$parser,
array( &$this,
'characterData' ) );
345 $bufferSize = 512 * 1024;
350 $chunk = fread(
$input, $bufferSize );
352 wfDebug(
"TextDumpPass::readDump encountered XML parsing error\n" );
354 $byte = xml_get_current_byte_index(
$parser );
356 'XML import parse failure',
357 xml_get_current_line_number(
$parser ),
358 xml_get_current_column_number(
$parser ),
359 $byte . ( is_null( $chunk ) ?
null : (
'; "' . substr( $chunk, $byte -$offset, 16 ) .
'"' ) ),
360 xml_error_string( xml_get_error_code(
$parser ) ) )->escaped();
366 $offset += strlen( $chunk );
367 }
while ( $chunk !==
false && !feof(
$input ) );
368 if ( $this->maxTimeAllowed ) {
369 $filenameList = (
array)$this->egress->getFilenames();
371 if ( file_exists( $filenameList[0] ) ) {
372 $newFilenames =
array();
373 # we might have just written the header and footer and had no
374 # pages or revisions written... perhaps they were all deleted
375 # there's no pageID 0 so we use that. the caller is responsible
376 # for deciding what to do with a file containing only the
377 # siteinfo information and the mw tags.
378 if ( ! $this->firstPageWritten ) {
379 $firstPageID = str_pad( 0, 9,
"0", STR_PAD_LEFT );
380 $lastPageID = str_pad( 0, 9,
"0", STR_PAD_LEFT );
383 $firstPageID = str_pad( $this->firstPageWritten, 9,
"0", STR_PAD_LEFT );
384 $lastPageID = str_pad( $this->lastPageWritten, 9,
"0", STR_PAD_LEFT );
386 for ( $i = 0; $i < count( $filenameList ); $i++ ) {
387 $checkpointNameFilledIn = sprintf( $this->checkpointFiles[$i], $firstPageID, $lastPageID );
388 $fileinfo = pathinfo( $filenameList[$i] );
389 $newFilenames[] = $fileinfo[
'dirname'] .
'/' . $checkpointNameFilledIn;
391 $this->egress->closeAndRename( $newFilenames );
414 global $wgContentHandlerUseDB;
416 $prefetchNotTried =
true;
421 static $consecutiveFailedTextRetrievals = 0;
428 $oldConsecutiveFailedTextRetrievals = $consecutiveFailedTextRetrievals;
429 $consecutiveFailedTextRetrievals = 0;
431 while ( $failures < $this->maxFailures ) {
442 if ( $text ===
false && isset( $this->prefetch ) && $prefetchNotTried ) {
443 $prefetchNotTried =
false;
444 $tryIsPrefetch =
true;
445 $text = $this->prefetch->prefetch( intval( $this->thisPage ),
446 intval( $this->thisRev ) );
447 if ( $text ===
null ) {
452 if ( $text ===
false ) {
454 $tryIsPrefetch =
false;
455 if ( $this->spawn ) {
464 if ( $text !==
false ) {
469 if ( $text ===
false ) {
470 throw new MWException(
"Generic error while obtaining text for id " . $id );
477 $revID = intval( $this->thisRev );
478 if ( ! isset( $this->db ) ) {
482 $revLength = strlen( $text );
483 if ( $wgContentHandlerUseDB ) {
484 $row = $this->db->selectRow(
486 array(
'rev_len',
'rev_content_model' ),
487 array(
'rev_id' => $revID ),
494 $revLength = $row->rev_len;
500 $revLength = $this->db->selectField(
'revision',
'rev_len',
array(
'rev_id' => $revID ) );
503 if ( strlen( $text ) == $revLength ) {
504 if ( $tryIsPrefetch ) {
505 $this->prefetchCount++;
511 throw new MWException(
"Received text is unplausible for id " . $id );
513 }
catch ( Exception
$e ) {
514 $msg =
"getting/checking text " . $id .
" failed (" .
$e->getMessage() .
")";
515 if ( $failures + 1 < $this->maxFailures ) {
516 $msg .=
" (Will retry " . ( $this->maxFailures - $failures - 1 ) .
" more times)";
525 if ( ! $tryIsPrefetch ) {
529 sleep( $this->failureTimeout );
532 if ( $this->spawn ) {
536 }
catch ( Exception
$e ) {
537 $this->
progress(
"Rebooting getText infrastructure failed (" . $e->getMessage() .
")" .
538 " Trying to continue anyways" );
548 $consecutiveFailedTextRetrievals = $oldConsecutiveFailedTextRetrievals + 1;
549 if ( $consecutiveFailedTextRetrievals > $this->maxConsecutiveFailedTextRetrievals ) {
550 throw new MWException(
"Graceful storage failure" );
564 if ( ! isset( $this->db ) ) {
565 throw new MWException( __METHOD__ .
"No database available" );
567 $row = $this->db->selectRow(
'text',
568 array(
'old_text',
'old_flags' ),
569 array(
'old_id' => $id ),
572 if ( $text ===
false ) {
575 $stripped = str_replace(
"\r",
"", $text );
582 if ( !$this->spawnProc ) {
594 if ( file_exists(
"$IP/../multiversion/MWScript.php" ) ) {
596 array_map(
'wfEscapeShellArg',
599 "$IP/../multiversion/MWScript.php",
605 array_map(
'wfEscapeShellArg',
608 "$IP/maintenance/fetchText.php",
612 0 =>
array(
"pipe",
"r" ),
613 1 =>
array(
"pipe",
"w" ),
614 2 =>
array(
"file",
"/dev/null",
"a" ) );
617 $this->
progress(
"Spawning database subprocess: $cmd" );
618 $this->spawnProc = proc_open( $cmd, $spec, $pipes );
619 if ( !$this->spawnProc ) {
621 $this->
progress(
"Subprocess spawn failed." );
634 if ( $this->spawnRead ) {
635 fclose( $this->spawnRead );
637 $this->spawnRead =
false;
638 if ( $this->spawnWrite ) {
639 fclose( $this->spawnWrite );
641 $this->spawnWrite =
false;
642 if ( $this->spawnErr ) {
643 fclose( $this->spawnErr );
645 $this->spawnErr =
false;
646 if ( $this->spawnProc ) {
647 pclose( $this->spawnProc );
649 $this->spawnProc =
false;
656 $ok = fwrite( $this->spawnWrite,
"$id\n" );
662 $ok = fflush( $this->spawnWrite );
670 $newId = fgets( $this->spawnRead );
671 if ( $newId ===
false ) {
674 if ( $id != intval( $newId ) ) {
678 $len = fgets( $this->spawnRead );
680 if ( $len ===
false ) {
684 $nbytes = intval( $len );
693 while ( $nbytes > strlen( $text ) ) {
694 $buffer = fread( $this->spawnRead, $nbytes - strlen( $text ) );
695 if ( $buffer ===
false ) {
701 $gotbytes = strlen( $text );
702 if ( $gotbytes != $nbytes ) {
703 $this->
progress(
"Expected $nbytes bytes from database subprocess, got $gotbytes " );
708 $stripped = str_replace(
"\r",
"", $text );
714 $this->checkpointJustWritten =
false;
717 $this->lastName =
$name;
719 if (
$name ==
'revision' ) {
720 $this->state =
$name;
721 $this->egress->writeOpenPage(
null, $this->buffer );
723 } elseif (
$name ==
'page' ) {
724 $this->state =
$name;
725 if ( $this->atStart ) {
726 $this->egress->writeOpenStream( $this->buffer );
728 $this->atStart =
false;
734 $this->openElement =
array(
$name,
array(
'xml:space' =>
'preserve' ) );
735 if ( strlen( $text ) > 0 ) {
744 $this->checkpointJustWritten =
false;
746 if ( $this->openElement ) {
749 $this->buffer .=
"</$name>";
752 if (
$name ==
'revision' ) {
753 $this->egress->writeRevision(
null, $this->buffer );
756 } elseif (
$name ==
'page' ) {
757 if ( ! $this->firstPageWritten ) {
758 $this->firstPageWritten = trim( $this->thisPage );
760 $this->lastPageWritten = trim( $this->thisPage );
761 if ( $this->timeExceeded ) {
762 $this->egress->writeClosePage( $this->buffer );
765 $this->egress->sink->write(
"\n" );
767 $this->buffer = $this->xmlwriterobj->closeStream();
768 $this->egress->writeCloseStream( $this->buffer );
771 $this->thisPage =
"";
774 $filenameList = (
array)$this->egress->getFilenames();
775 $newFilenames =
array();
776 $firstPageID = str_pad( $this->firstPageWritten, 9,
"0", STR_PAD_LEFT );
777 $lastPageID = str_pad( $this->lastPageWritten, 9,
"0", STR_PAD_LEFT );
778 for ( $i = 0; $i < count( $filenameList ); $i++ ) {
779 $checkpointNameFilledIn = sprintf( $this->checkpointFiles[$i], $firstPageID, $lastPageID );
780 $fileinfo = pathinfo( $filenameList[$i] );
781 $newFilenames[] = $fileinfo[
'dirname'] .
'/' . $checkpointNameFilledIn;
783 $this->egress->closeRenameAndReopen( $newFilenames );
784 $this->buffer = $this->xmlwriterobj->openStream();
785 $this->timeExceeded =
false;
787 $this->firstPageWritten =
false;
788 $this->checkpointJustWritten =
true;
791 $this->egress->writeClosePage( $this->buffer );
793 $this->thisPage =
"";
796 } elseif (
$name ==
'mediawiki' ) {
797 $this->egress->writeCloseStream( $this->buffer );
804 if ( $this->lastName ==
"id" ) {
805 if ( $this->state ==
"revision" ) {
806 $this->thisRev .= $data;
807 } elseif ( $this->state ==
"page" ) {
808 $this->thisPage .= $data;
813 if ( $this->checkpointJustWritten ) {
814 if ( $data[0] ==
"\n" ) {
815 $data = substr( $data, 1 );
817 $this->checkpointJustWritten =
false;
819 $this->buffer .= htmlspecialchars( $data );
823 if ( $this->openElement ) {
824 $this->buffer .=
Xml::element( $this->openElement[0], $this->openElement[1], $style );
825 $this->openElement =
false;