MediaWiki  master
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 ( !empty( $params['async'] ) ) { // deferred
00194                         $cmd = implode( ' ', array(
00195                                 wfIsWindows() ? 'COPY /B /Y' : 'cp', // (binary, overwrite)
00196                                 wfEscapeShellArg( $this->cleanPathSlashes( $params['src'] ) ),
00197                                 wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
00198                         ) );
00199                         $status->value = new FSFileOpHandle( $this, $params, 'Store', $cmd, $dest );
00200                 } else { // immediate write
00201                         $ok = copy( $params['src'], $dest );
00202                         // In some cases (at least over NFS), copy() returns true when it fails
00203                         if ( !$ok || ( filesize( $params['src'] ) !== filesize( $dest ) ) ) {
00204                                 if ( $ok ) { // PHP bug
00205                                         unlink( $dest ); // remove broken file
00206                                         trigger_error( __METHOD__ . ": copy() failed but returned true." );
00207                                 }
00208                                 $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
00209                                 return $status;
00210                         }
00211                         $this->chmod( $dest );
00212                 }
00213 
00214                 return $status;
00215         }
00216 
00220         protected function _getResponseStore( $errors, Status $status, array $params, $cmd ) {
00221                 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
00222                         $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
00223                         trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
00224                 }
00225         }
00226 
00231         protected function doCopyInternal( array $params ) {
00232                 $status = Status::newGood();
00233 
00234                 $source = $this->resolveToFSPath( $params['src'] );
00235                 if ( $source === null ) {
00236                         $status->fatal( 'backend-fail-invalidpath', $params['src'] );
00237                         return $status;
00238                 }
00239 
00240                 $dest = $this->resolveToFSPath( $params['dst'] );
00241                 if ( $dest === null ) {
00242                         $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
00243                         return $status;
00244                 }
00245 
00246                 if ( !is_file( $source ) ) {
00247                         if ( empty( $params['ignoreMissingSource'] ) ) {
00248                                 $status->fatal( 'backend-fail-copy', $params['src'] );
00249                         }
00250                         return $status; // do nothing; either OK or bad status
00251                 }
00252 
00253                 if ( !empty( $params['async'] ) ) { // deferred
00254                         $cmd = implode( ' ', array(
00255                                 wfIsWindows() ? 'COPY /B /Y' : 'cp', // (binary, overwrite)
00256                                 wfEscapeShellArg( $this->cleanPathSlashes( $source ) ),
00257                                 wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
00258                         ) );
00259                         $status->value = new FSFileOpHandle( $this, $params, 'Copy', $cmd, $dest );
00260                 } else { // immediate write
00261                         $ok = copy( $source, $dest );
00262                         // In some cases (at least over NFS), copy() returns true when it fails
00263                         if ( !$ok || ( filesize( $source ) !== 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-copy', $params['src'], $params['dst'] );
00269                                 return $status;
00270                         }
00271                         $this->chmod( $dest );
00272                 }
00273 
00274                 return $status;
00275         }
00276 
00280         protected function _getResponseCopy( $errors, Status $status, array $params, $cmd ) {
00281                 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
00282                         $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
00283                         trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
00284                 }
00285         }
00286 
00291         protected function doMoveInternal( 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-move', $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() ? 'MOVE /Y' : 'mv', // (overwrite)
00316                                 wfEscapeShellArg( $this->cleanPathSlashes( $source ) ),
00317                                 wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
00318                         ) );
00319                         $status->value = new FSFileOpHandle( $this, $params, 'Move', $cmd );
00320                 } else { // immediate write
00321                         $ok = rename( $source, $dest );
00322                         clearstatcache(); // file no longer at source
00323                         if ( !$ok ) {
00324                                 $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
00325                                 return $status;
00326                         }
00327                 }
00328 
00329                 return $status;
00330         }
00331 
00335         protected function _getResponseMove( $errors, Status $status, array $params, $cmd ) {
00336                 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
00337                         $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
00338                         trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
00339                 }
00340         }
00341 
00346         protected function doDeleteInternal( array $params ) {
00347                 $status = Status::newGood();
00348 
00349                 $source = $this->resolveToFSPath( $params['src'] );
00350                 if ( $source === null ) {
00351                         $status->fatal( 'backend-fail-invalidpath', $params['src'] );
00352                         return $status;
00353                 }
00354 
00355                 if ( !is_file( $source ) ) {
00356                         if ( empty( $params['ignoreMissingSource'] ) ) {
00357                                 $status->fatal( 'backend-fail-delete', $params['src'] );
00358                         }
00359                         return $status; // do nothing; either OK or bad status
00360                 }
00361 
00362                 if ( !empty( $params['async'] ) ) { // deferred
00363                         $cmd = implode( ' ', array(
00364                                 wfIsWindows() ? 'DEL' : 'unlink',
00365                                 wfEscapeShellArg( $this->cleanPathSlashes( $source ) )
00366                         ) );
00367                         $status->value = new FSFileOpHandle( $this, $params, 'Copy', $cmd );
00368                 } else { // immediate write
00369                         $ok = unlink( $source );
00370                         if ( !$ok ) {
00371                                 $status->fatal( 'backend-fail-delete', $params['src'] );
00372                                 return $status;
00373                         }
00374                 }
00375 
00376                 return $status;
00377         }
00378 
00382         protected function _getResponseDelete( $errors, Status $status, array $params, $cmd ) {
00383                 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
00384                         $status->fatal( 'backend-fail-delete', $params['src'] );
00385                         trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
00386                 }
00387         }
00388 
00393         protected function doCreateInternal( array $params ) {
00394                 $status = Status::newGood();
00395 
00396                 $dest = $this->resolveToFSPath( $params['dst'] );
00397                 if ( $dest === null ) {
00398                         $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
00399                         return $status;
00400                 }
00401 
00402                 if ( !empty( $params['async'] ) ) { // deferred
00403                         $tempFile = TempFSFile::factory( 'create_', 'tmp' );
00404                         if ( !$tempFile ) {
00405                                 $status->fatal( 'backend-fail-create', $params['dst'] );
00406                                 return $status;
00407                         }
00408                         $bytes = file_put_contents( $tempFile->getPath(), $params['content'] );
00409                         if ( $bytes === false ) {
00410                                 $status->fatal( 'backend-fail-create', $params['dst'] );
00411                                 return $status;
00412                         }
00413                         $cmd = implode( ' ', array(
00414                                 wfIsWindows() ? 'COPY /B /Y' : 'cp', // (binary, overwrite)
00415                                 wfEscapeShellArg( $this->cleanPathSlashes( $tempFile->getPath() ) ),
00416                                 wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
00417                         ) );
00418                         $status->value = new FSFileOpHandle( $this, $params, 'Create', $cmd, $dest );
00419                         $tempFile->bind( $status->value );
00420                 } else { // immediate write
00421                         $bytes = file_put_contents( $dest, $params['content'] );
00422                         if ( $bytes === false ) {
00423                                 $status->fatal( 'backend-fail-create', $params['dst'] );
00424                                 return $status;
00425                         }
00426                         $this->chmod( $dest );
00427                 }
00428 
00429                 return $status;
00430         }
00431 
00435         protected function _getResponseCreate( $errors, Status $status, array $params, $cmd ) {
00436                 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
00437                         $status->fatal( 'backend-fail-create', $params['dst'] );
00438                         trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
00439                 }
00440         }
00441 
00446         protected function doPrepareInternal( $fullCont, $dirRel, array $params ) {
00447                 $status = Status::newGood();
00448                 list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
00449                 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
00450                 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
00451                 $existed = is_dir( $dir ); // already there?
00452                 if ( !wfMkdirParents( $dir ) ) { // make directory and its parents
00453                         $status->fatal( 'directorycreateerror', $params['dir'] ); // fails on races
00454                 } elseif ( !is_writable( $dir ) ) {
00455                         $status->fatal( 'directoryreadonlyerror', $params['dir'] );
00456                 } elseif ( !is_readable( $dir ) ) {
00457                         $status->fatal( 'directorynotreadableerror', $params['dir'] );
00458                 }
00459                 if ( is_dir( $dir ) && !$existed ) {
00460                         // Respect any 'noAccess' or 'noListing' flags...
00461                         $status->merge( $this->doSecureInternal( $fullCont, $dirRel, $params ) );
00462                 }
00463                 return $status;
00464         }
00465 
00470         protected function doSecureInternal( $fullCont, $dirRel, array $params ) {
00471                 $status = Status::newGood();
00472                 list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
00473                 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
00474                 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
00475                 // Seed new directories with a blank index.html, to prevent crawling...
00476                 if ( !empty( $params['noListing'] ) && !file_exists( "{$dir}/index.html" ) ) {
00477                         $bytes = file_put_contents( "{$dir}/index.html", $this->indexHtmlPrivate() );
00478                         if ( $bytes === false ) {
00479                                 $status->fatal( 'backend-fail-create', $params['dir'] . '/index.html' );
00480                                 return $status;
00481                         }
00482                 }
00483                 // Add a .htaccess file to the root of the container...
00484                 if ( !empty( $params['noAccess'] ) && !file_exists( "{$contRoot}/.htaccess" ) ) {
00485                         $bytes = file_put_contents( "{$contRoot}/.htaccess", $this->htaccessPrivate() );
00486                         if ( $bytes === false ) {
00487                                 $storeDir = "mwstore://{$this->name}/{$shortCont}";
00488                                 $status->fatal( 'backend-fail-create', "{$storeDir}/.htaccess" );
00489                                 return $status;
00490                         }
00491                 }
00492                 return $status;
00493         }
00494 
00499         protected function doPublishInternal( $fullCont, $dirRel, array $params ) {
00500                 $status = Status::newGood();
00501                 list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
00502                 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
00503                 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
00504                 // Unseed new directories with a blank index.html, to allow crawling...
00505                 if ( !empty( $params['listing'] ) && is_file( "{$dir}/index.html" ) ) {
00506                         $exists = ( file_get_contents( "{$dir}/index.html" ) === $this->indexHtmlPrivate() );
00507                         if ( $exists && !unlink( "{$dir}/index.html" ) ) { // reverse secure()
00508                                 $status->fatal( 'backend-fail-delete', $params['dir'] . '/index.html' );
00509                                 return $status;
00510                         }
00511                 }
00512                 // Remove the .htaccess file from the root of the container...
00513                 if ( !empty( $params['access'] ) && is_file( "{$contRoot}/.htaccess" ) ) {
00514                         $exists = ( file_get_contents( "{$contRoot}/.htaccess" ) === $this->htaccessPrivate() );
00515                         if ( $exists && !unlink( "{$contRoot}/.htaccess" ) ) { // reverse secure()
00516                                 $storeDir = "mwstore://{$this->name}/{$shortCont}";
00517                                 $status->fatal( 'backend-fail-delete', "{$storeDir}/.htaccess" );
00518                                 return $status;
00519                         }
00520                 }
00521                 return $status;
00522         }
00523 
00528         protected function doCleanInternal( $fullCont, $dirRel, array $params ) {
00529                 $status = Status::newGood();
00530                 list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
00531                 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
00532                 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
00533                 wfSuppressWarnings();
00534                 if ( is_dir( $dir ) ) {
00535                         rmdir( $dir ); // remove directory if empty
00536                 }
00537                 wfRestoreWarnings();
00538                 return $status;
00539         }
00540 
00545         protected function doGetFileStat( array $params ) {
00546                 $source = $this->resolveToFSPath( $params['src'] );
00547                 if ( $source === null ) {
00548                         return false; // invalid storage path
00549                 }
00550 
00551                 $this->trapWarnings(); // don't trust 'false' if there were errors
00552                 $stat = is_file( $source ) ? stat( $source ) : false; // regular files only
00553                 $hadError = $this->untrapWarnings();
00554 
00555                 if ( $stat ) {
00556                         return array(
00557                                 'mtime' => wfTimestamp( TS_MW, $stat['mtime'] ),
00558                                 'size'  => $stat['size']
00559                         );
00560                 } elseif ( !$hadError ) {
00561                         return false; // file does not exist
00562                 } else {
00563                         return null; // failure
00564                 }
00565         }
00566 
00570         protected function doClearCache( array $paths = null ) {
00571                 clearstatcache(); // clear the PHP file stat cache
00572         }
00573 
00578         protected function doDirectoryExists( $fullCont, $dirRel, array $params ) {
00579                 list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
00580                 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
00581                 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
00582 
00583                 $this->trapWarnings(); // don't trust 'false' if there were errors
00584                 $exists = is_dir( $dir );
00585                 $hadError = $this->untrapWarnings();
00586 
00587                 return $hadError ? null : $exists;
00588         }
00589 
00594         public function getDirectoryListInternal( $fullCont, $dirRel, array $params ) {
00595                 list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
00596                 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
00597                 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
00598                 $exists = is_dir( $dir );
00599                 if ( !$exists ) {
00600                         wfDebug( __METHOD__ . "() given directory does not exist: '$dir'\n" );
00601                         return array(); // nothing under this dir
00602                 } elseif ( !is_readable( $dir ) ) {
00603                         wfDebug( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
00604                         return null; // bad permissions?
00605                 }
00606                 return new FSFileBackendDirList( $dir, $params );
00607         }
00608 
00613         public function getFileListInternal( $fullCont, $dirRel, array $params ) {
00614                 list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
00615                 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
00616                 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
00617                 $exists = is_dir( $dir );
00618                 if ( !$exists ) {
00619                         wfDebug( __METHOD__ . "() given directory does not exist: '$dir'\n" );
00620                         return array(); // nothing under this dir
00621                 } elseif ( !is_readable( $dir ) ) {
00622                         wfDebug( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
00623                         return null; // bad permissions?
00624                 }
00625                 return new FSFileBackendFileList( $dir, $params );
00626         }
00627 
00632         protected function doGetLocalReferenceMulti( array $params ) {
00633                 $fsFiles = array(); // (path => FSFile)
00634 
00635                 foreach ( $params['srcs'] as $src ) {
00636                         $source = $this->resolveToFSPath( $src );
00637                         if ( $source === null || !is_file( $source ) ) {
00638                                 $fsFiles[$src] = null; // invalid path or file does not exist
00639                         } else {
00640                                 $fsFiles[$src] = new FSFile( $source );
00641                         }
00642                 }
00643 
00644                 return $fsFiles;
00645         }
00646 
00651         protected function doGetLocalCopyMulti( array $params ) {
00652                 $tmpFiles = array(); // (path => TempFSFile)
00653 
00654                 foreach ( $params['srcs'] as $src ) {
00655                         $source = $this->resolveToFSPath( $src );
00656                         if ( $source === null ) {
00657                                 $tmpFiles[$src] = null; // invalid path
00658                         } else {
00659                                 // Create a new temporary file with the same extension...
00660                                 $ext = FileBackend::extensionFromPath( $src );
00661                                 $tmpFile = TempFSFile::factory( 'localcopy_', $ext );
00662                                 if ( !$tmpFile ) {
00663                                         $tmpFiles[$src] = null;
00664                                 } else {
00665                                         $tmpPath = $tmpFile->getPath();
00666                                         // Copy the source file over the temp file
00667                                         wfSuppressWarnings();
00668                                         $ok = copy( $source, $tmpPath );
00669                                         wfRestoreWarnings();
00670                                         if ( !$ok ) {
00671                                                 $tmpFiles[$src] = null;
00672                                         } else {
00673                                                 $this->chmod( $tmpPath );
00674                                                 $tmpFiles[$src] = $tmpFile;
00675                                         }
00676                                 }
00677                         }
00678                 }
00679 
00680                 return $tmpFiles;
00681         }
00682 
00687         protected function directoriesAreVirtual() {
00688                 return false;
00689         }
00690 
00695         protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
00696                 $statuses = array();
00697 
00698                 $pipes = array();
00699                 foreach ( $fileOpHandles as $index => $fileOpHandle ) {
00700                         $pipes[$index] = popen( "{$fileOpHandle->cmd} 2>&1", 'r' );
00701                 }
00702 
00703                 $errs = array();
00704                 foreach ( $pipes as $index => $pipe ) {
00705                         // Result will be empty on success in *NIX. On Windows,
00706                         // it may be something like "        1 file(s) [copied|moved].".
00707                         $errs[$index] = stream_get_contents( $pipe );
00708                         fclose( $pipe );
00709                 }
00710 
00711                 foreach ( $fileOpHandles as $index => $fileOpHandle ) {
00712                         $status = Status::newGood();
00713                         $function = '_getResponse' . $fileOpHandle->call;
00714                         $this->$function( $errs[$index], $status, $fileOpHandle->params, $fileOpHandle->cmd );
00715                         $statuses[$index] = $status;
00716                         if ( $status->isOK() && $fileOpHandle->chmodPath ) {
00717                                 $this->chmod( $fileOpHandle->chmodPath );
00718                         }
00719                 }
00720 
00721                 clearstatcache(); // files changed
00722                 return $statuses;
00723         }
00724 
00731         protected function chmod( $path ) {
00732                 wfSuppressWarnings();
00733                 $ok = chmod( $path, $this->fileMode );
00734                 wfRestoreWarnings();
00735 
00736                 return $ok;
00737         }
00738 
00744         protected function indexHtmlPrivate() {
00745                 return '';
00746         }
00747 
00753         protected function htaccessPrivate() {
00754                 return "Deny from all\n";
00755         }
00756 
00763         protected function cleanPathSlashes( $path ) {
00764                 return wfIsWindows() ? strtr( $path, '/', '\\' ) : $path;
00765         }
00766 
00772         protected function trapWarnings() {
00773                 $this->hadWarningErrors[] = false; // push to stack
00774                 set_error_handler( array( $this, 'handleWarning' ), E_WARNING );
00775                 return false; // invoke normal PHP error handler
00776         }
00777 
00783         protected function untrapWarnings() {
00784                 restore_error_handler(); // restore previous handler
00785                 return array_pop( $this->hadWarningErrors ); // pop from stack
00786         }
00787 
00791         private function handleWarning() {
00792                 $this->hadWarningErrors[count( $this->hadWarningErrors ) - 1] = true;
00793                 return true; // suppress from PHP handler
00794         }
00795 }
00796 
00800 class FSFileOpHandle extends FileBackendStoreOpHandle {
00801         public $cmd; // string; shell command
00802         public $chmodPath; // string; file to chmod
00803 
00811         public function __construct( $backend, array $params, $call, $cmd, $chmodPath = null ) {
00812                 $this->backend = $backend;
00813                 $this->params = $params;
00814                 $this->call = $call;
00815                 $this->cmd = $cmd;
00816                 $this->chmodPath = $chmodPath;
00817         }
00818 }
00819 
00827 abstract class FSFileBackendList implements Iterator {
00829         protected $iter;
00830         protected $suffixStart; // integer
00831         protected $pos = 0; // integer
00833         protected $params = array();
00834 
00839         public function __construct( $dir, array $params ) {
00840                 $dir = realpath( $dir ); // normalize
00841                 $this->suffixStart = strlen( $dir ) + 1; // size of "path/to/dir/"
00842                 $this->params = $params;
00843 
00844                 try {
00845                         $this->iter = $this->initIterator( $dir );
00846                 } catch ( UnexpectedValueException $e ) {
00847                         $this->iter = null; // bad permissions? deleted?
00848                 }
00849         }
00850 
00857         protected function initIterator( $dir ) {
00858                 if ( !empty( $this->params['topOnly'] ) ) { // non-recursive
00859                         # Get an iterator that will get direct sub-nodes
00860                         return new DirectoryIterator( $dir );
00861                 } else { // recursive
00862                         # Get an iterator that will return leaf nodes (non-directories)
00863                         # RecursiveDirectoryIterator extends FilesystemIterator.
00864                         # FilesystemIterator::SKIP_DOTS default is inconsistent in PHP 5.3.x.
00865                         $flags = FilesystemIterator::CURRENT_AS_SELF | FilesystemIterator::SKIP_DOTS;
00866                         return new RecursiveIteratorIterator(
00867                                 new RecursiveDirectoryIterator( $dir, $flags ),
00868                                 RecursiveIteratorIterator::CHILD_FIRST // include dirs
00869                         );
00870                 }
00871         }
00872 
00877         public function key() {
00878                 return $this->pos;
00879         }
00880 
00885         public function current() {
00886                 return $this->getRelPath( $this->iter->current()->getPathname() );
00887         }
00888 
00893         public function next() {
00894                 try {
00895                         $this->iter->next();
00896                         $this->filterViaNext();
00897                 } catch ( UnexpectedValueException $e ) {
00898                         $this->iter = null;
00899                 }
00900                 ++$this->pos;
00901         }
00902 
00907         public function rewind() {
00908                 $this->pos = 0;
00909                 try {
00910                         $this->iter->rewind();
00911                         $this->filterViaNext();
00912                 } catch ( UnexpectedValueException $e ) {
00913                         $this->iter = null;
00914                 }
00915         }
00916 
00921         public function valid() {
00922                 return $this->iter && $this->iter->valid();
00923         }
00924 
00928         protected function filterViaNext() {}
00929 
00937         protected function getRelPath( $path ) {
00938                 return strtr( substr( realpath( $path ), $this->suffixStart ), '\\', '/' );
00939         }
00940 }
00941 
00942 class FSFileBackendDirList extends FSFileBackendList {
00943         protected function filterViaNext() {
00944                 while ( $this->iter->valid() ) {
00945                         if ( $this->iter->current()->isDot() || !$this->iter->current()->isDir() ) {
00946                                 $this->iter->next(); // skip non-directories and dot files
00947                         } else {
00948                                 break;
00949                         }
00950                 }
00951         }
00952 }
00953 
00954 class FSFileBackendFileList extends FSFileBackendList {
00955         protected function filterViaNext() {
00956                 while ( $this->iter->valid() ) {
00957                         if ( !$this->iter->current()->isFile() ) {
00958                                 $this->iter->next(); // skip non-files and dot files
00959                         } else {
00960                                 break;
00961                         }
00962                 }
00963         }
00964 }