MediaWiki  REL1_20
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 
00049         protected $hadWarningErrors = array();
00050 
00059         public function __construct( array $config ) {
00060                 parent::__construct( $config );
00061 
00062                 // Remove any possible trailing slash from directories
00063                 if ( isset( $config['basePath'] ) ) {
00064                         $this->basePath = rtrim( $config['basePath'], '/' ); // remove trailing slash
00065                 } else {
00066                         $this->basePath = null; // none; containers must have explicit paths
00067                 }
00068 
00069                 if ( isset( $config['containerPaths'] ) ) {
00070                         $this->containerPaths = (array)$config['containerPaths'];
00071                         foreach ( $this->containerPaths as &$path ) {
00072                                 $path = rtrim( $path, '/' );  // remove trailing slash
00073                         }
00074                 }
00075 
00076                 $this->fileMode = isset( $config['fileMode'] ) ? $config['fileMode'] : 0644;
00077                 if ( isset( $config['fileOwner'] ) && function_exists( 'posix_getuid' ) ) {
00078                         $this->fileOwner = $config['fileOwner'];
00079                         $info = posix_getpwuid( posix_getuid() );
00080                         $this->currentUser = $info['name']; // cache this, assuming it doesn't change
00081                 }
00082         }
00083 
00090         protected function resolveContainerPath( $container, $relStoragePath ) {
00091                 // Check that container has a root directory
00092                 if ( isset( $this->containerPaths[$container] ) || isset( $this->basePath ) ) {
00093                         // Check for sane relative paths (assume the base paths are OK)
00094                         if ( $this->isLegalRelPath( $relStoragePath ) ) {
00095                                 return $relStoragePath;
00096                         }
00097                 }
00098                 return null;
00099         }
00100 
00107         protected function isLegalRelPath( $path ) {
00108                 // Check for file names longer than 255 chars
00109                 if ( preg_match( '![^/]{256}!', $path ) ) { // ext3/NTFS
00110                         return false;
00111                 }
00112                 if ( wfIsWindows() ) { // NTFS
00113                         return !preg_match( '![:*?"<>|]!', $path );
00114                 } else {
00115                         return true;
00116                 }
00117         }
00118 
00127         protected function containerFSRoot( $shortCont, $fullCont ) {
00128                 if ( isset( $this->containerPaths[$shortCont] ) ) {
00129                         return $this->containerPaths[$shortCont];
00130                 } elseif ( isset( $this->basePath ) ) {
00131                         return "{$this->basePath}/{$fullCont}";
00132                 }
00133                 return null; // no container base path defined
00134         }
00135 
00142         protected function resolveToFSPath( $storagePath ) {
00143                 list( $fullCont, $relPath ) = $this->resolveStoragePathReal( $storagePath );
00144                 if ( $relPath === null ) {
00145                         return null; // invalid
00146                 }
00147                 list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $storagePath );
00148                 $fsPath = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
00149                 if ( $relPath != '' ) {
00150                         $fsPath .= "/{$relPath}";
00151                 }
00152                 return $fsPath;
00153         }
00154 
00159         public function isPathUsableInternal( $storagePath ) {
00160                 $fsPath = $this->resolveToFSPath( $storagePath );
00161                 if ( $fsPath === null ) {
00162                         return false; // invalid
00163                 }
00164                 $parentDir = dirname( $fsPath );
00165 
00166                 if ( file_exists( $fsPath ) ) {
00167                         $ok = is_file( $fsPath ) && is_writable( $fsPath );
00168                 } else {
00169                         $ok = is_dir( $parentDir ) && is_writable( $parentDir );
00170                 }
00171 
00172                 if ( $this->fileOwner !== null && $this->currentUser !== $this->fileOwner ) {
00173                         $ok = false;
00174                         trigger_error( __METHOD__ . ": PHP process owner is not '{$this->fileOwner}'." );
00175                 }
00176 
00177                 return $ok;
00178         }
00179 
00184         protected function doStoreInternal( array $params ) {
00185                 $status = Status::newGood();
00186 
00187                 $dest = $this->resolveToFSPath( $params['dst'] );
00188                 if ( $dest === null ) {
00189                         $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
00190                         return $status;
00191                 }
00192 
00193                 if ( file_exists( $dest ) ) {
00194                         if ( !empty( $params['overwrite'] ) ) {
00195                                 $ok = unlink( $dest );
00196                                 if ( !$ok ) {
00197                                         $status->fatal( 'backend-fail-delete', $params['dst'] );
00198                                         return $status;
00199                                 }
00200                         } else {
00201                                 $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
00202                                 return $status;
00203                         }
00204                 }
00205 
00206                 if ( !empty( $params['async'] ) ) { // deferred
00207                         $cmd = implode( ' ', array( wfIsWindows() ? 'COPY' : 'cp',
00208                                 wfEscapeShellArg( $this->cleanPathSlashes( $params['src'] ) ),
00209                                 wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
00210                         ) );
00211                         $status->value = new FSFileOpHandle( $this, $params, 'Store', $cmd, $dest );
00212                 } else { // immediate write
00213                         $ok = copy( $params['src'], $dest );
00214                         // In some cases (at least over NFS), copy() returns true when it fails
00215                         if ( !$ok || ( filesize( $params['src'] ) !== filesize( $dest ) ) ) {
00216                                 if ( $ok ) { // PHP bug
00217                                         unlink( $dest ); // remove broken file
00218                                         trigger_error( __METHOD__ . ": copy() failed but returned true." );
00219                                 }
00220                                 $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
00221                                 return $status;
00222                         }
00223                         $this->chmod( $dest );
00224                 }
00225 
00226                 return $status;
00227         }
00228 
00232         protected function _getResponseStore( $errors, Status $status, array $params, $cmd ) {
00233                 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
00234                         $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
00235                         trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
00236                 }
00237         }
00238 
00243         protected function doCopyInternal( array $params ) {
00244                 $status = Status::newGood();
00245 
00246                 $source = $this->resolveToFSPath( $params['src'] );
00247                 if ( $source === null ) {
00248                         $status->fatal( 'backend-fail-invalidpath', $params['src'] );
00249                         return $status;
00250                 }
00251 
00252                 $dest = $this->resolveToFSPath( $params['dst'] );
00253                 if ( $dest === null ) {
00254                         $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
00255                         return $status;
00256                 }
00257 
00258                 if ( file_exists( $dest ) ) {
00259                         if ( !empty( $params['overwrite'] ) ) {
00260                                 $ok = unlink( $dest );
00261                                 if ( !$ok ) {
00262                                         $status->fatal( 'backend-fail-delete', $params['dst'] );
00263                                         return $status;
00264                                 }
00265                         } else {
00266                                 $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
00267                                 return $status;
00268                         }
00269                 }
00270 
00271                 if ( !empty( $params['async'] ) ) { // deferred
00272                         $cmd = implode( ' ', array( wfIsWindows() ? 'COPY' : 'cp',
00273                                 wfEscapeShellArg( $this->cleanPathSlashes( $source ) ),
00274                                 wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
00275                         ) );
00276                         $status->value = new FSFileOpHandle( $this, $params, 'Copy', $cmd, $dest );
00277                 } else { // immediate write
00278                         $ok = copy( $source, $dest );
00279                         // In some cases (at least over NFS), copy() returns true when it fails
00280                         if ( !$ok || ( filesize( $source ) !== filesize( $dest ) ) ) {
00281                                 if ( $ok ) { // PHP bug
00282                                         unlink( $dest ); // remove broken file
00283                                         trigger_error( __METHOD__ . ": copy() failed but returned true." );
00284                                 }
00285                                 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
00286                                 return $status;
00287                         }
00288                         $this->chmod( $dest );
00289                 }
00290 
00291                 return $status;
00292         }
00293 
00297         protected function _getResponseCopy( $errors, Status $status, array $params, $cmd ) {
00298                 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
00299                         $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
00300                         trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
00301                 }
00302         }
00303 
00308         protected function doMoveInternal( array $params ) {
00309                 $status = Status::newGood();
00310 
00311                 $source = $this->resolveToFSPath( $params['src'] );
00312                 if ( $source === null ) {
00313                         $status->fatal( 'backend-fail-invalidpath', $params['src'] );
00314                         return $status;
00315                 }
00316 
00317                 $dest = $this->resolveToFSPath( $params['dst'] );
00318                 if ( $dest === null ) {
00319                         $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
00320                         return $status;
00321                 }
00322 
00323                 if ( file_exists( $dest ) ) {
00324                         if ( !empty( $params['overwrite'] ) ) {
00325                                 // Windows does not support moving over existing files
00326                                 if ( wfIsWindows() ) {
00327                                         $ok = unlink( $dest );
00328                                         if ( !$ok ) {
00329                                                 $status->fatal( 'backend-fail-delete', $params['dst'] );
00330                                                 return $status;
00331                                         }
00332                                 }
00333                         } else {
00334                                 $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
00335                                 return $status;
00336                         }
00337                 }
00338 
00339                 if ( !empty( $params['async'] ) ) { // deferred
00340                         $cmd = implode( ' ', array( wfIsWindows() ? 'MOVE' : 'mv',
00341                                 wfEscapeShellArg( $this->cleanPathSlashes( $source ) ),
00342                                 wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
00343                         ) );
00344                         $status->value = new FSFileOpHandle( $this, $params, 'Move', $cmd );
00345                 } else { // immediate write
00346                         $ok = rename( $source, $dest );
00347                         clearstatcache(); // file no longer at source
00348                         if ( !$ok ) {
00349                                 $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
00350                                 return $status;
00351                         }
00352                 }
00353 
00354                 return $status;
00355         }
00356 
00360         protected function _getResponseMove( $errors, Status $status, array $params, $cmd ) {
00361                 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
00362                         $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
00363                         trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
00364                 }
00365         }
00366 
00371         protected function doDeleteInternal( array $params ) {
00372                 $status = Status::newGood();
00373 
00374                 $source = $this->resolveToFSPath( $params['src'] );
00375                 if ( $source === null ) {
00376                         $status->fatal( 'backend-fail-invalidpath', $params['src'] );
00377                         return $status;
00378                 }
00379 
00380                 if ( !is_file( $source ) ) {
00381                         if ( empty( $params['ignoreMissingSource'] ) ) {
00382                                 $status->fatal( 'backend-fail-delete', $params['src'] );
00383                         }
00384                         return $status; // do nothing; either OK or bad status
00385                 }
00386 
00387                 if ( !empty( $params['async'] ) ) { // deferred
00388                         $cmd = implode( ' ', array( wfIsWindows() ? 'DEL' : 'unlink',
00389                                 wfEscapeShellArg( $this->cleanPathSlashes( $source ) )
00390                         ) );
00391                         $status->value = new FSFileOpHandle( $this, $params, 'Copy', $cmd );
00392                 } else { // immediate write
00393                         $ok = unlink( $source );
00394                         if ( !$ok ) {
00395                                 $status->fatal( 'backend-fail-delete', $params['src'] );
00396                                 return $status;
00397                         }
00398                 }
00399 
00400                 return $status;
00401         }
00402 
00406         protected function _getResponseDelete( $errors, Status $status, array $params, $cmd ) {
00407                 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
00408                         $status->fatal( 'backend-fail-delete', $params['src'] );
00409                         trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
00410                 }
00411         }
00412 
00417         protected function doCreateInternal( array $params ) {
00418                 $status = Status::newGood();
00419 
00420                 $dest = $this->resolveToFSPath( $params['dst'] );
00421                 if ( $dest === null ) {
00422                         $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
00423                         return $status;
00424                 }
00425 
00426                 if ( file_exists( $dest ) ) {
00427                         if ( !empty( $params['overwrite'] ) ) {
00428                                 $ok = unlink( $dest );
00429                                 if ( !$ok ) {
00430                                         $status->fatal( 'backend-fail-delete', $params['dst'] );
00431                                         return $status;
00432                                 }
00433                         } else {
00434                                 $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
00435                                 return $status;
00436                         }
00437                 }
00438 
00439                 if ( !empty( $params['async'] ) ) { // deferred
00440                         $tempFile = TempFSFile::factory( 'create_', 'tmp' );
00441                         if ( !$tempFile ) {
00442                                 $status->fatal( 'backend-fail-create', $params['dst'] );
00443                                 return $status;
00444                         }
00445                         $bytes = file_put_contents( $tempFile->getPath(), $params['content'] );
00446                         if ( $bytes === false ) {
00447                                 $status->fatal( 'backend-fail-create', $params['dst'] );
00448                                 return $status;
00449                         }
00450                         $cmd = implode( ' ', array( wfIsWindows() ? 'COPY' : 'cp',
00451                                 wfEscapeShellArg( $this->cleanPathSlashes( $tempFile->getPath() ) ),
00452                                 wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
00453                         ) );
00454                         $status->value = new FSFileOpHandle( $this, $params, 'Create', $cmd, $dest );
00455                         $tempFile->bind( $status->value );
00456                 } else { // immediate write
00457                         $bytes = file_put_contents( $dest, $params['content'] );
00458                         if ( $bytes === false ) {
00459                                 $status->fatal( 'backend-fail-create', $params['dst'] );
00460                                 return $status;
00461                         }
00462                         $this->chmod( $dest );
00463                 }
00464 
00465                 return $status;
00466         }
00467 
00471         protected function _getResponseCreate( $errors, Status $status, array $params, $cmd ) {
00472                 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
00473                         $status->fatal( 'backend-fail-create', $params['dst'] );
00474                         trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
00475                 }
00476         }
00477 
00482         protected function doPrepareInternal( $fullCont, $dirRel, array $params ) {
00483                 $status = Status::newGood();
00484                 list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
00485                 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
00486                 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
00487                 $existed = is_dir( $dir ); // already there?
00488                 if ( !wfMkdirParents( $dir ) ) { // make directory and its parents
00489                         $status->fatal( 'directorycreateerror', $params['dir'] ); // fails on races
00490                 } elseif ( !is_writable( $dir ) ) {
00491                         $status->fatal( 'directoryreadonlyerror', $params['dir'] );
00492                 } elseif ( !is_readable( $dir ) ) {
00493                         $status->fatal( 'directorynotreadableerror', $params['dir'] );
00494                 }
00495                 if ( is_dir( $dir ) && !$existed ) {
00496                         // Respect any 'noAccess' or 'noListing' flags...
00497                         $status->merge( $this->doSecureInternal( $fullCont, $dirRel, $params ) );
00498                 }
00499                 return $status;
00500         }
00501 
00506         protected function doSecureInternal( $fullCont, $dirRel, array $params ) {
00507                 $status = Status::newGood();
00508                 list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
00509                 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
00510                 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
00511                 // Seed new directories with a blank index.html, to prevent crawling...
00512                 if ( !empty( $params['noListing'] ) && !file_exists( "{$dir}/index.html" ) ) {
00513                         $bytes = file_put_contents( "{$dir}/index.html", $this->indexHtmlPrivate() );
00514                         if ( $bytes === false ) {
00515                                 $status->fatal( 'backend-fail-create', $params['dir'] . '/index.html' );
00516                                 return $status;
00517                         }
00518                 }
00519                 // Add a .htaccess file to the root of the container...
00520                 if ( !empty( $params['noAccess'] ) && !file_exists( "{$contRoot}/.htaccess" ) ) {
00521                         $bytes = file_put_contents( "{$contRoot}/.htaccess", $this->htaccessPrivate() );
00522                         if ( $bytes === false ) {
00523                                 $storeDir = "mwstore://{$this->name}/{$shortCont}";
00524                                 $status->fatal( 'backend-fail-create', "{$storeDir}/.htaccess" );
00525                                 return $status;
00526                         }
00527                 }
00528                 return $status;
00529         }
00530 
00535         protected function doPublishInternal( $fullCont, $dirRel, array $params ) {
00536                 $status = Status::newGood();
00537                 list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
00538                 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
00539                 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
00540                 // Unseed new directories with a blank index.html, to allow crawling...
00541                 if ( !empty( $params['listing'] ) && is_file( "{$dir}/index.html" ) ) {
00542                         $exists = ( file_get_contents( "{$dir}/index.html" ) === $this->indexHtmlPrivate() );
00543                         if ( $exists && !unlink( "{$dir}/index.html" ) ) { // reverse secure()
00544                                 $status->fatal( 'backend-fail-delete', $params['dir'] . '/index.html' );
00545                                 return $status;
00546                         }
00547                 }
00548                 // Remove the .htaccess file from the root of the container...
00549                 if ( !empty( $params['access'] ) && is_file( "{$contRoot}/.htaccess" ) ) {
00550                         $exists = ( file_get_contents( "{$contRoot}/.htaccess" ) === $this->htaccessPrivate() );
00551                         if ( $exists && !unlink( "{$contRoot}/.htaccess" ) ) { // reverse secure()
00552                                 $storeDir = "mwstore://{$this->name}/{$shortCont}";
00553                                 $status->fatal( 'backend-fail-delete', "{$storeDir}/.htaccess" );
00554                                 return $status;
00555                         }
00556                 }
00557                 return $status;
00558         }
00559 
00564         protected function doCleanInternal( $fullCont, $dirRel, array $params ) {
00565                 $status = Status::newGood();
00566                 list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
00567                 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
00568                 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
00569                 wfSuppressWarnings();
00570                 if ( is_dir( $dir ) ) {
00571                         rmdir( $dir ); // remove directory if empty
00572                 }
00573                 wfRestoreWarnings();
00574                 return $status;
00575         }
00576 
00581         protected function doGetFileStat( array $params ) {
00582                 $source = $this->resolveToFSPath( $params['src'] );
00583                 if ( $source === null ) {
00584                         return false; // invalid storage path
00585                 }
00586 
00587                 $this->trapWarnings(); // don't trust 'false' if there were errors
00588                 $stat = is_file( $source ) ? stat( $source ) : false; // regular files only
00589                 $hadError = $this->untrapWarnings();
00590 
00591                 if ( $stat ) {
00592                         return array(
00593                                 'mtime' => wfTimestamp( TS_MW, $stat['mtime'] ),
00594                                 'size'  => $stat['size']
00595                         );
00596                 } elseif ( !$hadError ) {
00597                         return false; // file does not exist
00598                 } else {
00599                         return null; // failure
00600                 }
00601         }
00602 
00606         protected function doClearCache( array $paths = null ) {
00607                 clearstatcache(); // clear the PHP file stat cache
00608         }
00609 
00614         protected function doDirectoryExists( $fullCont, $dirRel, array $params ) {
00615                 list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
00616                 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
00617                 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
00618 
00619                 $this->trapWarnings(); // don't trust 'false' if there were errors
00620                 $exists = is_dir( $dir );
00621                 $hadError = $this->untrapWarnings();
00622 
00623                 return $hadError ? null : $exists;
00624         }
00625 
00630         public function getDirectoryListInternal( $fullCont, $dirRel, array $params ) {
00631                 list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
00632                 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
00633                 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
00634                 $exists = is_dir( $dir );
00635                 if ( !$exists ) {
00636                         wfDebug( __METHOD__ . "() given directory does not exist: '$dir'\n" );
00637                         return array(); // nothing under this dir
00638                 } elseif ( !is_readable( $dir ) ) {
00639                         wfDebug( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
00640                         return null; // bad permissions?
00641                 }
00642                 return new FSFileBackendDirList( $dir, $params );
00643         }
00644 
00649         public function getFileListInternal( $fullCont, $dirRel, array $params ) {
00650                 list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
00651                 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
00652                 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
00653                 $exists = is_dir( $dir );
00654                 if ( !$exists ) {
00655                         wfDebug( __METHOD__ . "() given directory does not exist: '$dir'\n" );
00656                         return array(); // nothing under this dir
00657                 } elseif ( !is_readable( $dir ) ) {
00658                         wfDebug( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
00659                         return null; // bad permissions?
00660                 }
00661                 return new FSFileBackendFileList( $dir, $params );
00662         }
00663 
00668         public function getLocalReference( array $params ) {
00669                 $source = $this->resolveToFSPath( $params['src'] );
00670                 if ( $source === null ) {
00671                         return null;
00672                 }
00673                 return new FSFile( $source );
00674         }
00675 
00680         public function getLocalCopy( array $params ) {
00681                 $source = $this->resolveToFSPath( $params['src'] );
00682                 if ( $source === null ) {
00683                         return null;
00684                 }
00685 
00686                 // Create a new temporary file with the same extension...
00687                 $ext = FileBackend::extensionFromPath( $params['src'] );
00688                 $tmpFile = TempFSFile::factory( 'localcopy_', $ext );
00689                 if ( !$tmpFile ) {
00690                         return null;
00691                 }
00692                 $tmpPath = $tmpFile->getPath();
00693 
00694                 // Copy the source file over the temp file
00695                 $ok = copy( $source, $tmpPath );
00696                 if ( !$ok ) {
00697                         return null;
00698                 }
00699 
00700                 $this->chmod( $tmpPath );
00701 
00702                 return $tmpFile;
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                 wfSuppressWarnings();
00755                 $ok = chmod( $path, $this->fileMode );
00756                 wfRestoreWarnings();
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                 return false; // invoke normal PHP error handler
00798         }
00799 
00805         protected function untrapWarnings() {
00806                 restore_error_handler(); // restore previous handler
00807                 return array_pop( $this->hadWarningErrors ); // pop from stack
00808         }
00809 
00813         private function handleWarning() {
00814                 $this->hadWarningErrors[count( $this->hadWarningErrors ) - 1] = true;
00815                 return true; // suppress from PHP handler
00816         }
00817 }
00818 
00822 class FSFileOpHandle extends FileBackendStoreOpHandle {
00823         public $cmd; // string; shell command
00824         public $chmodPath; // string; file to chmod
00825 
00833         public function __construct( $backend, array $params, $call, $cmd, $chmodPath = null ) {
00834                 $this->backend = $backend;
00835                 $this->params = $params;
00836                 $this->call = $call;
00837                 $this->cmd = $cmd;
00838                 $this->chmodPath = $chmodPath;
00839         }
00840 }
00841 
00849 abstract class FSFileBackendList implements Iterator {
00851         protected $iter;
00852         protected $suffixStart; // integer
00853         protected $pos = 0; // integer
00855         protected $params = array();
00856 
00861         public function __construct( $dir, array $params ) {
00862                 $dir = realpath( $dir ); // normalize
00863                 $this->suffixStart = strlen( $dir ) + 1; // size of "path/to/dir/"
00864                 $this->params = $params;
00865 
00866                 try {
00867                         $this->iter = $this->initIterator( $dir );
00868                 } catch ( UnexpectedValueException $e ) {
00869                         $this->iter = null; // bad permissions? deleted?
00870                 }
00871         }
00872 
00879         protected function initIterator( $dir ) {
00880                 if ( !empty( $this->params['topOnly'] ) ) { // non-recursive
00881                         # Get an iterator that will get direct sub-nodes
00882                         return new DirectoryIterator( $dir );
00883                 } else { // recursive
00884                         # Get an iterator that will return leaf nodes (non-directories)
00885                         # RecursiveDirectoryIterator extends FilesystemIterator.
00886                         # FilesystemIterator::SKIP_DOTS default is inconsistent in PHP 5.3.x.
00887                         $flags = FilesystemIterator::CURRENT_AS_SELF | FilesystemIterator::SKIP_DOTS;
00888                         return new RecursiveIteratorIterator(
00889                                 new RecursiveDirectoryIterator( $dir, $flags ),
00890                                 RecursiveIteratorIterator::CHILD_FIRST // include dirs
00891                         );
00892                 }
00893         }
00894 
00899         public function key() {
00900                 return $this->pos;
00901         }
00902 
00907         public function current() {
00908                 return $this->getRelPath( $this->iter->current()->getPathname() );
00909         }
00910 
00915         public function next() {
00916                 try {
00917                         $this->iter->next();
00918                         $this->filterViaNext();
00919                 } catch ( UnexpectedValueException $e ) {
00920                         $this->iter = null;
00921                 }
00922                 ++$this->pos;
00923         }
00924 
00929         public function rewind() {
00930                 $this->pos = 0;
00931                 try {
00932                         $this->iter->rewind();
00933                         $this->filterViaNext();
00934                 } catch ( UnexpectedValueException $e ) {
00935                         $this->iter = null;
00936                 }
00937         }
00938 
00943         public function valid() {
00944                 return $this->iter && $this->iter->valid();
00945         }
00946 
00950         protected function filterViaNext() {}
00951 
00959         protected function getRelPath( $path ) {
00960                 return strtr( substr( realpath( $path ), $this->suffixStart ), '\\', '/' );
00961         }
00962 }
00963 
00964 class FSFileBackendDirList extends FSFileBackendList {
00965         protected function filterViaNext() {
00966                 while ( $this->iter->valid() ) {
00967                         if ( $this->iter->current()->isDot() || !$this->iter->current()->isDir() ) {
00968                                 $this->iter->next(); // skip non-directories and dot files
00969                         } else {
00970                                 break;
00971                         }
00972                 }
00973         }
00974 }
00975 
00976 class FSFileBackendFileList extends FSFileBackendList {
00977         protected function filterViaNext() {
00978                 while ( $this->iter->valid() ) {
00979                         if ( !$this->iter->current()->isFile() ) {
00980                                 $this->iter->next(); // skip non-files and dot files
00981                         } else {
00982                                 break;
00983                         }
00984                 }
00985         }
00986 }