MediaWiki
REL1_21
|
00001 <?php 00038 abstract class FileBackendStore extends FileBackend { 00040 protected $memCache; 00042 protected $cheapCache; // Map of paths to small (RAM/disk) cache items 00044 protected $expensiveCache; // Map of paths to large (RAM/disk) cache items 00045 00047 protected $shardViaHashLevels = array(); // (container name => config array) 00048 00049 protected $maxFileSize = 4294967296; // integer bytes (4GiB) 00050 00051 const CACHE_TTL = 10; // integer; TTL in seconds for process cache entries 00052 00058 public function __construct( array $config ) { 00059 parent::__construct( $config ); 00060 $this->memCache = new EmptyBagOStuff(); // disabled by default 00061 $this->cheapCache = new ProcessCacheLRU( 300 ); 00062 $this->expensiveCache = new ProcessCacheLRU( 5 ); 00063 } 00064 00072 final public function maxFileSizeInternal() { 00073 return $this->maxFileSize; 00074 } 00075 00085 abstract public function isPathUsableInternal( $storagePath ); 00086 00106 final public function createInternal( array $params ) { 00107 wfProfileIn( __METHOD__ ); 00108 wfProfileIn( __METHOD__ . '-' . $this->name ); 00109 if ( strlen( $params['content'] ) > $this->maxFileSizeInternal() ) { 00110 $status = Status::newFatal( 'backend-fail-maxsize', 00111 $params['dst'], $this->maxFileSizeInternal() ); 00112 } else { 00113 $status = $this->doCreateInternal( $params ); 00114 $this->clearCache( array( $params['dst'] ) ); 00115 if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) { 00116 $this->deleteFileCache( $params['dst'] ); // persistent cache 00117 } 00118 } 00119 wfProfileOut( __METHOD__ . '-' . $this->name ); 00120 wfProfileOut( __METHOD__ ); 00121 return $status; 00122 } 00123 00128 abstract protected function doCreateInternal( array $params ); 00129 00149 final public function storeInternal( array $params ) { 00150 wfProfileIn( __METHOD__ ); 00151 wfProfileIn( __METHOD__ . '-' . $this->name ); 00152 if ( filesize( $params['src'] ) > $this->maxFileSizeInternal() ) { 00153 $status = Status::newFatal( 'backend-fail-maxsize', 00154 $params['dst'], $this->maxFileSizeInternal() ); 00155 } else { 00156 $status = $this->doStoreInternal( $params ); 00157 $this->clearCache( array( $params['dst'] ) ); 00158 if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) { 00159 $this->deleteFileCache( $params['dst'] ); // persistent cache 00160 } 00161 } 00162 wfProfileOut( __METHOD__ . '-' . $this->name ); 00163 wfProfileOut( __METHOD__ ); 00164 return $status; 00165 } 00166 00171 abstract protected function doStoreInternal( array $params ); 00172 00192 final public function copyInternal( array $params ) { 00193 wfProfileIn( __METHOD__ ); 00194 wfProfileIn( __METHOD__ . '-' . $this->name ); 00195 $status = $this->doCopyInternal( $params ); 00196 $this->clearCache( array( $params['dst'] ) ); 00197 if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) { 00198 $this->deleteFileCache( $params['dst'] ); // persistent cache 00199 } 00200 wfProfileOut( __METHOD__ . '-' . $this->name ); 00201 wfProfileOut( __METHOD__ ); 00202 return $status; 00203 } 00204 00209 abstract protected function doCopyInternal( array $params ); 00210 00225 final public function deleteInternal( array $params ) { 00226 wfProfileIn( __METHOD__ ); 00227 wfProfileIn( __METHOD__ . '-' . $this->name ); 00228 $status = $this->doDeleteInternal( $params ); 00229 $this->clearCache( array( $params['src'] ) ); 00230 $this->deleteFileCache( $params['src'] ); // persistent cache 00231 wfProfileOut( __METHOD__ . '-' . $this->name ); 00232 wfProfileOut( __METHOD__ ); 00233 return $status; 00234 } 00235 00240 abstract protected function doDeleteInternal( array $params ); 00241 00261 final public function moveInternal( array $params ) { 00262 wfProfileIn( __METHOD__ ); 00263 wfProfileIn( __METHOD__ . '-' . $this->name ); 00264 $status = $this->doMoveInternal( $params ); 00265 $this->clearCache( array( $params['src'], $params['dst'] ) ); 00266 $this->deleteFileCache( $params['src'] ); // persistent cache 00267 if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) { 00268 $this->deleteFileCache( $params['dst'] ); // persistent cache 00269 } 00270 wfProfileOut( __METHOD__ . '-' . $this->name ); 00271 wfProfileOut( __METHOD__ ); 00272 return $status; 00273 } 00274 00279 protected function doMoveInternal( array $params ) { 00280 unset( $params['async'] ); // two steps, won't work here :) 00281 // Copy source to dest 00282 $status = $this->copyInternal( $params ); 00283 if ( $status->isOK() ) { 00284 // Delete source (only fails due to races or medium going down) 00285 $status->merge( $this->deleteInternal( array( 'src' => $params['src'] ) ) ); 00286 $status->setResult( true, $status->value ); // ignore delete() errors 00287 } 00288 return $status; 00289 } 00290 00306 final public function describeInternal( array $params ) { 00307 wfProfileIn( __METHOD__ ); 00308 wfProfileIn( __METHOD__ . '-' . $this->name ); 00309 $status = $this->doDescribeInternal( $params ); 00310 $this->clearCache( array( $params['src'] ) ); 00311 $this->deleteFileCache( $params['src'] ); // persistent cache 00312 wfProfileOut( __METHOD__ . '-' . $this->name ); 00313 wfProfileOut( __METHOD__ ); 00314 return $status; 00315 } 00316 00321 protected function doDescribeInternal( array $params ) { 00322 return Status::newGood(); 00323 } 00324 00332 final public function nullInternal( array $params ) { 00333 return Status::newGood(); 00334 } 00335 00340 final public function concatenate( array $params ) { 00341 wfProfileIn( __METHOD__ ); 00342 wfProfileIn( __METHOD__ . '-' . $this->name ); 00343 $status = Status::newGood(); 00344 00345 // Try to lock the source files for the scope of this function 00346 $scopeLockS = $this->getScopedFileLocks( $params['srcs'], LockManager::LOCK_UW, $status ); 00347 if ( $status->isOK() ) { 00348 // Actually do the file concatenation... 00349 $start_time = microtime( true ); 00350 $status->merge( $this->doConcatenate( $params ) ); 00351 $sec = microtime( true ) - $start_time; 00352 if ( !$status->isOK() ) { 00353 wfDebugLog( 'FileOperation', get_class( $this ) . " failed to concatenate " . 00354 count( $params['srcs'] ) . " file(s) [$sec sec]" ); 00355 } 00356 } 00357 00358 wfProfileOut( __METHOD__ . '-' . $this->name ); 00359 wfProfileOut( __METHOD__ ); 00360 return $status; 00361 } 00362 00367 protected function doConcatenate( array $params ) { 00368 $status = Status::newGood(); 00369 $tmpPath = $params['dst']; // convenience 00370 unset( $params['latest'] ); // sanity 00371 00372 // Check that the specified temp file is valid... 00373 wfSuppressWarnings(); 00374 $ok = ( is_file( $tmpPath ) && filesize( $tmpPath ) == 0 ); 00375 wfRestoreWarnings(); 00376 if ( !$ok ) { // not present or not empty 00377 $status->fatal( 'backend-fail-opentemp', $tmpPath ); 00378 return $status; 00379 } 00380 00381 // Get local FS versions of the chunks needed for the concatenation... 00382 $fsFiles = $this->getLocalReferenceMulti( $params ); 00383 foreach ( $fsFiles as $path => &$fsFile ) { 00384 if ( !$fsFile ) { // chunk failed to download? 00385 $fsFile = $this->getLocalReference( array( 'src' => $path ) ); 00386 if ( !$fsFile ) { // retry failed? 00387 $status->fatal( 'backend-fail-read', $path ); 00388 return $status; 00389 } 00390 } 00391 } 00392 unset( $fsFile ); // unset reference so we can reuse $fsFile 00393 00394 // Get a handle for the destination temp file 00395 $tmpHandle = fopen( $tmpPath, 'ab' ); 00396 if ( $tmpHandle === false ) { 00397 $status->fatal( 'backend-fail-opentemp', $tmpPath ); 00398 return $status; 00399 } 00400 00401 // Build up the temp file using the source chunks (in order)... 00402 foreach ( $fsFiles as $virtualSource => $fsFile ) { 00403 // Get a handle to the local FS version 00404 $sourceHandle = fopen( $fsFile->getPath(), 'rb' ); 00405 if ( $sourceHandle === false ) { 00406 fclose( $tmpHandle ); 00407 $status->fatal( 'backend-fail-read', $virtualSource ); 00408 return $status; 00409 } 00410 // Append chunk to file (pass chunk size to avoid magic quotes) 00411 if ( !stream_copy_to_stream( $sourceHandle, $tmpHandle ) ) { 00412 fclose( $sourceHandle ); 00413 fclose( $tmpHandle ); 00414 $status->fatal( 'backend-fail-writetemp', $tmpPath ); 00415 return $status; 00416 } 00417 fclose( $sourceHandle ); 00418 } 00419 if ( !fclose( $tmpHandle ) ) { 00420 $status->fatal( 'backend-fail-closetemp', $tmpPath ); 00421 return $status; 00422 } 00423 00424 clearstatcache(); // temp file changed 00425 00426 return $status; 00427 } 00428 00433 final protected function doPrepare( array $params ) { 00434 wfProfileIn( __METHOD__ ); 00435 wfProfileIn( __METHOD__ . '-' . $this->name ); 00436 00437 $status = Status::newGood(); 00438 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] ); 00439 if ( $dir === null ) { 00440 $status->fatal( 'backend-fail-invalidpath', $params['dir'] ); 00441 wfProfileOut( __METHOD__ . '-' . $this->name ); 00442 wfProfileOut( __METHOD__ ); 00443 return $status; // invalid storage path 00444 } 00445 00446 if ( $shard !== null ) { // confined to a single container/shard 00447 $status->merge( $this->doPrepareInternal( $fullCont, $dir, $params ) ); 00448 } else { // directory is on several shards 00449 wfDebug( __METHOD__ . ": iterating over all container shards.\n" ); 00450 list( , $shortCont, ) = self::splitStoragePath( $params['dir'] ); 00451 foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) { 00452 $status->merge( $this->doPrepareInternal( "{$fullCont}{$suffix}", $dir, $params ) ); 00453 } 00454 } 00455 00456 wfProfileOut( __METHOD__ . '-' . $this->name ); 00457 wfProfileOut( __METHOD__ ); 00458 return $status; 00459 } 00460 00465 protected function doPrepareInternal( $container, $dir, array $params ) { 00466 return Status::newGood(); 00467 } 00468 00473 final protected function doSecure( array $params ) { 00474 wfProfileIn( __METHOD__ ); 00475 wfProfileIn( __METHOD__ . '-' . $this->name ); 00476 $status = Status::newGood(); 00477 00478 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] ); 00479 if ( $dir === null ) { 00480 $status->fatal( 'backend-fail-invalidpath', $params['dir'] ); 00481 wfProfileOut( __METHOD__ . '-' . $this->name ); 00482 wfProfileOut( __METHOD__ ); 00483 return $status; // invalid storage path 00484 } 00485 00486 if ( $shard !== null ) { // confined to a single container/shard 00487 $status->merge( $this->doSecureInternal( $fullCont, $dir, $params ) ); 00488 } else { // directory is on several shards 00489 wfDebug( __METHOD__ . ": iterating over all container shards.\n" ); 00490 list( , $shortCont, ) = self::splitStoragePath( $params['dir'] ); 00491 foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) { 00492 $status->merge( $this->doSecureInternal( "{$fullCont}{$suffix}", $dir, $params ) ); 00493 } 00494 } 00495 00496 wfProfileOut( __METHOD__ . '-' . $this->name ); 00497 wfProfileOut( __METHOD__ ); 00498 return $status; 00499 } 00500 00505 protected function doSecureInternal( $container, $dir, array $params ) { 00506 return Status::newGood(); 00507 } 00508 00513 final protected function doPublish( array $params ) { 00514 wfProfileIn( __METHOD__ ); 00515 wfProfileIn( __METHOD__ . '-' . $this->name ); 00516 $status = Status::newGood(); 00517 00518 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] ); 00519 if ( $dir === null ) { 00520 $status->fatal( 'backend-fail-invalidpath', $params['dir'] ); 00521 wfProfileOut( __METHOD__ . '-' . $this->name ); 00522 wfProfileOut( __METHOD__ ); 00523 return $status; // invalid storage path 00524 } 00525 00526 if ( $shard !== null ) { // confined to a single container/shard 00527 $status->merge( $this->doPublishInternal( $fullCont, $dir, $params ) ); 00528 } else { // directory is on several shards 00529 wfDebug( __METHOD__ . ": iterating over all container shards.\n" ); 00530 list( , $shortCont, ) = self::splitStoragePath( $params['dir'] ); 00531 foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) { 00532 $status->merge( $this->doPublishInternal( "{$fullCont}{$suffix}", $dir, $params ) ); 00533 } 00534 } 00535 00536 wfProfileOut( __METHOD__ . '-' . $this->name ); 00537 wfProfileOut( __METHOD__ ); 00538 return $status; 00539 } 00540 00545 protected function doPublishInternal( $container, $dir, array $params ) { 00546 return Status::newGood(); 00547 } 00548 00553 final protected function doClean( array $params ) { 00554 wfProfileIn( __METHOD__ ); 00555 wfProfileIn( __METHOD__ . '-' . $this->name ); 00556 $status = Status::newGood(); 00557 00558 // Recursive: first delete all empty subdirs recursively 00559 if ( !empty( $params['recursive'] ) && !$this->directoriesAreVirtual() ) { 00560 $subDirsRel = $this->getTopDirectoryList( array( 'dir' => $params['dir'] ) ); 00561 if ( $subDirsRel !== null ) { // no errors 00562 foreach ( $subDirsRel as $subDirRel ) { 00563 $subDir = $params['dir'] . "/{$subDirRel}"; // full path 00564 $status->merge( $this->doClean( array( 'dir' => $subDir ) + $params ) ); 00565 } 00566 unset( $subDirsRel ); // free directory for rmdir() on Windows (for FS backends) 00567 } 00568 } 00569 00570 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] ); 00571 if ( $dir === null ) { 00572 $status->fatal( 'backend-fail-invalidpath', $params['dir'] ); 00573 wfProfileOut( __METHOD__ . '-' . $this->name ); 00574 wfProfileOut( __METHOD__ ); 00575 return $status; // invalid storage path 00576 } 00577 00578 // Attempt to lock this directory... 00579 $filesLockEx = array( $params['dir'] ); 00580 $scopedLockE = $this->getScopedFileLocks( $filesLockEx, LockManager::LOCK_EX, $status ); 00581 if ( !$status->isOK() ) { 00582 wfProfileOut( __METHOD__ . '-' . $this->name ); 00583 wfProfileOut( __METHOD__ ); 00584 return $status; // abort 00585 } 00586 00587 if ( $shard !== null ) { // confined to a single container/shard 00588 $status->merge( $this->doCleanInternal( $fullCont, $dir, $params ) ); 00589 $this->deleteContainerCache( $fullCont ); // purge cache 00590 } else { // directory is on several shards 00591 wfDebug( __METHOD__ . ": iterating over all container shards.\n" ); 00592 list( , $shortCont, ) = self::splitStoragePath( $params['dir'] ); 00593 foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) { 00594 $status->merge( $this->doCleanInternal( "{$fullCont}{$suffix}", $dir, $params ) ); 00595 $this->deleteContainerCache( "{$fullCont}{$suffix}" ); // purge cache 00596 } 00597 } 00598 00599 wfProfileOut( __METHOD__ . '-' . $this->name ); 00600 wfProfileOut( __METHOD__ ); 00601 return $status; 00602 } 00603 00608 protected function doCleanInternal( $container, $dir, array $params ) { 00609 return Status::newGood(); 00610 } 00611 00616 final public function fileExists( array $params ) { 00617 wfProfileIn( __METHOD__ ); 00618 wfProfileIn( __METHOD__ . '-' . $this->name ); 00619 $stat = $this->getFileStat( $params ); 00620 wfProfileOut( __METHOD__ . '-' . $this->name ); 00621 wfProfileOut( __METHOD__ ); 00622 return ( $stat === null ) ? null : (bool)$stat; // null => failure 00623 } 00624 00629 final public function getFileTimestamp( array $params ) { 00630 wfProfileIn( __METHOD__ ); 00631 wfProfileIn( __METHOD__ . '-' . $this->name ); 00632 $stat = $this->getFileStat( $params ); 00633 wfProfileOut( __METHOD__ . '-' . $this->name ); 00634 wfProfileOut( __METHOD__ ); 00635 return $stat ? $stat['mtime'] : false; 00636 } 00637 00642 final public function getFileSize( array $params ) { 00643 wfProfileIn( __METHOD__ ); 00644 wfProfileIn( __METHOD__ . '-' . $this->name ); 00645 $stat = $this->getFileStat( $params ); 00646 wfProfileOut( __METHOD__ . '-' . $this->name ); 00647 wfProfileOut( __METHOD__ ); 00648 return $stat ? $stat['size'] : false; 00649 } 00650 00655 final public function getFileStat( array $params ) { 00656 $path = self::normalizeStoragePath( $params['src'] ); 00657 if ( $path === null ) { 00658 return false; // invalid storage path 00659 } 00660 wfProfileIn( __METHOD__ ); 00661 wfProfileIn( __METHOD__ . '-' . $this->name ); 00662 $latest = !empty( $params['latest'] ); // use latest data? 00663 if ( !$this->cheapCache->has( $path, 'stat', self::CACHE_TTL ) ) { 00664 $this->primeFileCache( array( $path ) ); // check persistent cache 00665 } 00666 if ( $this->cheapCache->has( $path, 'stat', self::CACHE_TTL ) ) { 00667 $stat = $this->cheapCache->get( $path, 'stat' ); 00668 // If we want the latest data, check that this cached 00669 // value was in fact fetched with the latest available data. 00670 if ( is_array( $stat ) ) { 00671 if ( !$latest || $stat['latest'] ) { 00672 wfProfileOut( __METHOD__ . '-' . $this->name ); 00673 wfProfileOut( __METHOD__ ); 00674 return $stat; 00675 } 00676 } elseif ( in_array( $stat, array( 'NOT_EXIST', 'NOT_EXIST_LATEST' ) ) ) { 00677 if ( !$latest || $stat === 'NOT_EXIST_LATEST' ) { 00678 wfProfileOut( __METHOD__ . '-' . $this->name ); 00679 wfProfileOut( __METHOD__ ); 00680 return false; 00681 } 00682 } 00683 } 00684 wfProfileIn( __METHOD__ . '-miss' ); 00685 wfProfileIn( __METHOD__ . '-miss-' . $this->name ); 00686 $stat = $this->doGetFileStat( $params ); 00687 wfProfileOut( __METHOD__ . '-miss-' . $this->name ); 00688 wfProfileOut( __METHOD__ . '-miss' ); 00689 if ( is_array( $stat ) ) { // file exists 00690 $stat['latest'] = $latest; 00691 $this->cheapCache->set( $path, 'stat', $stat ); 00692 $this->setFileCache( $path, $stat ); // update persistent cache 00693 if ( isset( $stat['sha1'] ) ) { // some backends store SHA-1 as metadata 00694 $this->cheapCache->set( $path, 'sha1', 00695 array( 'hash' => $stat['sha1'], 'latest' => $latest ) ); 00696 } 00697 } elseif ( $stat === false ) { // file does not exist 00698 $this->cheapCache->set( $path, 'stat', $latest ? 'NOT_EXIST_LATEST' : 'NOT_EXIST' ); 00699 wfDebug( __METHOD__ . ": File $path does not exist.\n" ); 00700 } else { // an error occurred 00701 wfDebug( __METHOD__ . ": Could not stat file $path.\n" ); 00702 } 00703 wfProfileOut( __METHOD__ . '-' . $this->name ); 00704 wfProfileOut( __METHOD__ ); 00705 return $stat; 00706 } 00707 00711 abstract protected function doGetFileStat( array $params ); 00712 00717 public function getFileContentsMulti( array $params ) { 00718 wfProfileIn( __METHOD__ ); 00719 wfProfileIn( __METHOD__ . '-' . $this->name ); 00720 00721 $params = $this->setConcurrencyFlags( $params ); 00722 $contents = $this->doGetFileContentsMulti( $params ); 00723 00724 wfProfileOut( __METHOD__ . '-' . $this->name ); 00725 wfProfileOut( __METHOD__ ); 00726 return $contents; 00727 } 00728 00733 protected function doGetFileContentsMulti( array $params ) { 00734 $contents = array(); 00735 foreach ( $this->doGetLocalReferenceMulti( $params ) as $path => $fsFile ) { 00736 wfSuppressWarnings(); 00737 $contents[$path] = $fsFile ? file_get_contents( $fsFile->getPath() ) : false; 00738 wfRestoreWarnings(); 00739 } 00740 return $contents; 00741 } 00742 00747 final public function getFileSha1Base36( array $params ) { 00748 $path = self::normalizeStoragePath( $params['src'] ); 00749 if ( $path === null ) { 00750 return false; // invalid storage path 00751 } 00752 wfProfileIn( __METHOD__ ); 00753 wfProfileIn( __METHOD__ . '-' . $this->name ); 00754 $latest = !empty( $params['latest'] ); // use latest data? 00755 if ( $this->cheapCache->has( $path, 'sha1', self::CACHE_TTL ) ) { 00756 $stat = $this->cheapCache->get( $path, 'sha1' ); 00757 // If we want the latest data, check that this cached 00758 // value was in fact fetched with the latest available data. 00759 if ( !$latest || $stat['latest'] ) { 00760 wfProfileOut( __METHOD__ . '-' . $this->name ); 00761 wfProfileOut( __METHOD__ ); 00762 return $stat['hash']; 00763 } 00764 } 00765 wfProfileIn( __METHOD__ . '-miss' ); 00766 wfProfileIn( __METHOD__ . '-miss-' . $this->name ); 00767 $hash = $this->doGetFileSha1Base36( $params ); 00768 wfProfileOut( __METHOD__ . '-miss-' . $this->name ); 00769 wfProfileOut( __METHOD__ . '-miss' ); 00770 $this->cheapCache->set( $path, 'sha1', array( 'hash' => $hash, 'latest' => $latest ) ); 00771 wfProfileOut( __METHOD__ . '-' . $this->name ); 00772 wfProfileOut( __METHOD__ ); 00773 return $hash; 00774 } 00775 00780 protected function doGetFileSha1Base36( array $params ) { 00781 $fsFile = $this->getLocalReference( $params ); 00782 if ( !$fsFile ) { 00783 return false; 00784 } else { 00785 return $fsFile->getSha1Base36(); 00786 } 00787 } 00788 00793 final public function getFileProps( array $params ) { 00794 wfProfileIn( __METHOD__ ); 00795 wfProfileIn( __METHOD__ . '-' . $this->name ); 00796 $fsFile = $this->getLocalReference( $params ); 00797 $props = $fsFile ? $fsFile->getProps() : FSFile::placeholderProps(); 00798 wfProfileOut( __METHOD__ . '-' . $this->name ); 00799 wfProfileOut( __METHOD__ ); 00800 return $props; 00801 } 00802 00807 final public function getLocalReferenceMulti( array $params ) { 00808 wfProfileIn( __METHOD__ ); 00809 wfProfileIn( __METHOD__ . '-' . $this->name ); 00810 00811 $params = $this->setConcurrencyFlags( $params ); 00812 00813 $fsFiles = array(); // (path => FSFile) 00814 $latest = !empty( $params['latest'] ); // use latest data? 00815 // Reuse any files already in process cache... 00816 foreach ( $params['srcs'] as $src ) { 00817 $path = self::normalizeStoragePath( $src ); 00818 if ( $path === null ) { 00819 $fsFiles[$src] = null; // invalid storage path 00820 } elseif ( $this->expensiveCache->has( $path, 'localRef' ) ) { 00821 $val = $this->expensiveCache->get( $path, 'localRef' ); 00822 // If we want the latest data, check that this cached 00823 // value was in fact fetched with the latest available data. 00824 if ( !$latest || $val['latest'] ) { 00825 $fsFiles[$src] = $val['object']; 00826 } 00827 } 00828 } 00829 // Fetch local references of any remaning files... 00830 $params['srcs'] = array_diff( $params['srcs'], array_keys( $fsFiles ) ); 00831 foreach ( $this->doGetLocalReferenceMulti( $params ) as $path => $fsFile ) { 00832 $fsFiles[$path] = $fsFile; 00833 if ( $fsFile ) { // update the process cache... 00834 $this->expensiveCache->set( $path, 'localRef', 00835 array( 'object' => $fsFile, 'latest' => $latest ) ); 00836 } 00837 } 00838 00839 wfProfileOut( __METHOD__ . '-' . $this->name ); 00840 wfProfileOut( __METHOD__ ); 00841 return $fsFiles; 00842 } 00843 00848 protected function doGetLocalReferenceMulti( array $params ) { 00849 return $this->doGetLocalCopyMulti( $params ); 00850 } 00851 00856 final public function getLocalCopyMulti( array $params ) { 00857 wfProfileIn( __METHOD__ ); 00858 wfProfileIn( __METHOD__ . '-' . $this->name ); 00859 00860 $params = $this->setConcurrencyFlags( $params ); 00861 $tmpFiles = $this->doGetLocalCopyMulti( $params ); 00862 00863 wfProfileOut( __METHOD__ . '-' . $this->name ); 00864 wfProfileOut( __METHOD__ ); 00865 return $tmpFiles; 00866 } 00867 00872 abstract protected function doGetLocalCopyMulti( array $params ); 00873 00878 public function getFileHttpUrl( array $params ) { 00879 return null; // not supported 00880 } 00881 00886 final public function streamFile( array $params ) { 00887 wfProfileIn( __METHOD__ ); 00888 wfProfileIn( __METHOD__ . '-' . $this->name ); 00889 $status = Status::newGood(); 00890 00891 $info = $this->getFileStat( $params ); 00892 if ( !$info ) { // let StreamFile handle the 404 00893 $status->fatal( 'backend-fail-notexists', $params['src'] ); 00894 } 00895 00896 // Set output buffer and HTTP headers for stream 00897 $extraHeaders = isset( $params['headers'] ) ? $params['headers'] : array(); 00898 $res = StreamFile::prepareForStream( $params['src'], $info, $extraHeaders ); 00899 if ( $res == StreamFile::NOT_MODIFIED ) { 00900 // do nothing; client cache is up to date 00901 } elseif ( $res == StreamFile::READY_STREAM ) { 00902 wfProfileIn( __METHOD__ . '-send' ); 00903 wfProfileIn( __METHOD__ . '-send-' . $this->name ); 00904 $status = $this->doStreamFile( $params ); 00905 wfProfileOut( __METHOD__ . '-send-' . $this->name ); 00906 wfProfileOut( __METHOD__ . '-send' ); 00907 if ( !$status->isOK() ) { 00908 // Per bug 41113, nasty things can happen if bad cache entries get 00909 // stuck in cache. It's also possible that this error can come up 00910 // with simple race conditions. Clear out the stat cache to be safe. 00911 $this->clearCache( array( $params['src'] ) ); 00912 $this->deleteFileCache( $params['src'] ); 00913 trigger_error( "Bad stat cache or race condition for file {$params['src']}." ); 00914 } 00915 } else { 00916 $status->fatal( 'backend-fail-stream', $params['src'] ); 00917 } 00918 00919 wfProfileOut( __METHOD__ . '-' . $this->name ); 00920 wfProfileOut( __METHOD__ ); 00921 return $status; 00922 } 00923 00928 protected function doStreamFile( array $params ) { 00929 $status = Status::newGood(); 00930 00931 $fsFile = $this->getLocalReference( $params ); 00932 if ( !$fsFile ) { 00933 $status->fatal( 'backend-fail-stream', $params['src'] ); 00934 } elseif ( !readfile( $fsFile->getPath() ) ) { 00935 $status->fatal( 'backend-fail-stream', $params['src'] ); 00936 } 00937 00938 return $status; 00939 } 00940 00945 final public function directoryExists( array $params ) { 00946 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] ); 00947 if ( $dir === null ) { 00948 return false; // invalid storage path 00949 } 00950 if ( $shard !== null ) { // confined to a single container/shard 00951 return $this->doDirectoryExists( $fullCont, $dir, $params ); 00952 } else { // directory is on several shards 00953 wfDebug( __METHOD__ . ": iterating over all container shards.\n" ); 00954 list( , $shortCont, ) = self::splitStoragePath( $params['dir'] ); 00955 $res = false; // response 00956 foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) { 00957 $exists = $this->doDirectoryExists( "{$fullCont}{$suffix}", $dir, $params ); 00958 if ( $exists ) { 00959 $res = true; 00960 break; // found one! 00961 } elseif ( $exists === null ) { // error? 00962 $res = null; // if we don't find anything, it is indeterminate 00963 } 00964 } 00965 return $res; 00966 } 00967 } 00968 00977 abstract protected function doDirectoryExists( $container, $dir, array $params ); 00978 00983 final public function getDirectoryList( array $params ) { 00984 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] ); 00985 if ( $dir === null ) { // invalid storage path 00986 return null; 00987 } 00988 if ( $shard !== null ) { 00989 // File listing is confined to a single container/shard 00990 return $this->getDirectoryListInternal( $fullCont, $dir, $params ); 00991 } else { 00992 wfDebug( __METHOD__ . ": iterating over all container shards.\n" ); 00993 // File listing spans multiple containers/shards 00994 list( , $shortCont, ) = self::splitStoragePath( $params['dir'] ); 00995 return new FileBackendStoreShardDirIterator( $this, 00996 $fullCont, $dir, $this->getContainerSuffixes( $shortCont ), $params ); 00997 } 00998 } 00999 01010 abstract public function getDirectoryListInternal( $container, $dir, array $params ); 01011 01016 final public function getFileList( array $params ) { 01017 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] ); 01018 if ( $dir === null ) { // invalid storage path 01019 return null; 01020 } 01021 if ( $shard !== null ) { 01022 // File listing is confined to a single container/shard 01023 return $this->getFileListInternal( $fullCont, $dir, $params ); 01024 } else { 01025 wfDebug( __METHOD__ . ": iterating over all container shards.\n" ); 01026 // File listing spans multiple containers/shards 01027 list( , $shortCont, ) = self::splitStoragePath( $params['dir'] ); 01028 return new FileBackendStoreShardFileIterator( $this, 01029 $fullCont, $dir, $this->getContainerSuffixes( $shortCont ), $params ); 01030 } 01031 } 01032 01043 abstract public function getFileListInternal( $container, $dir, array $params ); 01044 01056 final public function getOperationsInternal( array $ops ) { 01057 $supportedOps = array( 01058 'store' => 'StoreFileOp', 01059 'copy' => 'CopyFileOp', 01060 'move' => 'MoveFileOp', 01061 'delete' => 'DeleteFileOp', 01062 'create' => 'CreateFileOp', 01063 'describe' => 'DescribeFileOp', 01064 'null' => 'NullFileOp' 01065 ); 01066 01067 $performOps = array(); // array of FileOp objects 01068 // Build up ordered array of FileOps... 01069 foreach ( $ops as $operation ) { 01070 $opName = $operation['op']; 01071 if ( isset( $supportedOps[$opName] ) ) { 01072 $class = $supportedOps[$opName]; 01073 // Get params for this operation 01074 $params = $operation; 01075 // Append the FileOp class 01076 $performOps[] = new $class( $this, $params ); 01077 } else { 01078 throw new MWException( "Operation '$opName' is not supported." ); 01079 } 01080 } 01081 01082 return $performOps; 01083 } 01084 01094 final public function getPathsToLockForOpsInternal( array $performOps ) { 01095 // Build up a list of files to lock... 01096 $paths = array( 'sh' => array(), 'ex' => array() ); 01097 foreach ( $performOps as $fileOp ) { 01098 $paths['sh'] = array_merge( $paths['sh'], $fileOp->storagePathsRead() ); 01099 $paths['ex'] = array_merge( $paths['ex'], $fileOp->storagePathsChanged() ); 01100 } 01101 // Optimization: if doing an EX lock anyway, don't also set an SH one 01102 $paths['sh'] = array_diff( $paths['sh'], $paths['ex'] ); 01103 // Get a shared lock on the parent directory of each path changed 01104 $paths['sh'] = array_merge( $paths['sh'], array_map( 'dirname', $paths['ex'] ) ); 01105 01106 return $paths; 01107 } 01108 01113 public function getScopedLocksForOps( array $ops, Status $status ) { 01114 $paths = $this->getPathsToLockForOpsInternal( $this->getOperationsInternal( $ops ) ); 01115 return array( 01116 $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status ), 01117 $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status ) 01118 ); 01119 } 01120 01125 final protected function doOperationsInternal( array $ops, array $opts ) { 01126 wfProfileIn( __METHOD__ ); 01127 wfProfileIn( __METHOD__ . '-' . $this->name ); 01128 $status = Status::newGood(); 01129 01130 // Fix up custom header name/value pairs... 01131 $ops = array_map( array( $this, 'stripInvalidHeadersFromOp' ), $ops ); 01132 01133 // Build up a list of FileOps... 01134 $performOps = $this->getOperationsInternal( $ops ); 01135 01136 // Acquire any locks as needed... 01137 if ( empty( $opts['nonLocking'] ) ) { 01138 // Build up a list of files to lock... 01139 $paths = $this->getPathsToLockForOpsInternal( $performOps ); 01140 // Try to lock those files for the scope of this function... 01141 $scopeLockS = $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status ); 01142 $scopeLockE = $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status ); 01143 if ( !$status->isOK() ) { 01144 wfProfileOut( __METHOD__ . '-' . $this->name ); 01145 wfProfileOut( __METHOD__ ); 01146 return $status; // abort 01147 } 01148 } 01149 01150 // Clear any file cache entries (after locks acquired) 01151 if ( empty( $opts['preserveCache'] ) ) { 01152 $this->clearCache(); 01153 } 01154 01155 // Load from the persistent file and container caches 01156 $this->primeFileCache( $performOps ); 01157 $this->primeContainerCache( $performOps ); 01158 01159 // Actually attempt the operation batch... 01160 $opts = $this->setConcurrencyFlags( $opts ); 01161 $subStatus = FileOpBatch::attempt( $performOps, $opts, $this->fileJournal ); 01162 01163 // Merge errors into status fields 01164 $status->merge( $subStatus ); 01165 $status->success = $subStatus->success; // not done in merge() 01166 01167 wfProfileOut( __METHOD__ . '-' . $this->name ); 01168 wfProfileOut( __METHOD__ ); 01169 return $status; 01170 } 01171 01177 final protected function doQuickOperationsInternal( array $ops ) { 01178 wfProfileIn( __METHOD__ ); 01179 wfProfileIn( __METHOD__ . '-' . $this->name ); 01180 $status = Status::newGood(); 01181 01182 // Fix up custom header name/value pairs... 01183 $ops = array_map( array( $this, 'stripInvalidHeadersFromOp' ), $ops ); 01184 01185 // Clear any file cache entries 01186 $this->clearCache(); 01187 01188 $supportedOps = array( 'create', 'store', 'copy', 'move', 'delete', 'null' ); 01189 $async = ( $this->parallelize === 'implicit' ); 01190 $maxConcurrency = $this->concurrency; // throttle 01191 01192 $statuses = array(); // array of (index => Status) 01193 $fileOpHandles = array(); // list of (index => handle) arrays 01194 $curFileOpHandles = array(); // current handle batch 01195 // Perform the sync-only ops and build up op handles for the async ops... 01196 foreach ( $ops as $index => $params ) { 01197 if ( !in_array( $params['op'], $supportedOps ) ) { 01198 wfProfileOut( __METHOD__ . '-' . $this->name ); 01199 wfProfileOut( __METHOD__ ); 01200 throw new MWException( "Operation '{$params['op']}' is not supported." ); 01201 } 01202 $method = $params['op'] . 'Internal'; // e.g. "storeInternal" 01203 $subStatus = $this->$method( array( 'async' => $async ) + $params ); 01204 if ( $subStatus->value instanceof FileBackendStoreOpHandle ) { // async 01205 if ( count( $curFileOpHandles ) >= $maxConcurrency ) { 01206 $fileOpHandles[] = $curFileOpHandles; // push this batch 01207 $curFileOpHandles = array(); 01208 } 01209 $curFileOpHandles[$index] = $subStatus->value; // keep index 01210 } else { // error or completed 01211 $statuses[$index] = $subStatus; // keep index 01212 } 01213 } 01214 if ( count( $curFileOpHandles ) ) { 01215 $fileOpHandles[] = $curFileOpHandles; // last batch 01216 } 01217 // Do all the async ops that can be done concurrently... 01218 foreach ( $fileOpHandles as $fileHandleBatch ) { 01219 $statuses = $statuses + $this->executeOpHandlesInternal( $fileHandleBatch ); 01220 } 01221 // Marshall and merge all the responses... 01222 foreach ( $statuses as $index => $subStatus ) { 01223 $status->merge( $subStatus ); 01224 if ( $subStatus->isOK() ) { 01225 $status->success[$index] = true; 01226 ++$status->successCount; 01227 } else { 01228 $status->success[$index] = false; 01229 ++$status->failCount; 01230 } 01231 } 01232 01233 wfProfileOut( __METHOD__ . '-' . $this->name ); 01234 wfProfileOut( __METHOD__ ); 01235 return $status; 01236 } 01237 01247 final public function executeOpHandlesInternal( array $fileOpHandles ) { 01248 wfProfileIn( __METHOD__ ); 01249 wfProfileIn( __METHOD__ . '-' . $this->name ); 01250 foreach ( $fileOpHandles as $fileOpHandle ) { 01251 if ( !( $fileOpHandle instanceof FileBackendStoreOpHandle ) ) { 01252 throw new MWException( "Given a non-FileBackendStoreOpHandle object." ); 01253 } elseif ( $fileOpHandle->backend->getName() !== $this->getName() ) { 01254 throw new MWException( "Given a FileBackendStoreOpHandle for the wrong backend." ); 01255 } 01256 } 01257 $res = $this->doExecuteOpHandlesInternal( $fileOpHandles ); 01258 foreach ( $fileOpHandles as $fileOpHandle ) { 01259 $fileOpHandle->closeResources(); 01260 } 01261 wfProfileOut( __METHOD__ . '-' . $this->name ); 01262 wfProfileOut( __METHOD__ ); 01263 return $res; 01264 } 01265 01272 protected function doExecuteOpHandlesInternal( array $fileOpHandles ) { 01273 foreach ( $fileOpHandles as $fileOpHandle ) { // OK if empty 01274 throw new MWException( "This backend supports no asynchronous operations." ); 01275 } 01276 return array(); 01277 } 01278 01285 protected function stripInvalidHeadersFromOp( array $op ) { 01286 if ( isset( $op['headers'] ) ) { 01287 foreach ( $op['headers'] as $name => $value ) { 01288 if ( strlen( $name ) > 255 || strlen( $value ) > 255 ) { 01289 trigger_error( "Header '$name: $value' is too long." ); 01290 unset( $op['headers'][$name] ); 01291 } elseif ( !strlen( $value ) ) { 01292 $op['headers'][$name] = ''; // null/false => "" 01293 } 01294 } 01295 } 01296 return $op; 01297 } 01298 01302 final public function preloadCache( array $paths ) { 01303 $fullConts = array(); // full container names 01304 foreach ( $paths as $path ) { 01305 list( $fullCont, , ) = $this->resolveStoragePath( $path ); 01306 $fullConts[] = $fullCont; 01307 } 01308 // Load from the persistent file and container caches 01309 $this->primeContainerCache( $fullConts ); 01310 $this->primeFileCache( $paths ); 01311 } 01312 01316 final public function clearCache( array $paths = null ) { 01317 if ( is_array( $paths ) ) { 01318 $paths = array_map( 'FileBackend::normalizeStoragePath', $paths ); 01319 $paths = array_filter( $paths, 'strlen' ); // remove nulls 01320 } 01321 if ( $paths === null ) { 01322 $this->cheapCache->clear(); 01323 $this->expensiveCache->clear(); 01324 } else { 01325 foreach ( $paths as $path ) { 01326 $this->cheapCache->clear( $path ); 01327 $this->expensiveCache->clear( $path ); 01328 } 01329 } 01330 $this->doClearCache( $paths ); 01331 } 01332 01341 protected function doClearCache( array $paths = null ) {} 01342 01350 abstract protected function directoriesAreVirtual(); 01351 01359 final protected static function isValidContainerName( $container ) { 01360 // This accounts for Swift and S3 restrictions while leaving room 01361 // for things like '.xxx' (hex shard chars) or '.seg' (segments). 01362 // This disallows directory separators or traversal characters. 01363 // Note that matching strings URL encode to the same string; 01364 // in Swift, the length restriction is *after* URL encoding. 01365 return preg_match( '/^[a-z0-9][a-z0-9-_]{0,199}$/i', $container ); 01366 } 01367 01381 final protected function resolveStoragePath( $storagePath ) { 01382 list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath ); 01383 if ( $backend === $this->name ) { // must be for this backend 01384 $relPath = self::normalizeContainerPath( $relPath ); 01385 if ( $relPath !== null ) { 01386 // Get shard for the normalized path if this container is sharded 01387 $cShard = $this->getContainerShard( $container, $relPath ); 01388 // Validate and sanitize the relative path (backend-specific) 01389 $relPath = $this->resolveContainerPath( $container, $relPath ); 01390 if ( $relPath !== null ) { 01391 // Prepend any wiki ID prefix to the container name 01392 $container = $this->fullContainerName( $container ); 01393 if ( self::isValidContainerName( $container ) ) { 01394 // Validate and sanitize the container name (backend-specific) 01395 $container = $this->resolveContainerName( "{$container}{$cShard}" ); 01396 if ( $container !== null ) { 01397 return array( $container, $relPath, $cShard ); 01398 } 01399 } 01400 } 01401 } 01402 } 01403 return array( null, null, null ); 01404 } 01405 01415 final protected function resolveStoragePathReal( $storagePath ) { 01416 list( $container, $relPath, $cShard ) = $this->resolveStoragePath( $storagePath ); 01417 if ( $cShard !== null ) { 01418 return array( $container, $relPath ); 01419 } 01420 return array( null, null ); 01421 } 01422 01431 final protected function getContainerShard( $container, $relPath ) { 01432 list( $levels, $base, $repeat ) = $this->getContainerHashLevels( $container ); 01433 if ( $levels == 1 || $levels == 2 ) { 01434 // Hash characters are either base 16 or 36 01435 $char = ( $base == 36 ) ? '[0-9a-z]' : '[0-9a-f]'; 01436 // Get a regex that represents the shard portion of paths. 01437 // The concatenation of the captures gives us the shard. 01438 if ( $levels === 1 ) { // 16 or 36 shards per container 01439 $hashDirRegex = '(' . $char . ')'; 01440 } else { // 256 or 1296 shards per container 01441 if ( $repeat ) { // verbose hash dir format (e.g. "a/ab/abc") 01442 $hashDirRegex = $char . '/(' . $char . '{2})'; 01443 } else { // short hash dir format (e.g. "a/b/c") 01444 $hashDirRegex = '(' . $char . ')/(' . $char . ')'; 01445 } 01446 } 01447 // Allow certain directories to be above the hash dirs so as 01448 // to work with FileRepo (e.g. "archive/a/ab" or "temp/a/ab"). 01449 // They must be 2+ chars to avoid any hash directory ambiguity. 01450 $m = array(); 01451 if ( preg_match( "!^(?:[^/]{2,}/)*$hashDirRegex(?:/|$)!", $relPath, $m ) ) { 01452 return '.' . implode( '', array_slice( $m, 1 ) ); 01453 } 01454 return null; // failed to match 01455 } 01456 return ''; // no sharding 01457 } 01458 01467 final public function isSingleShardPathInternal( $storagePath ) { 01468 list( , , $shard ) = $this->resolveStoragePath( $storagePath ); 01469 return ( $shard !== null ); 01470 } 01471 01480 final protected function getContainerHashLevels( $container ) { 01481 if ( isset( $this->shardViaHashLevels[$container] ) ) { 01482 $config = $this->shardViaHashLevels[$container]; 01483 $hashLevels = (int)$config['levels']; 01484 if ( $hashLevels == 1 || $hashLevels == 2 ) { 01485 $hashBase = (int)$config['base']; 01486 if ( $hashBase == 16 || $hashBase == 36 ) { 01487 return array( $hashLevels, $hashBase, $config['repeat'] ); 01488 } 01489 } 01490 } 01491 return array( 0, 0, false ); // no sharding 01492 } 01493 01500 final protected function getContainerSuffixes( $container ) { 01501 $shards = array(); 01502 list( $digits, $base ) = $this->getContainerHashLevels( $container ); 01503 if ( $digits > 0 ) { 01504 $numShards = pow( $base, $digits ); 01505 for ( $index = 0; $index < $numShards; $index++ ) { 01506 $shards[] = '.' . wfBaseConvert( $index, 10, $base, $digits ); 01507 } 01508 } 01509 return $shards; 01510 } 01511 01518 final protected function fullContainerName( $container ) { 01519 if ( $this->wikiId != '' ) { 01520 return "{$this->wikiId}-$container"; 01521 } else { 01522 return $container; 01523 } 01524 } 01525 01534 protected function resolveContainerName( $container ) { 01535 return $container; 01536 } 01537 01548 protected function resolveContainerPath( $container, $relStoragePath ) { 01549 return $relStoragePath; 01550 } 01551 01558 private function containerCacheKey( $container ) { 01559 return wfMemcKey( 'backend', $this->getName(), 'container', $container ); 01560 } 01561 01568 final protected function setContainerCache( $container, $val ) { 01569 $this->memCache->add( $this->containerCacheKey( $container ), $val, 14*86400 ); 01570 } 01571 01578 final protected function deleteContainerCache( $container ) { 01579 if ( !$this->memCache->set( $this->containerCacheKey( $container ), 'PURGED', 300 ) ) { 01580 trigger_error( "Unable to delete stat cache for container $container." ); 01581 } 01582 } 01583 01592 final protected function primeContainerCache( array $items ) { 01593 wfProfileIn( __METHOD__ ); 01594 wfProfileIn( __METHOD__ . '-' . $this->name ); 01595 01596 $paths = array(); // list of storage paths 01597 $contNames = array(); // (cache key => resolved container name) 01598 // Get all the paths/containers from the items... 01599 foreach ( $items as $item ) { 01600 if ( $item instanceof FileOp ) { 01601 $paths = array_merge( $paths, $item->storagePathsRead() ); 01602 $paths = array_merge( $paths, $item->storagePathsChanged() ); 01603 } elseif ( self::isStoragePath( $item ) ) { 01604 $paths[] = $item; 01605 } elseif ( is_string( $item ) ) { // full container name 01606 $contNames[$this->containerCacheKey( $item )] = $item; 01607 } 01608 } 01609 // Get all the corresponding cache keys for paths... 01610 foreach ( $paths as $path ) { 01611 list( $fullCont, , ) = $this->resolveStoragePath( $path ); 01612 if ( $fullCont !== null ) { // valid path for this backend 01613 $contNames[$this->containerCacheKey( $fullCont )] = $fullCont; 01614 } 01615 } 01616 01617 $contInfo = array(); // (resolved container name => cache value) 01618 // Get all cache entries for these container cache keys... 01619 $values = $this->memCache->getMulti( array_keys( $contNames ) ); 01620 foreach ( $values as $cacheKey => $val ) { 01621 $contInfo[$contNames[$cacheKey]] = $val; 01622 } 01623 01624 // Populate the container process cache for the backend... 01625 $this->doPrimeContainerCache( array_filter( $contInfo, 'is_array' ) ); 01626 01627 wfProfileOut( __METHOD__ . '-' . $this->name ); 01628 wfProfileOut( __METHOD__ ); 01629 } 01630 01639 protected function doPrimeContainerCache( array $containerInfo ) {} 01640 01647 private function fileCacheKey( $path ) { 01648 return wfMemcKey( 'backend', $this->getName(), 'file', sha1( $path ) ); 01649 } 01650 01659 final protected function setFileCache( $path, $val ) { 01660 $path = FileBackend::normalizeStoragePath( $path ); 01661 if ( $path === null ) { 01662 return; // invalid storage path 01663 } 01664 $this->memCache->add( $this->fileCacheKey( $path ), $val, 7*86400 ); 01665 } 01666 01675 final protected function deleteFileCache( $path ) { 01676 $path = FileBackend::normalizeStoragePath( $path ); 01677 if ( $path === null ) { 01678 return; // invalid storage path 01679 } 01680 if ( !$this->memCache->set( $this->fileCacheKey( $path ), 'PURGED', 300 ) ) { 01681 trigger_error( "Unable to delete stat cache for file $path." ); 01682 } 01683 } 01684 01693 final protected function primeFileCache( array $items ) { 01694 wfProfileIn( __METHOD__ ); 01695 wfProfileIn( __METHOD__ . '-' . $this->name ); 01696 01697 $paths = array(); // list of storage paths 01698 $pathNames = array(); // (cache key => storage path) 01699 // Get all the paths/containers from the items... 01700 foreach ( $items as $item ) { 01701 if ( $item instanceof FileOp ) { 01702 $paths = array_merge( $paths, $item->storagePathsRead() ); 01703 $paths = array_merge( $paths, $item->storagePathsChanged() ); 01704 } elseif ( self::isStoragePath( $item ) ) { 01705 $paths[] = FileBackend::normalizeStoragePath( $item ); 01706 } 01707 } 01708 // Get rid of any paths that failed normalization... 01709 $paths = array_filter( $paths, 'strlen' ); // remove nulls 01710 // Get all the corresponding cache keys for paths... 01711 foreach ( $paths as $path ) { 01712 list( , $rel, ) = $this->resolveStoragePath( $path ); 01713 if ( $rel !== null ) { // valid path for this backend 01714 $pathNames[$this->fileCacheKey( $path )] = $path; 01715 } 01716 } 01717 // Get all cache entries for these container cache keys... 01718 $values = $this->memCache->getMulti( array_keys( $pathNames ) ); 01719 foreach ( $values as $cacheKey => $val ) { 01720 if ( is_array( $val ) ) { 01721 $path = $pathNames[$cacheKey]; 01722 $this->cheapCache->set( $path, 'stat', $val ); 01723 if ( isset( $val['sha1'] ) ) { // some backends store SHA-1 as metadata 01724 $this->cheapCache->set( $path, 'sha1', 01725 array( 'hash' => $val['sha1'], 'latest' => $val['latest'] ) ); 01726 } 01727 } 01728 } 01729 01730 wfProfileOut( __METHOD__ . '-' . $this->name ); 01731 wfProfileOut( __METHOD__ ); 01732 } 01733 01740 final protected function setConcurrencyFlags( array $opts ) { 01741 $opts['concurrency'] = 1; // off 01742 if ( $this->parallelize === 'implicit' ) { 01743 if ( !isset( $opts['parallelize'] ) || $opts['parallelize'] ) { 01744 $opts['concurrency'] = $this->concurrency; 01745 } 01746 } elseif ( $this->parallelize === 'explicit' ) { 01747 if ( !empty( $opts['parallelize'] ) ) { 01748 $opts['concurrency'] = $this->concurrency; 01749 } 01750 } 01751 return $opts; 01752 } 01753 } 01754 01763 abstract class FileBackendStoreOpHandle { 01765 public $params = array(); // params to caller functions 01767 public $backend; 01769 public $resourcesToClose = array(); 01770 01771 public $call; // string; name that identifies the function called 01772 01778 public function closeResources() { 01779 array_map( 'fclose', $this->resourcesToClose ); 01780 } 01781 } 01782 01789 abstract class FileBackendStoreShardListIterator implements Iterator { 01791 protected $backend; 01793 protected $params; 01795 protected $shardSuffixes; 01796 protected $container; // string; full container name 01797 protected $directory; // string; resolved relative path 01798 01800 protected $iter; 01801 protected $curShard = 0; // integer 01802 protected $pos = 0; // integer 01803 01805 protected $multiShardPaths = array(); // (rel path => 1) 01806 01814 public function __construct( 01815 FileBackendStore $backend, $container, $dir, array $suffixes, array $params 01816 ) { 01817 $this->backend = $backend; 01818 $this->container = $container; 01819 $this->directory = $dir; 01820 $this->shardSuffixes = $suffixes; 01821 $this->params = $params; 01822 } 01823 01828 public function key() { 01829 return $this->pos; 01830 } 01831 01836 public function valid() { 01837 if ( $this->iter instanceof Iterator ) { 01838 return $this->iter->valid(); 01839 } elseif ( is_array( $this->iter ) ) { 01840 return ( current( $this->iter ) !== false ); // no paths can have this value 01841 } 01842 return false; // some failure? 01843 } 01844 01849 public function current() { 01850 return ( $this->iter instanceof Iterator ) 01851 ? $this->iter->current() 01852 : current( $this->iter ); 01853 } 01854 01859 public function next() { 01860 ++$this->pos; 01861 ( $this->iter instanceof Iterator ) ? $this->iter->next() : next( $this->iter ); 01862 do { 01863 $continue = false; // keep scanning shards? 01864 $this->filterViaNext(); // filter out duplicates 01865 // Find the next non-empty shard if no elements are left 01866 if ( !$this->valid() ) { 01867 $this->nextShardIteratorIfNotValid(); 01868 $continue = $this->valid(); // re-filter unless we ran out of shards 01869 } 01870 } while ( $continue ); 01871 } 01872 01877 public function rewind() { 01878 $this->pos = 0; 01879 $this->curShard = 0; 01880 $this->setIteratorFromCurrentShard(); 01881 do { 01882 $continue = false; // keep scanning shards? 01883 $this->filterViaNext(); // filter out duplicates 01884 // Find the next non-empty shard if no elements are left 01885 if ( !$this->valid() ) { 01886 $this->nextShardIteratorIfNotValid(); 01887 $continue = $this->valid(); // re-filter unless we ran out of shards 01888 } 01889 } while ( $continue ); 01890 } 01891 01895 protected function filterViaNext() { 01896 while ( $this->valid() ) { 01897 $rel = $this->iter->current(); // path relative to given directory 01898 $path = $this->params['dir'] . "/{$rel}"; // full storage path 01899 if ( $this->backend->isSingleShardPathInternal( $path ) ) { 01900 break; // path is only on one shard; no issue with duplicates 01901 } elseif ( isset( $this->multiShardPaths[$rel] ) ) { 01902 // Don't keep listing paths that are on multiple shards 01903 ( $this->iter instanceof Iterator ) ? $this->iter->next() : next( $this->iter ); 01904 } else { 01905 $this->multiShardPaths[$rel] = 1; 01906 break; 01907 } 01908 } 01909 } 01910 01916 protected function nextShardIteratorIfNotValid() { 01917 while ( !$this->valid() && ++$this->curShard < count( $this->shardSuffixes ) ) { 01918 $this->setIteratorFromCurrentShard(); 01919 } 01920 } 01921 01925 protected function setIteratorFromCurrentShard() { 01926 $this->iter = $this->listFromShard( 01927 $this->container . $this->shardSuffixes[$this->curShard], 01928 $this->directory, $this->params ); 01929 // Start loading results so that current() works 01930 if ( $this->iter ) { 01931 ( $this->iter instanceof Iterator ) ? $this->iter->rewind() : reset( $this->iter ); 01932 } 01933 } 01934 01943 abstract protected function listFromShard( $container, $dir, array $params ); 01944 } 01945 01949 class FileBackendStoreShardDirIterator extends FileBackendStoreShardListIterator { 01954 protected function listFromShard( $container, $dir, array $params ) { 01955 return $this->backend->getDirectoryListInternal( $container, $dir, $params ); 01956 } 01957 } 01958 01962 class FileBackendStoreShardFileIterator extends FileBackendStoreShardListIterator { 01967 protected function listFromShard( $container, $dir, array $params ) { 01968 return $this->backend->getFileListInternal( $container, $dir, $params ); 01969 } 01970 }