MediaWiki
REL1_23
|
00001 <?php 00041 class FSFileBackend extends FileBackendStore { 00043 protected $basePath; 00044 00046 protected $containerPaths = array(); 00047 00049 protected $fileMode; 00050 00052 protected $fileOwner; 00053 00055 protected $currentUser; 00056 00058 protected $hadWarningErrors = array(); 00059 00068 public function __construct( array $config ) { 00069 parent::__construct( $config ); 00070 00071 // Remove any possible trailing slash from directories 00072 if ( isset( $config['basePath'] ) ) { 00073 $this->basePath = rtrim( $config['basePath'], '/' ); // remove trailing slash 00074 } else { 00075 $this->basePath = null; // none; containers must have explicit paths 00076 } 00077 00078 if ( isset( $config['containerPaths'] ) ) { 00079 $this->containerPaths = (array)$config['containerPaths']; 00080 foreach ( $this->containerPaths as &$path ) { 00081 $path = rtrim( $path, '/' ); // remove trailing slash 00082 } 00083 } 00084 00085 $this->fileMode = isset( $config['fileMode'] ) ? $config['fileMode'] : 0644; 00086 if ( isset( $config['fileOwner'] ) && function_exists( 'posix_getuid' ) ) { 00087 $this->fileOwner = $config['fileOwner']; 00088 $info = posix_getpwuid( posix_getuid() ); 00089 $this->currentUser = $info['name']; // cache this, assuming it doesn't change 00090 } 00091 } 00092 00093 protected function resolveContainerPath( $container, $relStoragePath ) { 00094 // Check that container has a root directory 00095 if ( isset( $this->containerPaths[$container] ) || isset( $this->basePath ) ) { 00096 // Check for sane relative paths (assume the base paths are OK) 00097 if ( $this->isLegalRelPath( $relStoragePath ) ) { 00098 return $relStoragePath; 00099 } 00100 } 00101 00102 return null; 00103 } 00104 00111 protected function isLegalRelPath( $path ) { 00112 // Check for file names longer than 255 chars 00113 if ( preg_match( '![^/]{256}!', $path ) ) { // ext3/NTFS 00114 return false; 00115 } 00116 if ( wfIsWindows() ) { // NTFS 00117 return !preg_match( '![:*?"<>|]!', $path ); 00118 } else { 00119 return true; 00120 } 00121 } 00122 00131 protected function containerFSRoot( $shortCont, $fullCont ) { 00132 if ( isset( $this->containerPaths[$shortCont] ) ) { 00133 return $this->containerPaths[$shortCont]; 00134 } elseif ( isset( $this->basePath ) ) { 00135 return "{$this->basePath}/{$fullCont}"; 00136 } 00137 00138 return null; // no container base path defined 00139 } 00140 00147 protected function resolveToFSPath( $storagePath ) { 00148 list( $fullCont, $relPath ) = $this->resolveStoragePathReal( $storagePath ); 00149 if ( $relPath === null ) { 00150 return null; // invalid 00151 } 00152 list( , $shortCont, ) = FileBackend::splitStoragePath( $storagePath ); 00153 $fsPath = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid 00154 if ( $relPath != '' ) { 00155 $fsPath .= "/{$relPath}"; 00156 } 00157 00158 return $fsPath; 00159 } 00160 00161 public function isPathUsableInternal( $storagePath ) { 00162 $fsPath = $this->resolveToFSPath( $storagePath ); 00163 if ( $fsPath === null ) { 00164 return false; // invalid 00165 } 00166 $parentDir = dirname( $fsPath ); 00167 00168 if ( file_exists( $fsPath ) ) { 00169 $ok = is_file( $fsPath ) && is_writable( $fsPath ); 00170 } else { 00171 $ok = is_dir( $parentDir ) && is_writable( $parentDir ); 00172 } 00173 00174 if ( $this->fileOwner !== null && $this->currentUser !== $this->fileOwner ) { 00175 $ok = false; 00176 trigger_error( __METHOD__ . ": PHP process owner is not '{$this->fileOwner}'." ); 00177 } 00178 00179 return $ok; 00180 } 00181 00182 protected function doCreateInternal( array $params ) { 00183 $status = Status::newGood(); 00184 00185 $dest = $this->resolveToFSPath( $params['dst'] ); 00186 if ( $dest === null ) { 00187 $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); 00188 00189 return $status; 00190 } 00191 00192 if ( !empty( $params['async'] ) ) { // deferred 00193 $tempFile = TempFSFile::factory( 'create_', 'tmp' ); 00194 if ( !$tempFile ) { 00195 $status->fatal( 'backend-fail-create', $params['dst'] ); 00196 00197 return $status; 00198 } 00199 $this->trapWarnings(); 00200 $bytes = file_put_contents( $tempFile->getPath(), $params['content'] ); 00201 $this->untrapWarnings(); 00202 if ( $bytes === false ) { 00203 $status->fatal( 'backend-fail-create', $params['dst'] ); 00204 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 00221 return $status; 00222 } 00223 $this->chmod( $dest ); 00224 } 00225 00226 return $status; 00227 } 00228 00232 protected function getResponseCreate( $errors, Status $status, array $params, $cmd ) { 00233 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) { 00234 $status->fatal( 'backend-fail-create', $params['dst'] ); 00235 trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output 00236 } 00237 } 00238 00239 protected function doStoreInternal( array $params ) { 00240 $status = Status::newGood(); 00241 00242 $dest = $this->resolveToFSPath( $params['dst'] ); 00243 if ( $dest === null ) { 00244 $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); 00245 00246 return $status; 00247 } 00248 00249 if ( !empty( $params['async'] ) ) { // deferred 00250 $cmd = implode( ' ', array( 00251 wfIsWindows() ? 'COPY /B /Y' : 'cp', // (binary, overwrite) 00252 wfEscapeShellArg( $this->cleanPathSlashes( $params['src'] ) ), 00253 wfEscapeShellArg( $this->cleanPathSlashes( $dest ) ) 00254 ) ); 00255 $status->value = new FSFileOpHandle( $this, $params, 'Store', $cmd, $dest ); 00256 } else { // immediate write 00257 $this->trapWarnings(); 00258 $ok = copy( $params['src'], $dest ); 00259 $this->untrapWarnings(); 00260 // In some cases (at least over NFS), copy() returns true when it fails 00261 if ( !$ok || ( filesize( $params['src'] ) !== filesize( $dest ) ) ) { 00262 if ( $ok ) { // PHP bug 00263 unlink( $dest ); // remove broken file 00264 trigger_error( __METHOD__ . ": copy() failed but returned true." ); 00265 } 00266 $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] ); 00267 00268 return $status; 00269 } 00270 $this->chmod( $dest ); 00271 } 00272 00273 return $status; 00274 } 00275 00279 protected function getResponseStore( $errors, Status $status, array $params, $cmd ) { 00280 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) { 00281 $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] ); 00282 trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output 00283 } 00284 } 00285 00286 protected function doCopyInternal( array $params ) { 00287 $status = Status::newGood(); 00288 00289 $source = $this->resolveToFSPath( $params['src'] ); 00290 if ( $source === null ) { 00291 $status->fatal( 'backend-fail-invalidpath', $params['src'] ); 00292 00293 return $status; 00294 } 00295 00296 $dest = $this->resolveToFSPath( $params['dst'] ); 00297 if ( $dest === null ) { 00298 $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); 00299 00300 return $status; 00301 } 00302 00303 if ( !is_file( $source ) ) { 00304 if ( empty( $params['ignoreMissingSource'] ) ) { 00305 $status->fatal( 'backend-fail-copy', $params['src'] ); 00306 } 00307 00308 return $status; // do nothing; either OK or bad status 00309 } 00310 00311 if ( !empty( $params['async'] ) ) { // deferred 00312 $cmd = implode( ' ', array( 00313 wfIsWindows() ? 'COPY /B /Y' : 'cp', // (binary, overwrite) 00314 wfEscapeShellArg( $this->cleanPathSlashes( $source ) ), 00315 wfEscapeShellArg( $this->cleanPathSlashes( $dest ) ) 00316 ) ); 00317 $status->value = new FSFileOpHandle( $this, $params, 'Copy', $cmd, $dest ); 00318 } else { // immediate write 00319 $this->trapWarnings(); 00320 $ok = ( $source === $dest ) ? true : copy( $source, $dest ); 00321 $this->untrapWarnings(); 00322 // In some cases (at least over NFS), copy() returns true when it fails 00323 if ( !$ok || ( filesize( $source ) !== filesize( $dest ) ) ) { 00324 if ( $ok ) { // PHP bug 00325 $this->trapWarnings(); 00326 unlink( $dest ); // remove broken file 00327 $this->untrapWarnings(); 00328 trigger_error( __METHOD__ . ": copy() failed but returned true." ); 00329 } 00330 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); 00331 00332 return $status; 00333 } 00334 $this->chmod( $dest ); 00335 } 00336 00337 return $status; 00338 } 00339 00343 protected function getResponseCopy( $errors, Status $status, array $params, $cmd ) { 00344 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) { 00345 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); 00346 trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output 00347 } 00348 } 00349 00350 protected function doMoveInternal( array $params ) { 00351 $status = Status::newGood(); 00352 00353 $source = $this->resolveToFSPath( $params['src'] ); 00354 if ( $source === null ) { 00355 $status->fatal( 'backend-fail-invalidpath', $params['src'] ); 00356 00357 return $status; 00358 } 00359 00360 $dest = $this->resolveToFSPath( $params['dst'] ); 00361 if ( $dest === null ) { 00362 $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); 00363 00364 return $status; 00365 } 00366 00367 if ( !is_file( $source ) ) { 00368 if ( empty( $params['ignoreMissingSource'] ) ) { 00369 $status->fatal( 'backend-fail-move', $params['src'] ); 00370 } 00371 00372 return $status; // do nothing; either OK or bad status 00373 } 00374 00375 if ( !empty( $params['async'] ) ) { // deferred 00376 $cmd = implode( ' ', array( 00377 wfIsWindows() ? 'MOVE /Y' : 'mv', // (overwrite) 00378 wfEscapeShellArg( $this->cleanPathSlashes( $source ) ), 00379 wfEscapeShellArg( $this->cleanPathSlashes( $dest ) ) 00380 ) ); 00381 $status->value = new FSFileOpHandle( $this, $params, 'Move', $cmd ); 00382 } else { // immediate write 00383 $this->trapWarnings(); 00384 $ok = ( $source === $dest ) ? true : rename( $source, $dest ); 00385 $this->untrapWarnings(); 00386 clearstatcache(); // file no longer at source 00387 if ( !$ok ) { 00388 $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] ); 00389 00390 return $status; 00391 } 00392 } 00393 00394 return $status; 00395 } 00396 00400 protected function getResponseMove( $errors, Status $status, array $params, $cmd ) { 00401 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) { 00402 $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] ); 00403 trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output 00404 } 00405 } 00406 00407 protected function doDeleteInternal( array $params ) { 00408 $status = Status::newGood(); 00409 00410 $source = $this->resolveToFSPath( $params['src'] ); 00411 if ( $source === null ) { 00412 $status->fatal( 'backend-fail-invalidpath', $params['src'] ); 00413 00414 return $status; 00415 } 00416 00417 if ( !is_file( $source ) ) { 00418 if ( empty( $params['ignoreMissingSource'] ) ) { 00419 $status->fatal( 'backend-fail-delete', $params['src'] ); 00420 } 00421 00422 return $status; // do nothing; either OK or bad status 00423 } 00424 00425 if ( !empty( $params['async'] ) ) { // deferred 00426 $cmd = implode( ' ', array( 00427 wfIsWindows() ? 'DEL' : 'unlink', 00428 wfEscapeShellArg( $this->cleanPathSlashes( $source ) ) 00429 ) ); 00430 $status->value = new FSFileOpHandle( $this, $params, 'Copy', $cmd ); 00431 } else { // immediate write 00432 $this->trapWarnings(); 00433 $ok = unlink( $source ); 00434 $this->untrapWarnings(); 00435 if ( !$ok ) { 00436 $status->fatal( 'backend-fail-delete', $params['src'] ); 00437 00438 return $status; 00439 } 00440 } 00441 00442 return $status; 00443 } 00444 00448 protected function getResponseDelete( $errors, Status $status, array $params, $cmd ) { 00449 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) { 00450 $status->fatal( 'backend-fail-delete', $params['src'] ); 00451 trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output 00452 } 00453 } 00454 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 00482 return $status; 00483 } 00484 00485 protected function doSecureInternal( $fullCont, $dirRel, array $params ) { 00486 $status = Status::newGood(); 00487 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] ); 00488 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid 00489 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot; 00490 // Seed new directories with a blank index.html, to prevent crawling... 00491 if ( !empty( $params['noListing'] ) && !file_exists( "{$dir}/index.html" ) ) { 00492 $this->trapWarnings(); 00493 $bytes = file_put_contents( "{$dir}/index.html", $this->indexHtmlPrivate() ); 00494 $this->untrapWarnings(); 00495 if ( $bytes === false ) { 00496 $status->fatal( 'backend-fail-create', $params['dir'] . '/index.html' ); 00497 } 00498 } 00499 // Add a .htaccess file to the root of the container... 00500 if ( !empty( $params['noAccess'] ) && !file_exists( "{$contRoot}/.htaccess" ) ) { 00501 $this->trapWarnings(); 00502 $bytes = file_put_contents( "{$contRoot}/.htaccess", $this->htaccessPrivate() ); 00503 $this->untrapWarnings(); 00504 if ( $bytes === false ) { 00505 $storeDir = "mwstore://{$this->name}/{$shortCont}"; 00506 $status->fatal( 'backend-fail-create', "{$storeDir}/.htaccess" ); 00507 } 00508 } 00509 00510 return $status; 00511 } 00512 00513 protected function doPublishInternal( $fullCont, $dirRel, array $params ) { 00514 $status = Status::newGood(); 00515 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] ); 00516 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid 00517 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot; 00518 // Unseed new directories with a blank index.html, to allow crawling... 00519 if ( !empty( $params['listing'] ) && is_file( "{$dir}/index.html" ) ) { 00520 $exists = ( file_get_contents( "{$dir}/index.html" ) === $this->indexHtmlPrivate() ); 00521 $this->trapWarnings(); 00522 if ( $exists && !unlink( "{$dir}/index.html" ) ) { // reverse secure() 00523 $status->fatal( 'backend-fail-delete', $params['dir'] . '/index.html' ); 00524 } 00525 $this->untrapWarnings(); 00526 } 00527 // Remove the .htaccess file from the root of the container... 00528 if ( !empty( $params['access'] ) && is_file( "{$contRoot}/.htaccess" ) ) { 00529 $exists = ( file_get_contents( "{$contRoot}/.htaccess" ) === $this->htaccessPrivate() ); 00530 $this->trapWarnings(); 00531 if ( $exists && !unlink( "{$contRoot}/.htaccess" ) ) { // reverse secure() 00532 $storeDir = "mwstore://{$this->name}/{$shortCont}"; 00533 $status->fatal( 'backend-fail-delete', "{$storeDir}/.htaccess" ); 00534 } 00535 $this->untrapWarnings(); 00536 } 00537 00538 return $status; 00539 } 00540 00541 protected function doCleanInternal( $fullCont, $dirRel, array $params ) { 00542 $status = Status::newGood(); 00543 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] ); 00544 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid 00545 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot; 00546 $this->trapWarnings(); 00547 if ( is_dir( $dir ) ) { 00548 rmdir( $dir ); // remove directory if empty 00549 } 00550 $this->untrapWarnings(); 00551 00552 return $status; 00553 } 00554 00555 protected function doGetFileStat( array $params ) { 00556 $source = $this->resolveToFSPath( $params['src'] ); 00557 if ( $source === null ) { 00558 return false; // invalid storage path 00559 } 00560 00561 $this->trapWarnings(); // don't trust 'false' if there were errors 00562 $stat = is_file( $source ) ? stat( $source ) : false; // regular files only 00563 $hadError = $this->untrapWarnings(); 00564 00565 if ( $stat ) { 00566 return array( 00567 'mtime' => wfTimestamp( TS_MW, $stat['mtime'] ), 00568 'size' => $stat['size'] 00569 ); 00570 } elseif ( !$hadError ) { 00571 return false; // file does not exist 00572 } else { 00573 return null; // failure 00574 } 00575 } 00576 00580 protected function doClearCache( array $paths = null ) { 00581 clearstatcache(); // clear the PHP file stat cache 00582 } 00583 00584 protected function doDirectoryExists( $fullCont, $dirRel, array $params ) { 00585 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] ); 00586 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid 00587 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot; 00588 00589 $this->trapWarnings(); // don't trust 'false' if there were errors 00590 $exists = is_dir( $dir ); 00591 $hadError = $this->untrapWarnings(); 00592 00593 return $hadError ? null : $exists; 00594 } 00595 00603 public function getDirectoryListInternal( $fullCont, $dirRel, array $params ) { 00604 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] ); 00605 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid 00606 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot; 00607 $exists = is_dir( $dir ); 00608 if ( !$exists ) { 00609 wfDebug( __METHOD__ . "() given directory does not exist: '$dir'\n" ); 00610 00611 return array(); // nothing under this dir 00612 } elseif ( !is_readable( $dir ) ) { 00613 wfDebug( __METHOD__ . "() given directory is unreadable: '$dir'\n" ); 00614 00615 return null; // bad permissions? 00616 } 00617 00618 return new FSFileBackendDirList( $dir, $params ); 00619 } 00620 00628 public function getFileListInternal( $fullCont, $dirRel, array $params ) { 00629 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] ); 00630 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid 00631 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot; 00632 $exists = is_dir( $dir ); 00633 if ( !$exists ) { 00634 wfDebug( __METHOD__ . "() given directory does not exist: '$dir'\n" ); 00635 00636 return array(); // nothing under this dir 00637 } elseif ( !is_readable( $dir ) ) { 00638 wfDebug( __METHOD__ . "() given directory is unreadable: '$dir'\n" ); 00639 00640 return null; // bad permissions? 00641 } 00642 00643 return new FSFileBackendFileList( $dir, $params ); 00644 } 00645 00646 protected function doGetLocalReferenceMulti( array $params ) { 00647 $fsFiles = array(); // (path => FSFile) 00648 00649 foreach ( $params['srcs'] as $src ) { 00650 $source = $this->resolveToFSPath( $src ); 00651 if ( $source === null || !is_file( $source ) ) { 00652 $fsFiles[$src] = null; // invalid path or file does not exist 00653 } else { 00654 $fsFiles[$src] = new FSFile( $source ); 00655 } 00656 } 00657 00658 return $fsFiles; 00659 } 00660 00661 protected function doGetLocalCopyMulti( array $params ) { 00662 $tmpFiles = array(); // (path => TempFSFile) 00663 00664 foreach ( $params['srcs'] as $src ) { 00665 $source = $this->resolveToFSPath( $src ); 00666 if ( $source === null ) { 00667 $tmpFiles[$src] = null; // invalid path 00668 } else { 00669 // Create a new temporary file with the same extension... 00670 $ext = FileBackend::extensionFromPath( $src ); 00671 $tmpFile = TempFSFile::factory( 'localcopy_', $ext ); 00672 if ( !$tmpFile ) { 00673 $tmpFiles[$src] = null; 00674 } else { 00675 $tmpPath = $tmpFile->getPath(); 00676 // Copy the source file over the temp file 00677 $this->trapWarnings(); 00678 $ok = copy( $source, $tmpPath ); 00679 $this->untrapWarnings(); 00680 if ( !$ok ) { 00681 $tmpFiles[$src] = null; 00682 } else { 00683 $this->chmod( $tmpPath ); 00684 $tmpFiles[$src] = $tmpFile; 00685 } 00686 } 00687 } 00688 } 00689 00690 return $tmpFiles; 00691 } 00692 00693 protected function directoriesAreVirtual() { 00694 return false; 00695 } 00696 00697 protected function doExecuteOpHandlesInternal( array $fileOpHandles ) { 00698 $statuses = array(); 00699 00700 $pipes = array(); 00701 foreach ( $fileOpHandles as $index => $fileOpHandle ) { 00702 $pipes[$index] = popen( "{$fileOpHandle->cmd} 2>&1", 'r' ); 00703 } 00704 00705 $errs = array(); 00706 foreach ( $pipes as $index => $pipe ) { 00707 // Result will be empty on success in *NIX. On Windows, 00708 // it may be something like " 1 file(s) [copied|moved].". 00709 $errs[$index] = stream_get_contents( $pipe ); 00710 fclose( $pipe ); 00711 } 00712 00713 foreach ( $fileOpHandles as $index => $fileOpHandle ) { 00714 $status = Status::newGood(); 00715 $function = 'getResponse' . $fileOpHandle->call; 00716 $this->$function( $errs[$index], $status, $fileOpHandle->params, $fileOpHandle->cmd ); 00717 $statuses[$index] = $status; 00718 if ( $status->isOK() && $fileOpHandle->chmodPath ) { 00719 $this->chmod( $fileOpHandle->chmodPath ); 00720 } 00721 } 00722 00723 clearstatcache(); // files changed 00724 return $statuses; 00725 } 00726 00733 protected function chmod( $path ) { 00734 $this->trapWarnings(); 00735 $ok = chmod( $path, $this->fileMode ); 00736 $this->untrapWarnings(); 00737 00738 return $ok; 00739 } 00740 00746 protected function indexHtmlPrivate() { 00747 return ''; 00748 } 00749 00755 protected function htaccessPrivate() { 00756 return "Deny from all\n"; 00757 } 00758 00765 protected function cleanPathSlashes( $path ) { 00766 return wfIsWindows() ? strtr( $path, '/', '\\' ) : $path; 00767 } 00768 00772 protected function trapWarnings() { 00773 $this->hadWarningErrors[] = false; // push to stack 00774 set_error_handler( array( $this, 'handleWarning' ), E_WARNING ); 00775 } 00776 00782 protected function untrapWarnings() { 00783 restore_error_handler(); // restore previous handler 00784 return array_pop( $this->hadWarningErrors ); // pop from stack 00785 } 00786 00793 public function handleWarning( $errno, $errstr ) { 00794 wfDebugLog( 'FSFileBackend', $errstr ); // more detailed error logging 00795 $this->hadWarningErrors[count( $this->hadWarningErrors ) - 1] = true; 00796 00797 return true; // suppress from PHP handler 00798 } 00799 } 00800 00804 class FSFileOpHandle extends FileBackendStoreOpHandle { 00805 public $cmd; // string; shell command 00806 public $chmodPath; // string; file to chmod 00807 00815 public function __construct( 00816 FSFileBackend $backend, array $params, $call, $cmd, $chmodPath = null 00817 ) { 00818 $this->backend = $backend; 00819 $this->params = $params; 00820 $this->call = $call; 00821 $this->cmd = $cmd; 00822 $this->chmodPath = $chmodPath; 00823 } 00824 } 00825 00833 abstract class FSFileBackendList implements Iterator { 00835 protected $iter; 00836 00838 protected $suffixStart; 00839 00841 protected $pos = 0; 00842 00844 protected $params = array(); 00845 00850 public function __construct( $dir, array $params ) { 00851 $path = realpath( $dir ); // normalize 00852 if ( $path === false ) { 00853 $path = $dir; 00854 } 00855 $this->suffixStart = strlen( $path ) + 1; // size of "path/to/dir/" 00856 $this->params = $params; 00857 00858 try { 00859 $this->iter = $this->initIterator( $path ); 00860 } catch ( UnexpectedValueException $e ) { 00861 $this->iter = null; // bad permissions? deleted? 00862 } 00863 } 00864 00871 protected function initIterator( $dir ) { 00872 if ( !empty( $this->params['topOnly'] ) ) { // non-recursive 00873 # Get an iterator that will get direct sub-nodes 00874 return new DirectoryIterator( $dir ); 00875 } else { // recursive 00876 # Get an iterator that will return leaf nodes (non-directories) 00877 # RecursiveDirectoryIterator extends FilesystemIterator. 00878 # FilesystemIterator::SKIP_DOTS default is inconsistent in PHP 5.3.x. 00879 $flags = FilesystemIterator::CURRENT_AS_SELF | FilesystemIterator::SKIP_DOTS; 00880 00881 return new RecursiveIteratorIterator( 00882 new RecursiveDirectoryIterator( $dir, $flags ), 00883 RecursiveIteratorIterator::CHILD_FIRST // include dirs 00884 ); 00885 } 00886 } 00887 00892 public function key() { 00893 return $this->pos; 00894 } 00895 00900 public function current() { 00901 return $this->getRelPath( $this->iter->current()->getPathname() ); 00902 } 00903 00908 public function next() { 00909 try { 00910 $this->iter->next(); 00911 $this->filterViaNext(); 00912 } catch ( UnexpectedValueException $e ) { // bad permissions? deleted? 00913 throw new FileBackendError( "File iterator gave UnexpectedValueException." ); 00914 } 00915 ++$this->pos; 00916 } 00917 00922 public function rewind() { 00923 $this->pos = 0; 00924 try { 00925 $this->iter->rewind(); 00926 $this->filterViaNext(); 00927 } catch ( UnexpectedValueException $e ) { // bad permissions? deleted? 00928 throw new FileBackendError( "File iterator gave UnexpectedValueException." ); 00929 } 00930 } 00931 00936 public function valid() { 00937 return $this->iter && $this->iter->valid(); 00938 } 00939 00943 protected function filterViaNext() { 00944 } 00945 00953 protected function getRelPath( $dir ) { 00954 $path = realpath( $dir ); 00955 if ( $path === false ) { 00956 $path = $dir; 00957 } 00958 00959 return strtr( substr( $path, $this->suffixStart ), '\\', '/' ); 00960 } 00961 } 00962 00963 class FSFileBackendDirList extends FSFileBackendList { 00964 protected function filterViaNext() { 00965 while ( $this->iter->valid() ) { 00966 if ( $this->iter->current()->isDot() || !$this->iter->current()->isDir() ) { 00967 $this->iter->next(); // skip non-directories and dot files 00968 } else { 00969 break; 00970 } 00971 } 00972 } 00973 } 00974 00975 class FSFileBackendFileList extends FSFileBackendList { 00976 protected function filterViaNext() { 00977 while ( $this->iter->valid() ) { 00978 if ( !$this->iter->current()->isFile() ) { 00979 $this->iter->next(); // skip non-files and dot files 00980 } else { 00981 break; 00982 } 00983 } 00984 } 00985 }