MediaWiki
REL1_24
|
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 public function getFeatures() { 00094 return !wfIsWindows() ? FileBackend::ATTR_UNICODE_PATHS : 0; 00095 } 00096 00097 protected function resolveContainerPath( $container, $relStoragePath ) { 00098 // Check that container has a root directory 00099 if ( isset( $this->containerPaths[$container] ) || isset( $this->basePath ) ) { 00100 // Check for sane relative paths (assume the base paths are OK) 00101 if ( $this->isLegalRelPath( $relStoragePath ) ) { 00102 return $relStoragePath; 00103 } 00104 } 00105 00106 return null; 00107 } 00108 00115 protected function isLegalRelPath( $path ) { 00116 // Check for file names longer than 255 chars 00117 if ( preg_match( '![^/]{256}!', $path ) ) { // ext3/NTFS 00118 return false; 00119 } 00120 if ( wfIsWindows() ) { // NTFS 00121 return !preg_match( '![:*?"<>|]!', $path ); 00122 } else { 00123 return true; 00124 } 00125 } 00126 00135 protected function containerFSRoot( $shortCont, $fullCont ) { 00136 if ( isset( $this->containerPaths[$shortCont] ) ) { 00137 return $this->containerPaths[$shortCont]; 00138 } elseif ( isset( $this->basePath ) ) { 00139 return "{$this->basePath}/{$fullCont}"; 00140 } 00141 00142 return null; // no container base path defined 00143 } 00144 00151 protected function resolveToFSPath( $storagePath ) { 00152 list( $fullCont, $relPath ) = $this->resolveStoragePathReal( $storagePath ); 00153 if ( $relPath === null ) { 00154 return null; // invalid 00155 } 00156 list( , $shortCont, ) = FileBackend::splitStoragePath( $storagePath ); 00157 $fsPath = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid 00158 if ( $relPath != '' ) { 00159 $fsPath .= "/{$relPath}"; 00160 } 00161 00162 return $fsPath; 00163 } 00164 00165 public function isPathUsableInternal( $storagePath ) { 00166 $fsPath = $this->resolveToFSPath( $storagePath ); 00167 if ( $fsPath === null ) { 00168 return false; // invalid 00169 } 00170 $parentDir = dirname( $fsPath ); 00171 00172 if ( file_exists( $fsPath ) ) { 00173 $ok = is_file( $fsPath ) && is_writable( $fsPath ); 00174 } else { 00175 $ok = is_dir( $parentDir ) && is_writable( $parentDir ); 00176 } 00177 00178 if ( $this->fileOwner !== null && $this->currentUser !== $this->fileOwner ) { 00179 $ok = false; 00180 trigger_error( __METHOD__ . ": PHP process owner is not '{$this->fileOwner}'." ); 00181 } 00182 00183 return $ok; 00184 } 00185 00186 protected function doCreateInternal( array $params ) { 00187 $status = Status::newGood(); 00188 00189 $dest = $this->resolveToFSPath( $params['dst'] ); 00190 if ( $dest === null ) { 00191 $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); 00192 00193 return $status; 00194 } 00195 00196 if ( !empty( $params['async'] ) ) { // deferred 00197 $tempFile = TempFSFile::factory( 'create_', 'tmp' ); 00198 if ( !$tempFile ) { 00199 $status->fatal( 'backend-fail-create', $params['dst'] ); 00200 00201 return $status; 00202 } 00203 $this->trapWarnings(); 00204 $bytes = file_put_contents( $tempFile->getPath(), $params['content'] ); 00205 $this->untrapWarnings(); 00206 if ( $bytes === false ) { 00207 $status->fatal( 'backend-fail-create', $params['dst'] ); 00208 00209 return $status; 00210 } 00211 $cmd = implode( ' ', array( 00212 wfIsWindows() ? 'COPY /B /Y' : 'cp', // (binary, overwrite) 00213 wfEscapeShellArg( $this->cleanPathSlashes( $tempFile->getPath() ) ), 00214 wfEscapeShellArg( $this->cleanPathSlashes( $dest ) ) 00215 ) ); 00216 $handler = function ( $errors, Status $status, array $params, $cmd ) { 00217 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) { 00218 $status->fatal( 'backend-fail-create', $params['dst'] ); 00219 trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output 00220 } 00221 }; 00222 $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd, $dest ); 00223 $tempFile->bind( $status->value ); 00224 } else { // immediate write 00225 $this->trapWarnings(); 00226 $bytes = file_put_contents( $dest, $params['content'] ); 00227 $this->untrapWarnings(); 00228 if ( $bytes === false ) { 00229 $status->fatal( 'backend-fail-create', $params['dst'] ); 00230 00231 return $status; 00232 } 00233 $this->chmod( $dest ); 00234 } 00235 00236 return $status; 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 $handler = function ( $errors, Status $status, array $params, $cmd ) { 00256 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) { 00257 $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] ); 00258 trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output 00259 } 00260 }; 00261 $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd, $dest ); 00262 } else { // immediate write 00263 $this->trapWarnings(); 00264 $ok = copy( $params['src'], $dest ); 00265 $this->untrapWarnings(); 00266 // In some cases (at least over NFS), copy() returns true when it fails 00267 if ( !$ok || ( filesize( $params['src'] ) !== filesize( $dest ) ) ) { 00268 if ( $ok ) { // PHP bug 00269 unlink( $dest ); // remove broken file 00270 trigger_error( __METHOD__ . ": copy() failed but returned true." ); 00271 } 00272 $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] ); 00273 00274 return $status; 00275 } 00276 $this->chmod( $dest ); 00277 } 00278 00279 return $status; 00280 } 00281 00282 protected function doCopyInternal( array $params ) { 00283 $status = Status::newGood(); 00284 00285 $source = $this->resolveToFSPath( $params['src'] ); 00286 if ( $source === null ) { 00287 $status->fatal( 'backend-fail-invalidpath', $params['src'] ); 00288 00289 return $status; 00290 } 00291 00292 $dest = $this->resolveToFSPath( $params['dst'] ); 00293 if ( $dest === null ) { 00294 $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); 00295 00296 return $status; 00297 } 00298 00299 if ( !is_file( $source ) ) { 00300 if ( empty( $params['ignoreMissingSource'] ) ) { 00301 $status->fatal( 'backend-fail-copy', $params['src'] ); 00302 } 00303 00304 return $status; // do nothing; either OK or bad status 00305 } 00306 00307 if ( !empty( $params['async'] ) ) { // deferred 00308 $cmd = implode( ' ', array( 00309 wfIsWindows() ? 'COPY /B /Y' : 'cp', // (binary, overwrite) 00310 wfEscapeShellArg( $this->cleanPathSlashes( $source ) ), 00311 wfEscapeShellArg( $this->cleanPathSlashes( $dest ) ) 00312 ) ); 00313 $handler = function ( $errors, Status $status, array $params, $cmd ) { 00314 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) { 00315 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); 00316 trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output 00317 } 00318 }; 00319 $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd, $dest ); 00320 } else { // immediate write 00321 $this->trapWarnings(); 00322 $ok = ( $source === $dest ) ? true : copy( $source, $dest ); 00323 $this->untrapWarnings(); 00324 // In some cases (at least over NFS), copy() returns true when it fails 00325 if ( !$ok || ( filesize( $source ) !== filesize( $dest ) ) ) { 00326 if ( $ok ) { // PHP bug 00327 $this->trapWarnings(); 00328 unlink( $dest ); // remove broken file 00329 $this->untrapWarnings(); 00330 trigger_error( __METHOD__ . ": copy() failed but returned true." ); 00331 } 00332 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); 00333 00334 return $status; 00335 } 00336 $this->chmod( $dest ); 00337 } 00338 00339 return $status; 00340 } 00341 00342 protected function doMoveInternal( array $params ) { 00343 $status = Status::newGood(); 00344 00345 $source = $this->resolveToFSPath( $params['src'] ); 00346 if ( $source === null ) { 00347 $status->fatal( 'backend-fail-invalidpath', $params['src'] ); 00348 00349 return $status; 00350 } 00351 00352 $dest = $this->resolveToFSPath( $params['dst'] ); 00353 if ( $dest === null ) { 00354 $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); 00355 00356 return $status; 00357 } 00358 00359 if ( !is_file( $source ) ) { 00360 if ( empty( $params['ignoreMissingSource'] ) ) { 00361 $status->fatal( 'backend-fail-move', $params['src'] ); 00362 } 00363 00364 return $status; // do nothing; either OK or bad status 00365 } 00366 00367 if ( !empty( $params['async'] ) ) { // deferred 00368 $cmd = implode( ' ', array( 00369 wfIsWindows() ? 'MOVE /Y' : 'mv', // (overwrite) 00370 wfEscapeShellArg( $this->cleanPathSlashes( $source ) ), 00371 wfEscapeShellArg( $this->cleanPathSlashes( $dest ) ) 00372 ) ); 00373 $handler = function ( $errors, Status $status, array $params, $cmd ) { 00374 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) { 00375 $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] ); 00376 trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output 00377 } 00378 }; 00379 $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd ); 00380 } else { // immediate write 00381 $this->trapWarnings(); 00382 $ok = ( $source === $dest ) ? true : rename( $source, $dest ); 00383 $this->untrapWarnings(); 00384 clearstatcache(); // file no longer at source 00385 if ( !$ok ) { 00386 $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] ); 00387 00388 return $status; 00389 } 00390 } 00391 00392 return $status; 00393 } 00394 00395 protected function doDeleteInternal( array $params ) { 00396 $status = Status::newGood(); 00397 00398 $source = $this->resolveToFSPath( $params['src'] ); 00399 if ( $source === null ) { 00400 $status->fatal( 'backend-fail-invalidpath', $params['src'] ); 00401 00402 return $status; 00403 } 00404 00405 if ( !is_file( $source ) ) { 00406 if ( empty( $params['ignoreMissingSource'] ) ) { 00407 $status->fatal( 'backend-fail-delete', $params['src'] ); 00408 } 00409 00410 return $status; // do nothing; either OK or bad status 00411 } 00412 00413 if ( !empty( $params['async'] ) ) { // deferred 00414 $cmd = implode( ' ', array( 00415 wfIsWindows() ? 'DEL' : 'unlink', 00416 wfEscapeShellArg( $this->cleanPathSlashes( $source ) ) 00417 ) ); 00418 $handler = function ( $errors, Status $status, array $params, $cmd ) { 00419 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) { 00420 $status->fatal( 'backend-fail-delete', $params['src'] ); 00421 trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output 00422 } 00423 }; 00424 $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd ); 00425 } else { // immediate write 00426 $this->trapWarnings(); 00427 $ok = unlink( $source ); 00428 $this->untrapWarnings(); 00429 if ( !$ok ) { 00430 $status->fatal( 'backend-fail-delete', $params['src'] ); 00431 00432 return $status; 00433 } 00434 } 00435 00436 return $status; 00437 } 00438 00445 protected function doPrepareInternal( $fullCont, $dirRel, array $params ) { 00446 $status = Status::newGood(); 00447 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] ); 00448 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid 00449 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot; 00450 $existed = is_dir( $dir ); // already there? 00451 // Create the directory and its parents as needed... 00452 $this->trapWarnings(); 00453 if ( !wfMkdirParents( $dir ) ) { 00454 $status->fatal( 'directorycreateerror', $params['dir'] ); // fails on races 00455 } elseif ( !is_writable( $dir ) ) { 00456 $status->fatal( 'directoryreadonlyerror', $params['dir'] ); 00457 } elseif ( !is_readable( $dir ) ) { 00458 $status->fatal( 'directorynotreadableerror', $params['dir'] ); 00459 } 00460 $this->untrapWarnings(); 00461 // Respect any 'noAccess' or 'noListing' flags... 00462 if ( is_dir( $dir ) && !$existed ) { 00463 $status->merge( $this->doSecureInternal( $fullCont, $dirRel, $params ) ); 00464 } 00465 00466 return $status; 00467 } 00468 00469 protected function doSecureInternal( $fullCont, $dirRel, array $params ) { 00470 $status = Status::newGood(); 00471 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] ); 00472 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid 00473 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot; 00474 // Seed new directories with a blank index.html, to prevent crawling... 00475 if ( !empty( $params['noListing'] ) && !file_exists( "{$dir}/index.html" ) ) { 00476 $this->trapWarnings(); 00477 $bytes = file_put_contents( "{$dir}/index.html", $this->indexHtmlPrivate() ); 00478 $this->untrapWarnings(); 00479 if ( $bytes === false ) { 00480 $status->fatal( 'backend-fail-create', $params['dir'] . '/index.html' ); 00481 } 00482 } 00483 // Add a .htaccess file to the root of the container... 00484 if ( !empty( $params['noAccess'] ) && !file_exists( "{$contRoot}/.htaccess" ) ) { 00485 $this->trapWarnings(); 00486 $bytes = file_put_contents( "{$contRoot}/.htaccess", $this->htaccessPrivate() ); 00487 $this->untrapWarnings(); 00488 if ( $bytes === false ) { 00489 $storeDir = "mwstore://{$this->name}/{$shortCont}"; 00490 $status->fatal( 'backend-fail-create', "{$storeDir}/.htaccess" ); 00491 } 00492 } 00493 00494 return $status; 00495 } 00496 00497 protected function doPublishInternal( $fullCont, $dirRel, array $params ) { 00498 $status = Status::newGood(); 00499 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] ); 00500 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid 00501 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot; 00502 // Unseed new directories with a blank index.html, to allow crawling... 00503 if ( !empty( $params['listing'] ) && is_file( "{$dir}/index.html" ) ) { 00504 $exists = ( file_get_contents( "{$dir}/index.html" ) === $this->indexHtmlPrivate() ); 00505 $this->trapWarnings(); 00506 if ( $exists && !unlink( "{$dir}/index.html" ) ) { // reverse secure() 00507 $status->fatal( 'backend-fail-delete', $params['dir'] . '/index.html' ); 00508 } 00509 $this->untrapWarnings(); 00510 } 00511 // Remove the .htaccess file from the root of the container... 00512 if ( !empty( $params['access'] ) && is_file( "{$contRoot}/.htaccess" ) ) { 00513 $exists = ( file_get_contents( "{$contRoot}/.htaccess" ) === $this->htaccessPrivate() ); 00514 $this->trapWarnings(); 00515 if ( $exists && !unlink( "{$contRoot}/.htaccess" ) ) { // reverse secure() 00516 $storeDir = "mwstore://{$this->name}/{$shortCont}"; 00517 $status->fatal( 'backend-fail-delete', "{$storeDir}/.htaccess" ); 00518 } 00519 $this->untrapWarnings(); 00520 } 00521 00522 return $status; 00523 } 00524 00525 protected function doCleanInternal( $fullCont, $dirRel, array $params ) { 00526 $status = Status::newGood(); 00527 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] ); 00528 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid 00529 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot; 00530 $this->trapWarnings(); 00531 if ( is_dir( $dir ) ) { 00532 rmdir( $dir ); // remove directory if empty 00533 } 00534 $this->untrapWarnings(); 00535 00536 return $status; 00537 } 00538 00539 protected function doGetFileStat( array $params ) { 00540 $source = $this->resolveToFSPath( $params['src'] ); 00541 if ( $source === null ) { 00542 return false; // invalid storage path 00543 } 00544 00545 $this->trapWarnings(); // don't trust 'false' if there were errors 00546 $stat = is_file( $source ) ? stat( $source ) : false; // regular files only 00547 $hadError = $this->untrapWarnings(); 00548 00549 if ( $stat ) { 00550 return array( 00551 'mtime' => wfTimestamp( TS_MW, $stat['mtime'] ), 00552 'size' => $stat['size'] 00553 ); 00554 } elseif ( !$hadError ) { 00555 return false; // file does not exist 00556 } else { 00557 return null; // failure 00558 } 00559 } 00560 00564 protected function doClearCache( array $paths = null ) { 00565 clearstatcache(); // clear the PHP file stat cache 00566 } 00567 00568 protected function doDirectoryExists( $fullCont, $dirRel, array $params ) { 00569 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] ); 00570 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid 00571 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot; 00572 00573 $this->trapWarnings(); // don't trust 'false' if there were errors 00574 $exists = is_dir( $dir ); 00575 $hadError = $this->untrapWarnings(); 00576 00577 return $hadError ? null : $exists; 00578 } 00579 00587 public function getDirectoryListInternal( $fullCont, $dirRel, array $params ) { 00588 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] ); 00589 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid 00590 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot; 00591 $exists = is_dir( $dir ); 00592 if ( !$exists ) { 00593 wfDebug( __METHOD__ . "() given directory does not exist: '$dir'\n" ); 00594 00595 return array(); // nothing under this dir 00596 } elseif ( !is_readable( $dir ) ) { 00597 wfDebug( __METHOD__ . "() given directory is unreadable: '$dir'\n" ); 00598 00599 return null; // bad permissions? 00600 } 00601 00602 return new FSFileBackendDirList( $dir, $params ); 00603 } 00604 00612 public function getFileListInternal( $fullCont, $dirRel, array $params ) { 00613 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] ); 00614 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid 00615 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot; 00616 $exists = is_dir( $dir ); 00617 if ( !$exists ) { 00618 wfDebug( __METHOD__ . "() given directory does not exist: '$dir'\n" ); 00619 00620 return array(); // nothing under this dir 00621 } elseif ( !is_readable( $dir ) ) { 00622 wfDebug( __METHOD__ . "() given directory is unreadable: '$dir'\n" ); 00623 00624 return null; // bad permissions? 00625 } 00626 00627 return new FSFileBackendFileList( $dir, $params ); 00628 } 00629 00630 protected function doGetLocalReferenceMulti( array $params ) { 00631 $fsFiles = array(); // (path => FSFile) 00632 00633 foreach ( $params['srcs'] as $src ) { 00634 $source = $this->resolveToFSPath( $src ); 00635 if ( $source === null || !is_file( $source ) ) { 00636 $fsFiles[$src] = null; // invalid path or file does not exist 00637 } else { 00638 $fsFiles[$src] = new FSFile( $source ); 00639 } 00640 } 00641 00642 return $fsFiles; 00643 } 00644 00645 protected function doGetLocalCopyMulti( array $params ) { 00646 $tmpFiles = array(); // (path => TempFSFile) 00647 00648 foreach ( $params['srcs'] as $src ) { 00649 $source = $this->resolveToFSPath( $src ); 00650 if ( $source === null ) { 00651 $tmpFiles[$src] = null; // invalid path 00652 } else { 00653 // Create a new temporary file with the same extension... 00654 $ext = FileBackend::extensionFromPath( $src ); 00655 $tmpFile = TempFSFile::factory( 'localcopy_', $ext ); 00656 if ( !$tmpFile ) { 00657 $tmpFiles[$src] = null; 00658 } else { 00659 $tmpPath = $tmpFile->getPath(); 00660 // Copy the source file over the temp file 00661 $this->trapWarnings(); 00662 $ok = copy( $source, $tmpPath ); 00663 $this->untrapWarnings(); 00664 if ( !$ok ) { 00665 $tmpFiles[$src] = null; 00666 } else { 00667 $this->chmod( $tmpPath ); 00668 $tmpFiles[$src] = $tmpFile; 00669 } 00670 } 00671 } 00672 } 00673 00674 return $tmpFiles; 00675 } 00676 00677 protected function directoriesAreVirtual() { 00678 return false; 00679 } 00680 00681 protected function doExecuteOpHandlesInternal( array $fileOpHandles ) { 00682 $statuses = array(); 00683 00684 $pipes = array(); 00685 foreach ( $fileOpHandles as $index => $fileOpHandle ) { 00686 $pipes[$index] = popen( "{$fileOpHandle->cmd} 2>&1", 'r' ); 00687 } 00688 00689 $errs = array(); 00690 foreach ( $pipes as $index => $pipe ) { 00691 // Result will be empty on success in *NIX. On Windows, 00692 // it may be something like " 1 file(s) [copied|moved].". 00693 $errs[$index] = stream_get_contents( $pipe ); 00694 fclose( $pipe ); 00695 } 00696 00697 foreach ( $fileOpHandles as $index => $fileOpHandle ) { 00698 $status = Status::newGood(); 00699 $function = $fileOpHandle->call; 00700 $function( $errs[$index], $status, $fileOpHandle->params, $fileOpHandle->cmd ); 00701 $statuses[$index] = $status; 00702 if ( $status->isOK() && $fileOpHandle->chmodPath ) { 00703 $this->chmod( $fileOpHandle->chmodPath ); 00704 } 00705 } 00706 00707 clearstatcache(); // files changed 00708 return $statuses; 00709 } 00710 00717 protected function chmod( $path ) { 00718 $this->trapWarnings(); 00719 $ok = chmod( $path, $this->fileMode ); 00720 $this->untrapWarnings(); 00721 00722 return $ok; 00723 } 00724 00730 protected function indexHtmlPrivate() { 00731 return ''; 00732 } 00733 00739 protected function htaccessPrivate() { 00740 return "Deny from all\n"; 00741 } 00742 00749 protected function cleanPathSlashes( $path ) { 00750 return wfIsWindows() ? strtr( $path, '/', '\\' ) : $path; 00751 } 00752 00756 protected function trapWarnings() { 00757 $this->hadWarningErrors[] = false; // push to stack 00758 set_error_handler( array( $this, 'handleWarning' ), E_WARNING ); 00759 } 00760 00766 protected function untrapWarnings() { 00767 restore_error_handler(); // restore previous handler 00768 return array_pop( $this->hadWarningErrors ); // pop from stack 00769 } 00770 00777 public function handleWarning( $errno, $errstr ) { 00778 wfDebugLog( 'FSFileBackend', $errstr ); // more detailed error logging 00779 $this->hadWarningErrors[count( $this->hadWarningErrors ) - 1] = true; 00780 00781 return true; // suppress from PHP handler 00782 } 00783 } 00784 00788 class FSFileOpHandle extends FileBackendStoreOpHandle { 00789 public $cmd; // string; shell command 00790 public $chmodPath; // string; file to chmod 00791 00799 public function __construct( 00800 FSFileBackend $backend, array $params, $call, $cmd, $chmodPath = null 00801 ) { 00802 $this->backend = $backend; 00803 $this->params = $params; 00804 $this->call = $call; 00805 $this->cmd = $cmd; 00806 $this->chmodPath = $chmodPath; 00807 } 00808 } 00809 00817 abstract class FSFileBackendList implements Iterator { 00819 protected $iter; 00820 00822 protected $suffixStart; 00823 00825 protected $pos = 0; 00826 00828 protected $params = array(); 00829 00834 public function __construct( $dir, array $params ) { 00835 $path = realpath( $dir ); // normalize 00836 if ( $path === false ) { 00837 $path = $dir; 00838 } 00839 $this->suffixStart = strlen( $path ) + 1; // size of "path/to/dir/" 00840 $this->params = $params; 00841 00842 try { 00843 $this->iter = $this->initIterator( $path ); 00844 } catch ( UnexpectedValueException $e ) { 00845 $this->iter = null; // bad permissions? deleted? 00846 } 00847 } 00848 00855 protected function initIterator( $dir ) { 00856 if ( !empty( $this->params['topOnly'] ) ) { // non-recursive 00857 # Get an iterator that will get direct sub-nodes 00858 return new DirectoryIterator( $dir ); 00859 } else { // recursive 00860 # Get an iterator that will return leaf nodes (non-directories) 00861 # RecursiveDirectoryIterator extends FilesystemIterator. 00862 # FilesystemIterator::SKIP_DOTS default is inconsistent in PHP 5.3.x. 00863 $flags = FilesystemIterator::CURRENT_AS_SELF | FilesystemIterator::SKIP_DOTS; 00864 00865 return new RecursiveIteratorIterator( 00866 new RecursiveDirectoryIterator( $dir, $flags ), 00867 RecursiveIteratorIterator::CHILD_FIRST // include dirs 00868 ); 00869 } 00870 } 00871 00876 public function key() { 00877 return $this->pos; 00878 } 00879 00884 public function current() { 00885 return $this->getRelPath( $this->iter->current()->getPathname() ); 00886 } 00887 00892 public function next() { 00893 try { 00894 $this->iter->next(); 00895 $this->filterViaNext(); 00896 } catch ( UnexpectedValueException $e ) { // bad permissions? deleted? 00897 throw new FileBackendError( "File iterator gave UnexpectedValueException." ); 00898 } 00899 ++$this->pos; 00900 } 00901 00906 public function rewind() { 00907 $this->pos = 0; 00908 try { 00909 $this->iter->rewind(); 00910 $this->filterViaNext(); 00911 } catch ( UnexpectedValueException $e ) { // bad permissions? deleted? 00912 throw new FileBackendError( "File iterator gave UnexpectedValueException." ); 00913 } 00914 } 00915 00920 public function valid() { 00921 return $this->iter && $this->iter->valid(); 00922 } 00923 00927 protected function filterViaNext() { 00928 } 00929 00937 protected function getRelPath( $dir ) { 00938 $path = realpath( $dir ); 00939 if ( $path === false ) { 00940 $path = $dir; 00941 } 00942 00943 return strtr( substr( $path, $this->suffixStart ), '\\', '/' ); 00944 } 00945 } 00946 00947 class FSFileBackendDirList extends FSFileBackendList { 00948 protected function filterViaNext() { 00949 while ( $this->iter->valid() ) { 00950 if ( $this->iter->current()->isDot() || !$this->iter->current()->isDir() ) { 00951 $this->iter->next(); // skip non-directories and dot files 00952 } else { 00953 break; 00954 } 00955 } 00956 } 00957 } 00958 00959 class FSFileBackendFileList extends FSFileBackendList { 00960 protected function filterViaNext() { 00961 while ( $this->iter->valid() ) { 00962 if ( !$this->iter->current()->isFile() ) { 00963 $this->iter->next(); // skip non-files and dot files 00964 } else { 00965 break; 00966 } 00967 } 00968 } 00969 }