MediaWiki  REL1_21
FSFileBackend.php
Go to the documentation of this file.
00001 <?php
00041 class FSFileBackend extends FileBackendStore {
00042         protected $basePath; // string; directory holding the container directories
00044         protected $containerPaths = array(); // for custom container paths
00045         protected $fileMode; // integer; file permission mode
00046         protected $fileOwner; // string; required OS username to own files
00047         protected $currentUser; // string; OS username running this script
00048 
00050         protected $hadWarningErrors = array();
00051 
00060         public function __construct( array $config ) {
00061                 parent::__construct( $config );
00062 
00063                 // Remove any possible trailing slash from directories
00064                 if ( isset( $config['basePath'] ) ) {
00065                         $this->basePath = rtrim( $config['basePath'], '/' ); // remove trailing slash
00066                 } else {
00067                         $this->basePath = null; // none; containers must have explicit paths
00068                 }
00069 
00070                 if ( isset( $config['containerPaths'] ) ) {
00071                         $this->containerPaths = (array)$config['containerPaths'];
00072                         foreach ( $this->containerPaths as &$path ) {
00073                                 $path = rtrim( $path, '/' ); // remove trailing slash
00074                         }
00075                 }
00076 
00077                 $this->fileMode = isset( $config['fileMode'] ) ? $config['fileMode'] : 0644;
00078                 if ( isset( $config['fileOwner'] ) && function_exists( 'posix_getuid' ) ) {
00079                         $this->fileOwner = $config['fileOwner'];
00080                         $info = posix_getpwuid( posix_getuid() );
00081                         $this->currentUser = $info['name']; // cache this, assuming it doesn't change
00082                 }
00083         }
00084 
00091         protected function resolveContainerPath( $container, $relStoragePath ) {
00092                 // Check that container has a root directory
00093                 if ( isset( $this->containerPaths[$container] ) || isset( $this->basePath ) ) {
00094                         // Check for sane relative paths (assume the base paths are OK)
00095                         if ( $this->isLegalRelPath( $relStoragePath ) ) {
00096                                 return $relStoragePath;
00097                         }
00098                 }
00099                 return null;
00100         }
00101 
00108         protected function isLegalRelPath( $path ) {
00109                 // Check for file names longer than 255 chars
00110                 if ( preg_match( '![^/]{256}!', $path ) ) { // ext3/NTFS
00111                         return false;
00112                 }
00113                 if ( wfIsWindows() ) { // NTFS
00114                         return !preg_match( '![:*?"<>|]!', $path );
00115                 } else {
00116                         return true;
00117                 }
00118         }
00119 
00128         protected function containerFSRoot( $shortCont, $fullCont ) {
00129                 if ( isset( $this->containerPaths[$shortCont] ) ) {
00130                         return $this->containerPaths[$shortCont];
00131                 } elseif ( isset( $this->basePath ) ) {
00132                         return "{$this->basePath}/{$fullCont}";
00133                 }
00134                 return null; // no container base path defined
00135         }
00136 
00143         protected function resolveToFSPath( $storagePath ) {
00144                 list( $fullCont, $relPath ) = $this->resolveStoragePathReal( $storagePath );
00145                 if ( $relPath === null ) {
00146                         return null; // invalid
00147                 }
00148                 list( , $shortCont, ) = FileBackend::splitStoragePath( $storagePath );
00149                 $fsPath = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
00150                 if ( $relPath != '' ) {
00151                         $fsPath .= "/{$relPath}";
00152                 }
00153                 return $fsPath;
00154         }
00155 
00160         public function isPathUsableInternal( $storagePath ) {
00161                 $fsPath = $this->resolveToFSPath( $storagePath );
00162                 if ( $fsPath === null ) {
00163                         return false; // invalid
00164                 }
00165                 $parentDir = dirname( $fsPath );
00166 
00167                 if ( file_exists( $fsPath ) ) {
00168                         $ok = is_file( $fsPath ) && is_writable( $fsPath );
00169                 } else {
00170                         $ok = is_dir( $parentDir ) && is_writable( $parentDir );
00171                 }
00172 
00173                 if ( $this->fileOwner !== null && $this->currentUser !== $this->fileOwner ) {
00174                         $ok = false;
00175                         trigger_error( __METHOD__ . ": PHP process owner is not '{$this->fileOwner}'." );
00176                 }
00177 
00178                 return $ok;
00179         }
00180 
00185         protected function doCreateInternal( array $params ) {
00186                 $status = Status::newGood();
00187 
00188                 $dest = $this->resolveToFSPath( $params['dst'] );
00189                 if ( $dest === null ) {
00190                         $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
00191                         return $status;
00192                 }
00193 
00194                 if ( !empty( $params['async'] ) ) { // deferred
00195                         $tempFile = TempFSFile::factory( 'create_', 'tmp' );
00196                         if ( !$tempFile ) {
00197                                 $status->fatal( 'backend-fail-create', $params['dst'] );
00198                                 return $status;
00199                         }
00200                         $this->trapWarnings();
00201                         $bytes = file_put_contents( $tempFile->getPath(), $params['content'] );
00202                         $this->untrapWarnings();
00203                         if ( $bytes === false ) {
00204                                 $status->fatal( 'backend-fail-create', $params['dst'] );
00205                                 return $status;
00206                         }
00207                         $cmd = implode( ' ', array(
00208                                 wfIsWindows() ? 'COPY /B /Y' : 'cp', // (binary, overwrite)
00209                                 wfEscapeShellArg( $this->cleanPathSlashes( $tempFile->getPath() ) ),
00210                                 wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
00211                         ) );
00212                         $status->value = new FSFileOpHandle( $this, $params, 'Create', $cmd, $dest );
00213                         $tempFile->bind( $status->value );
00214                 } else { // immediate write
00215                         $this->trapWarnings();
00216                         $bytes = file_put_contents( $dest, $params['content'] );
00217                         $this->untrapWarnings();
00218                         if ( $bytes === false ) {
00219                                 $status->fatal( 'backend-fail-create', $params['dst'] );
00220                                 return $status;
00221                         }
00222                         $this->chmod( $dest );
00223                 }
00224 
00225                 return $status;
00226         }
00227 
00231         protected function _getResponseCreate( $errors, Status $status, array $params, $cmd ) {
00232                 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
00233                         $status->fatal( 'backend-fail-create', $params['dst'] );
00234                         trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
00235                 }
00236         }
00237 
00242         protected function doStoreInternal( array $params ) {
00243                 $status = Status::newGood();
00244 
00245                 $dest = $this->resolveToFSPath( $params['dst'] );
00246                 if ( $dest === null ) {
00247                         $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
00248                         return $status;
00249                 }
00250 
00251                 if ( !empty( $params['async'] ) ) { // deferred
00252                         $cmd = implode( ' ', array(
00253                                 wfIsWindows() ? 'COPY /B /Y' : 'cp', // (binary, overwrite)
00254                                 wfEscapeShellArg( $this->cleanPathSlashes( $params['src'] ) ),
00255                                 wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
00256                         ) );
00257                         $status->value = new FSFileOpHandle( $this, $params, 'Store', $cmd, $dest );
00258                 } else { // immediate write
00259                         $this->trapWarnings();
00260                         $ok = copy( $params['src'], $dest );
00261                         $this->untrapWarnings();
00262                         // In some cases (at least over NFS), copy() returns true when it fails
00263                         if ( !$ok || ( filesize( $params['src'] ) !== filesize( $dest ) ) ) {
00264                                 if ( $ok ) { // PHP bug
00265                                         unlink( $dest ); // remove broken file
00266                                         trigger_error( __METHOD__ . ": copy() failed but returned true." );
00267                                 }
00268                                 $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
00269                                 return $status;
00270                         }
00271                         $this->chmod( $dest );
00272                 }
00273 
00274                 return $status;
00275         }
00276 
00280         protected function _getResponseStore( $errors, Status $status, array $params, $cmd ) {
00281                 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
00282                         $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
00283                         trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
00284                 }
00285         }
00286 
00291         protected function doCopyInternal( array $params ) {
00292                 $status = Status::newGood();
00293 
00294                 $source = $this->resolveToFSPath( $params['src'] );
00295                 if ( $source === null ) {
00296                         $status->fatal( 'backend-fail-invalidpath', $params['src'] );
00297                         return $status;
00298                 }
00299 
00300                 $dest = $this->resolveToFSPath( $params['dst'] );
00301                 if ( $dest === null ) {
00302                         $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
00303                         return $status;
00304                 }
00305 
00306                 if ( !is_file( $source ) ) {
00307                         if ( empty( $params['ignoreMissingSource'] ) ) {
00308                                 $status->fatal( 'backend-fail-copy', $params['src'] );
00309                         }
00310                         return $status; // do nothing; either OK or bad status
00311                 }
00312 
00313                 if ( !empty( $params['async'] ) ) { // deferred
00314                         $cmd = implode( ' ', array(
00315                                 wfIsWindows() ? 'COPY /B /Y' : 'cp', // (binary, overwrite)
00316                                 wfEscapeShellArg( $this->cleanPathSlashes( $source ) ),
00317                                 wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
00318                         ) );
00319                         $status->value = new FSFileOpHandle( $this, $params, 'Copy', $cmd, $dest );
00320                 } else { // immediate write
00321                         $this->trapWarnings();
00322                         $ok = copy( $source, $dest );
00323                         $this->untrapWarnings();
00324                         // In some cases (at least over NFS), copy() returns true when it fails
00325                         if ( !$ok || ( filesize( $source ) !== filesize( $dest ) ) ) {
00326                                 if ( $ok ) { // PHP bug
00327                                         $this->trapWarnings();
00328                                         unlink( $dest ); // remove broken file
00329                                         $this->untrapWarnings();
00330                                         trigger_error( __METHOD__ . ": copy() failed but returned true." );
00331                                 }
00332                                 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
00333                                 return $status;
00334                         }
00335                         $this->chmod( $dest );
00336                 }
00337 
00338                 return $status;
00339         }
00340 
00344         protected function _getResponseCopy( $errors, Status $status, array $params, $cmd ) {
00345                 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
00346                         $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
00347                         trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
00348                 }
00349         }
00350 
00355         protected function doMoveInternal( array $params ) {
00356                 $status = Status::newGood();
00357 
00358                 $source = $this->resolveToFSPath( $params['src'] );
00359                 if ( $source === null ) {
00360                         $status->fatal( 'backend-fail-invalidpath', $params['src'] );
00361                         return $status;
00362                 }
00363 
00364                 $dest = $this->resolveToFSPath( $params['dst'] );
00365                 if ( $dest === null ) {
00366                         $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
00367                         return $status;
00368                 }
00369 
00370                 if ( !is_file( $source ) ) {
00371                         if ( empty( $params['ignoreMissingSource'] ) ) {
00372                                 $status->fatal( 'backend-fail-move', $params['src'] );
00373                         }
00374                         return $status; // do nothing; either OK or bad status
00375                 }
00376 
00377                 if ( !empty( $params['async'] ) ) { // deferred
00378                         $cmd = implode( ' ', array(
00379                                 wfIsWindows() ? 'MOVE /Y' : 'mv', // (overwrite)
00380                                 wfEscapeShellArg( $this->cleanPathSlashes( $source ) ),
00381                                 wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
00382                         ) );
00383                         $status->value = new FSFileOpHandle( $this, $params, 'Move', $cmd );
00384                 } else { // immediate write
00385                         $this->trapWarnings();
00386                         $ok = rename( $source, $dest );
00387                         $this->untrapWarnings();
00388                         clearstatcache(); // file no longer at source
00389                         if ( !$ok ) {
00390                                 $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
00391                                 return $status;
00392                         }
00393                 }
00394 
00395                 return $status;
00396         }
00397 
00401         protected function _getResponseMove( $errors, Status $status, array $params, $cmd ) {
00402                 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
00403                         $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
00404                         trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
00405                 }
00406         }
00407 
00412         protected function doDeleteInternal( array $params ) {
00413                 $status = Status::newGood();
00414 
00415                 $source = $this->resolveToFSPath( $params['src'] );
00416                 if ( $source === null ) {
00417                         $status->fatal( 'backend-fail-invalidpath', $params['src'] );
00418                         return $status;
00419                 }
00420 
00421                 if ( !is_file( $source ) ) {
00422                         if ( empty( $params['ignoreMissingSource'] ) ) {
00423                                 $status->fatal( 'backend-fail-delete', $params['src'] );
00424                         }
00425                         return $status; // do nothing; either OK or bad status
00426                 }
00427 
00428                 if ( !empty( $params['async'] ) ) { // deferred
00429                         $cmd = implode( ' ', array(
00430                                 wfIsWindows() ? 'DEL' : 'unlink',
00431                                 wfEscapeShellArg( $this->cleanPathSlashes( $source ) )
00432                         ) );
00433                         $status->value = new FSFileOpHandle( $this, $params, 'Copy', $cmd );
00434                 } else { // immediate write
00435                         $this->trapWarnings();
00436                         $ok = unlink( $source );
00437                         $this->untrapWarnings();
00438                         if ( !$ok ) {
00439                                 $status->fatal( 'backend-fail-delete', $params['src'] );
00440                                 return $status;
00441                         }
00442                 }
00443 
00444                 return $status;
00445         }
00446 
00450         protected function _getResponseDelete( $errors, Status $status, array $params, $cmd ) {
00451                 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
00452                         $status->fatal( 'backend-fail-delete', $params['src'] );
00453                         trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
00454                 }
00455         }
00456 
00461         protected function doPrepareInternal( $fullCont, $dirRel, array $params ) {
00462                 $status = Status::newGood();
00463                 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
00464                 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
00465                 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
00466                 $existed = is_dir( $dir ); // already there?
00467                 // Create the directory and its parents as needed...
00468                 $this->trapWarnings();
00469                 if ( !wfMkdirParents( $dir ) ) {
00470                         $status->fatal( 'directorycreateerror', $params['dir'] ); // fails on races
00471                 } elseif ( !is_writable( $dir ) ) {
00472                         $status->fatal( 'directoryreadonlyerror', $params['dir'] );
00473                 } elseif ( !is_readable( $dir ) ) {
00474                         $status->fatal( 'directorynotreadableerror', $params['dir'] );
00475                 }
00476                 $this->untrapWarnings();
00477                 // Respect any 'noAccess' or 'noListing' flags...
00478                 if ( is_dir( $dir ) && !$existed ) {
00479                         $status->merge( $this->doSecureInternal( $fullCont, $dirRel, $params ) );
00480                 }
00481                 return $status;
00482         }
00483 
00488         protected function doSecureInternal( $fullCont, $dirRel, array $params ) {
00489                 $status = Status::newGood();
00490                 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
00491                 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
00492                 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
00493                 // Seed new directories with a blank index.html, to prevent crawling...
00494                 if ( !empty( $params['noListing'] ) && !file_exists( "{$dir}/index.html" ) ) {
00495                         $this->trapWarnings();
00496                         $bytes = file_put_contents( "{$dir}/index.html", $this->indexHtmlPrivate() );
00497                         $this->untrapWarnings();
00498                         if ( $bytes === false ) {
00499                                 $status->fatal( 'backend-fail-create', $params['dir'] . '/index.html' );
00500                         }
00501                 }
00502                 // Add a .htaccess file to the root of the container...
00503                 if ( !empty( $params['noAccess'] ) && !file_exists( "{$contRoot}/.htaccess" ) ) {
00504                         $this->trapWarnings();
00505                         $bytes = file_put_contents( "{$contRoot}/.htaccess", $this->htaccessPrivate() );
00506                         $this->untrapWarnings();
00507                         if ( $bytes === false ) {
00508                                 $storeDir = "mwstore://{$this->name}/{$shortCont}";
00509                                 $status->fatal( 'backend-fail-create', "{$storeDir}/.htaccess" );
00510                         }
00511                 }
00512                 return $status;
00513         }
00514 
00519         protected function doPublishInternal( $fullCont, $dirRel, array $params ) {
00520                 $status = Status::newGood();
00521                 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
00522                 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
00523                 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
00524                 // Unseed new directories with a blank index.html, to allow crawling...
00525                 if ( !empty( $params['listing'] ) && is_file( "{$dir}/index.html" ) ) {
00526                         $exists = ( file_get_contents( "{$dir}/index.html" ) === $this->indexHtmlPrivate() );
00527                         $this->trapWarnings();
00528                         if ( $exists && !unlink( "{$dir}/index.html" ) ) { // reverse secure()
00529                                 $status->fatal( 'backend-fail-delete', $params['dir'] . '/index.html' );
00530                         }
00531                         $this->untrapWarnings();
00532                 }
00533                 // Remove the .htaccess file from the root of the container...
00534                 if ( !empty( $params['access'] ) && is_file( "{$contRoot}/.htaccess" ) ) {
00535                         $exists = ( file_get_contents( "{$contRoot}/.htaccess" ) === $this->htaccessPrivate() );
00536                         $this->trapWarnings();
00537                         if ( $exists && !unlink( "{$contRoot}/.htaccess" ) ) { // reverse secure()
00538                                 $storeDir = "mwstore://{$this->name}/{$shortCont}";
00539                                 $status->fatal( 'backend-fail-delete', "{$storeDir}/.htaccess" );
00540                         }
00541                         $this->untrapWarnings();
00542                 }
00543                 return $status;
00544         }
00545 
00550         protected function doCleanInternal( $fullCont, $dirRel, array $params ) {
00551                 $status = Status::newGood();
00552                 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
00553                 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
00554                 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
00555                 $this->trapWarnings();
00556                 if ( is_dir( $dir ) ) {
00557                         rmdir( $dir ); // remove directory if empty
00558                 }
00559                 $this->untrapWarnings();
00560                 return $status;
00561         }
00562 
00567         protected function doGetFileStat( array $params ) {
00568                 $source = $this->resolveToFSPath( $params['src'] );
00569                 if ( $source === null ) {
00570                         return false; // invalid storage path
00571                 }
00572 
00573                 $this->trapWarnings(); // don't trust 'false' if there were errors
00574                 $stat = is_file( $source ) ? stat( $source ) : false; // regular files only
00575                 $hadError = $this->untrapWarnings();
00576 
00577                 if ( $stat ) {
00578                         return array(
00579                                 'mtime' => wfTimestamp( TS_MW, $stat['mtime'] ),
00580                                 'size'  => $stat['size']
00581                         );
00582                 } elseif ( !$hadError ) {
00583                         return false; // file does not exist
00584                 } else {
00585                         return null; // failure
00586                 }
00587         }
00588 
00592         protected function doClearCache( array $paths = null ) {
00593                 clearstatcache(); // clear the PHP file stat cache
00594         }
00595 
00600         protected function doDirectoryExists( $fullCont, $dirRel, array $params ) {
00601                 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
00602                 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
00603                 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
00604 
00605                 $this->trapWarnings(); // don't trust 'false' if there were errors
00606                 $exists = is_dir( $dir );
00607                 $hadError = $this->untrapWarnings();
00608 
00609                 return $hadError ? null : $exists;
00610         }
00611 
00616         public function getDirectoryListInternal( $fullCont, $dirRel, array $params ) {
00617                 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
00618                 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
00619                 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
00620                 $exists = is_dir( $dir );
00621                 if ( !$exists ) {
00622                         wfDebug( __METHOD__ . "() given directory does not exist: '$dir'\n" );
00623                         return array(); // nothing under this dir
00624                 } elseif ( !is_readable( $dir ) ) {
00625                         wfDebug( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
00626                         return null; // bad permissions?
00627                 }
00628                 return new FSFileBackendDirList( $dir, $params );
00629         }
00630 
00635         public function getFileListInternal( $fullCont, $dirRel, array $params ) {
00636                 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
00637                 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
00638                 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
00639                 $exists = is_dir( $dir );
00640                 if ( !$exists ) {
00641                         wfDebug( __METHOD__ . "() given directory does not exist: '$dir'\n" );
00642                         return array(); // nothing under this dir
00643                 } elseif ( !is_readable( $dir ) ) {
00644                         wfDebug( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
00645                         return null; // bad permissions?
00646                 }
00647                 return new FSFileBackendFileList( $dir, $params );
00648         }
00649 
00654         protected function doGetLocalReferenceMulti( array $params ) {
00655                 $fsFiles = array(); // (path => FSFile)
00656 
00657                 foreach ( $params['srcs'] as $src ) {
00658                         $source = $this->resolveToFSPath( $src );
00659                         if ( $source === null || !is_file( $source ) ) {
00660                                 $fsFiles[$src] = null; // invalid path or file does not exist
00661                         } else {
00662                                 $fsFiles[$src] = new FSFile( $source );
00663                         }
00664                 }
00665 
00666                 return $fsFiles;
00667         }
00668 
00673         protected function doGetLocalCopyMulti( array $params ) {
00674                 $tmpFiles = array(); // (path => TempFSFile)
00675 
00676                 foreach ( $params['srcs'] as $src ) {
00677                         $source = $this->resolveToFSPath( $src );
00678                         if ( $source === null ) {
00679                                 $tmpFiles[$src] = null; // invalid path
00680                         } else {
00681                                 // Create a new temporary file with the same extension...
00682                                 $ext = FileBackend::extensionFromPath( $src );
00683                                 $tmpFile = TempFSFile::factory( 'localcopy_', $ext );
00684                                 if ( !$tmpFile ) {
00685                                         $tmpFiles[$src] = null;
00686                                 } else {
00687                                         $tmpPath = $tmpFile->getPath();
00688                                         // Copy the source file over the temp file
00689                                         $this->trapWarnings();
00690                                         $ok = copy( $source, $tmpPath );
00691                                         $this->untrapWarnings();
00692                                         if ( !$ok ) {
00693                                                 $tmpFiles[$src] = null;
00694                                         } else {
00695                                                 $this->chmod( $tmpPath );
00696                                                 $tmpFiles[$src] = $tmpFile;
00697                                         }
00698                                 }
00699                         }
00700                 }
00701 
00702                 return $tmpFiles;
00703         }
00704 
00709         protected function directoriesAreVirtual() {
00710                 return false;
00711         }
00712 
00717         protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
00718                 $statuses = array();
00719 
00720                 $pipes = array();
00721                 foreach ( $fileOpHandles as $index => $fileOpHandle ) {
00722                         $pipes[$index] = popen( "{$fileOpHandle->cmd} 2>&1", 'r' );
00723                 }
00724 
00725                 $errs = array();
00726                 foreach ( $pipes as $index => $pipe ) {
00727                         // Result will be empty on success in *NIX. On Windows,
00728                         // it may be something like "        1 file(s) [copied|moved].".
00729                         $errs[$index] = stream_get_contents( $pipe );
00730                         fclose( $pipe );
00731                 }
00732 
00733                 foreach ( $fileOpHandles as $index => $fileOpHandle ) {
00734                         $status = Status::newGood();
00735                         $function = '_getResponse' . $fileOpHandle->call;
00736                         $this->$function( $errs[$index], $status, $fileOpHandle->params, $fileOpHandle->cmd );
00737                         $statuses[$index] = $status;
00738                         if ( $status->isOK() && $fileOpHandle->chmodPath ) {
00739                                 $this->chmod( $fileOpHandle->chmodPath );
00740                         }
00741                 }
00742 
00743                 clearstatcache(); // files changed
00744                 return $statuses;
00745         }
00746 
00753         protected function chmod( $path ) {
00754                 $this->trapWarnings();
00755                 $ok = chmod( $path, $this->fileMode );
00756                 $this->untrapWarnings();
00757 
00758                 return $ok;
00759         }
00760 
00766         protected function indexHtmlPrivate() {
00767                 return '';
00768         }
00769 
00775         protected function htaccessPrivate() {
00776                 return "Deny from all\n";
00777         }
00778 
00785         protected function cleanPathSlashes( $path ) {
00786                 return wfIsWindows() ? strtr( $path, '/', '\\' ) : $path;
00787         }
00788 
00794         protected function trapWarnings() {
00795                 $this->hadWarningErrors[] = false; // push to stack
00796                 set_error_handler( array( $this, 'handleWarning' ), E_WARNING );
00797         }
00798 
00804         protected function untrapWarnings() {
00805                 restore_error_handler(); // restore previous handler
00806                 return array_pop( $this->hadWarningErrors ); // pop from stack
00807         }
00808 
00814         private function handleWarning( $errno, $errstr ) {
00815                 wfDebugLog( 'FSFileBackend', $errstr ); // more detailed error logging
00816                 $this->hadWarningErrors[count( $this->hadWarningErrors ) - 1] = true;
00817                 return true; // suppress from PHP handler
00818         }
00819 }
00820 
00824 class FSFileOpHandle extends FileBackendStoreOpHandle {
00825         public $cmd; // string; shell command
00826         public $chmodPath; // string; file to chmod
00827 
00835         public function __construct( $backend, array $params, $call, $cmd, $chmodPath = null ) {
00836                 $this->backend = $backend;
00837                 $this->params = $params;
00838                 $this->call = $call;
00839                 $this->cmd = $cmd;
00840                 $this->chmodPath = $chmodPath;
00841         }
00842 }
00843 
00851 abstract class FSFileBackendList implements Iterator {
00853         protected $iter;
00854         protected $suffixStart; // integer
00855         protected $pos = 0; // integer
00857         protected $params = array();
00858 
00863         public function __construct( $dir, array $params ) {
00864                 $path = realpath( $dir ); // normalize
00865                 if( $path === false ) {
00866                         $path = $dir;
00867                 }
00868                 $this->suffixStart = strlen( $path ) + 1; // size of "path/to/dir/"
00869                 $this->params = $params;
00870 
00871                 try {
00872                         $this->iter = $this->initIterator( $path );
00873                 } catch ( UnexpectedValueException $e ) {
00874                         $this->iter = null; // bad permissions? deleted?
00875                 }
00876         }
00877 
00884         protected function initIterator( $dir ) {
00885                 if ( !empty( $this->params['topOnly'] ) ) { // non-recursive
00886                         # Get an iterator that will get direct sub-nodes
00887                         return new DirectoryIterator( $dir );
00888                 } else { // recursive
00889                         # Get an iterator that will return leaf nodes (non-directories)
00890                         # RecursiveDirectoryIterator extends FilesystemIterator.
00891                         # FilesystemIterator::SKIP_DOTS default is inconsistent in PHP 5.3.x.
00892                         $flags = FilesystemIterator::CURRENT_AS_SELF | FilesystemIterator::SKIP_DOTS;
00893                         return new RecursiveIteratorIterator(
00894                                 new RecursiveDirectoryIterator( $dir, $flags ),
00895                                 RecursiveIteratorIterator::CHILD_FIRST // include dirs
00896                         );
00897                 }
00898         }
00899 
00904         public function key() {
00905                 return $this->pos;
00906         }
00907 
00912         public function current() {
00913                 return $this->getRelPath( $this->iter->current()->getPathname() );
00914         }
00915 
00920         public function next() {
00921                 try {
00922                         $this->iter->next();
00923                         $this->filterViaNext();
00924                 } catch ( UnexpectedValueException $e ) {
00925                         $this->iter = null;
00926                 }
00927                 ++$this->pos;
00928         }
00929 
00934         public function rewind() {
00935                 $this->pos = 0;
00936                 try {
00937                         $this->iter->rewind();
00938                         $this->filterViaNext();
00939                 } catch ( UnexpectedValueException $e ) {
00940                         $this->iter = null;
00941                 }
00942         }
00943 
00948         public function valid() {
00949                 return $this->iter && $this->iter->valid();
00950         }
00951 
00955         protected function filterViaNext() {}
00956 
00964         protected function getRelPath( $dir ) {
00965                 $path = realpath( $dir );
00966                 if( $path === false ) {
00967                         $path = $dir;
00968                 }
00969                 return strtr( substr( $path, $this->suffixStart ), '\\', '/' );
00970         }
00971 }
00972 
00973 class FSFileBackendDirList extends FSFileBackendList {
00974         protected function filterViaNext() {
00975                 while ( $this->iter->valid() ) {
00976                         if ( $this->iter->current()->isDot() || !$this->iter->current()->isDir() ) {
00977                                 $this->iter->next(); // skip non-directories and dot files
00978                         } else {
00979                                 break;
00980                         }
00981                 }
00982         }
00983 }
00984 
00985 class FSFileBackendFileList extends FSFileBackendList {
00986         protected function filterViaNext() {
00987                 while ( $this->iter->valid() ) {
00988                         if ( !$this->iter->current()->isFile() ) {
00989                                 $this->iter->next(); // skip non-files and dot files
00990                         } else {
00991                                 break;
00992                         }
00993                 }
00994         }
00995 }