MediaWiki REL1_29
FSFileBackend.php
Go to the documentation of this file.
1<?php
24use Wikimedia\Timestamp\ConvertibleTimestamp;
25
44 protected $basePath;
45
47 protected $containerPaths = [];
48
50 protected $fileMode;
52 protected $dirMode;
53
55 protected $fileOwner;
56
58 protected $isWindows;
60 protected $currentUser;
61
63 protected $hadWarningErrors = [];
64
75 public function __construct( array $config ) {
76 parent::__construct( $config );
77
78 $this->isWindows = ( strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN' );
79 // Remove any possible trailing slash from directories
80 if ( isset( $config['basePath'] ) ) {
81 $this->basePath = rtrim( $config['basePath'], '/' ); // remove trailing slash
82 } else {
83 $this->basePath = null; // none; containers must have explicit paths
84 }
85
86 if ( isset( $config['containerPaths'] ) ) {
87 $this->containerPaths = (array)$config['containerPaths'];
88 foreach ( $this->containerPaths as &$path ) {
89 $path = rtrim( $path, '/' ); // remove trailing slash
90 }
91 }
92
93 $this->fileMode = isset( $config['fileMode'] ) ? $config['fileMode'] : 0644;
94 $this->dirMode = isset( $config['directoryMode'] ) ? $config['directoryMode'] : 0777;
95 if ( isset( $config['fileOwner'] ) && function_exists( 'posix_getuid' ) ) {
96 $this->fileOwner = $config['fileOwner'];
97 // cache this, assuming it doesn't change
98 $this->currentUser = posix_getpwuid( posix_getuid() )['name'];
99 }
100 }
101
102 public function getFeatures() {
103 return !$this->isWindows ? FileBackend::ATTR_UNICODE_PATHS : 0;
104 }
105
106 protected function resolveContainerPath( $container, $relStoragePath ) {
107 // Check that container has a root directory
108 if ( isset( $this->containerPaths[$container] ) || isset( $this->basePath ) ) {
109 // Check for sane relative paths (assume the base paths are OK)
110 if ( $this->isLegalRelPath( $relStoragePath ) ) {
111 return $relStoragePath;
112 }
113 }
114
115 return null;
116 }
117
124 protected function isLegalRelPath( $path ) {
125 // Check for file names longer than 255 chars
126 if ( preg_match( '![^/]{256}!', $path ) ) { // ext3/NTFS
127 return false;
128 }
129 if ( $this->isWindows ) { // NTFS
130 return !preg_match( '![:*?"<>|]!', $path );
131 } else {
132 return true;
133 }
134 }
135
144 protected function containerFSRoot( $shortCont, $fullCont ) {
145 if ( isset( $this->containerPaths[$shortCont] ) ) {
146 return $this->containerPaths[$shortCont];
147 } elseif ( isset( $this->basePath ) ) {
148 return "{$this->basePath}/{$fullCont}";
149 }
150
151 return null; // no container base path defined
152 }
153
160 protected function resolveToFSPath( $storagePath ) {
161 list( $fullCont, $relPath ) = $this->resolveStoragePathReal( $storagePath );
162 if ( $relPath === null ) {
163 return null; // invalid
164 }
165 list( , $shortCont, ) = FileBackend::splitStoragePath( $storagePath );
166 $fsPath = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
167 if ( $relPath != '' ) {
168 $fsPath .= "/{$relPath}";
169 }
170
171 return $fsPath;
172 }
173
174 public function isPathUsableInternal( $storagePath ) {
175 $fsPath = $this->resolveToFSPath( $storagePath );
176 if ( $fsPath === null ) {
177 return false; // invalid
178 }
179 $parentDir = dirname( $fsPath );
180
181 if ( file_exists( $fsPath ) ) {
182 $ok = is_file( $fsPath ) && is_writable( $fsPath );
183 } else {
184 $ok = is_dir( $parentDir ) && is_writable( $parentDir );
185 }
186
187 if ( $this->fileOwner !== null && $this->currentUser !== $this->fileOwner ) {
188 $ok = false;
189 trigger_error( __METHOD__ . ": PHP process owner is not '{$this->fileOwner}'." );
190 }
191
192 return $ok;
193 }
194
195 protected function doCreateInternal( array $params ) {
196 $status = $this->newStatus();
197
198 $dest = $this->resolveToFSPath( $params['dst'] );
199 if ( $dest === null ) {
200 $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
201
202 return $status;
203 }
204
205 if ( !empty( $params['async'] ) ) { // deferred
206 $tempFile = TempFSFile::factory( 'create_', 'tmp', $this->tmpDirectory );
207 if ( !$tempFile ) {
208 $status->fatal( 'backend-fail-create', $params['dst'] );
209
210 return $status;
211 }
212 $this->trapWarnings();
213 $bytes = file_put_contents( $tempFile->getPath(), $params['content'] );
214 $this->untrapWarnings();
215 if ( $bytes === false ) {
216 $status->fatal( 'backend-fail-create', $params['dst'] );
217
218 return $status;
219 }
220 $cmd = implode( ' ', [
221 $this->isWindows ? 'COPY /B /Y' : 'cp', // (binary, overwrite)
222 escapeshellarg( $this->cleanPathSlashes( $tempFile->getPath() ) ),
223 escapeshellarg( $this->cleanPathSlashes( $dest ) )
224 ] );
225 $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
226 if ( $errors !== '' && !( $this->isWindows && $errors[0] === " " ) ) {
227 $status->fatal( 'backend-fail-create', $params['dst'] );
228 trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
229 }
230 };
231 $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd, $dest );
232 $tempFile->bind( $status->value );
233 } else { // immediate write
234 $this->trapWarnings();
235 $bytes = file_put_contents( $dest, $params['content'] );
236 $this->untrapWarnings();
237 if ( $bytes === false ) {
238 $status->fatal( 'backend-fail-create', $params['dst'] );
239
240 return $status;
241 }
242 $this->chmod( $dest );
243 }
244
245 return $status;
246 }
247
248 protected function doStoreInternal( array $params ) {
249 $status = $this->newStatus();
250
251 $dest = $this->resolveToFSPath( $params['dst'] );
252 if ( $dest === null ) {
253 $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
254
255 return $status;
256 }
257
258 if ( !empty( $params['async'] ) ) { // deferred
259 $cmd = implode( ' ', [
260 $this->isWindows ? 'COPY /B /Y' : 'cp', // (binary, overwrite)
261 escapeshellarg( $this->cleanPathSlashes( $params['src'] ) ),
262 escapeshellarg( $this->cleanPathSlashes( $dest ) )
263 ] );
264 $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
265 if ( $errors !== '' && !( $this->isWindows && $errors[0] === " " ) ) {
266 $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
267 trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
268 }
269 };
270 $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd, $dest );
271 } else { // immediate write
272 $this->trapWarnings();
273 $ok = copy( $params['src'], $dest );
274 $this->untrapWarnings();
275 // In some cases (at least over NFS), copy() returns true when it fails
276 if ( !$ok || ( filesize( $params['src'] ) !== filesize( $dest ) ) ) {
277 if ( $ok ) { // PHP bug
278 unlink( $dest ); // remove broken file
279 trigger_error( __METHOD__ . ": copy() failed but returned true." );
280 }
281 $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
282
283 return $status;
284 }
285 $this->chmod( $dest );
286 }
287
288 return $status;
289 }
290
291 protected function doCopyInternal( array $params ) {
292 $status = $this->newStatus();
293
294 $source = $this->resolveToFSPath( $params['src'] );
295 if ( $source === null ) {
296 $status->fatal( 'backend-fail-invalidpath', $params['src'] );
297
298 return $status;
299 }
300
301 $dest = $this->resolveToFSPath( $params['dst'] );
302 if ( $dest === null ) {
303 $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
304
305 return $status;
306 }
307
308 if ( !is_file( $source ) ) {
309 if ( empty( $params['ignoreMissingSource'] ) ) {
310 $status->fatal( 'backend-fail-copy', $params['src'] );
311 }
312
313 return $status; // do nothing; either OK or bad status
314 }
315
316 if ( !empty( $params['async'] ) ) { // deferred
317 $cmd = implode( ' ', [
318 $this->isWindows ? 'COPY /B /Y' : 'cp', // (binary, overwrite)
319 escapeshellarg( $this->cleanPathSlashes( $source ) ),
320 escapeshellarg( $this->cleanPathSlashes( $dest ) )
321 ] );
322 $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
323 if ( $errors !== '' && !( $this->isWindows && $errors[0] === " " ) ) {
324 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
325 trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
326 }
327 };
328 $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd, $dest );
329 } else { // immediate write
330 $this->trapWarnings();
331 $ok = ( $source === $dest ) ? true : copy( $source, $dest );
332 $this->untrapWarnings();
333 // In some cases (at least over NFS), copy() returns true when it fails
334 if ( !$ok || ( filesize( $source ) !== filesize( $dest ) ) ) {
335 if ( $ok ) { // PHP bug
336 $this->trapWarnings();
337 unlink( $dest ); // remove broken file
338 $this->untrapWarnings();
339 trigger_error( __METHOD__ . ": copy() failed but returned true." );
340 }
341 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
342
343 return $status;
344 }
345 $this->chmod( $dest );
346 }
347
348 return $status;
349 }
350
351 protected function doMoveInternal( array $params ) {
352 $status = $this->newStatus();
353
354 $source = $this->resolveToFSPath( $params['src'] );
355 if ( $source === null ) {
356 $status->fatal( 'backend-fail-invalidpath', $params['src'] );
357
358 return $status;
359 }
360
361 $dest = $this->resolveToFSPath( $params['dst'] );
362 if ( $dest === null ) {
363 $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
364
365 return $status;
366 }
367
368 if ( !is_file( $source ) ) {
369 if ( empty( $params['ignoreMissingSource'] ) ) {
370 $status->fatal( 'backend-fail-move', $params['src'] );
371 }
372
373 return $status; // do nothing; either OK or bad status
374 }
375
376 if ( !empty( $params['async'] ) ) { // deferred
377 $cmd = implode( ' ', [
378 $this->isWindows ? 'MOVE /Y' : 'mv', // (overwrite)
379 escapeshellarg( $this->cleanPathSlashes( $source ) ),
380 escapeshellarg( $this->cleanPathSlashes( $dest ) )
381 ] );
382 $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
383 if ( $errors !== '' && !( $this->isWindows && $errors[0] === " " ) ) {
384 $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
385 trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
386 }
387 };
388 $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd );
389 } else { // immediate write
390 $this->trapWarnings();
391 $ok = ( $source === $dest ) ? true : rename( $source, $dest );
392 $this->untrapWarnings();
393 clearstatcache(); // file no longer at source
394 if ( !$ok ) {
395 $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
396
397 return $status;
398 }
399 }
400
401 return $status;
402 }
403
404 protected function doDeleteInternal( array $params ) {
405 $status = $this->newStatus();
406
407 $source = $this->resolveToFSPath( $params['src'] );
408 if ( $source === null ) {
409 $status->fatal( 'backend-fail-invalidpath', $params['src'] );
410
411 return $status;
412 }
413
414 if ( !is_file( $source ) ) {
415 if ( empty( $params['ignoreMissingSource'] ) ) {
416 $status->fatal( 'backend-fail-delete', $params['src'] );
417 }
418
419 return $status; // do nothing; either OK or bad status
420 }
421
422 if ( !empty( $params['async'] ) ) { // deferred
423 $cmd = implode( ' ', [
424 $this->isWindows ? 'DEL' : 'unlink',
425 escapeshellarg( $this->cleanPathSlashes( $source ) )
426 ] );
427 $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
428 if ( $errors !== '' && !( $this->isWindows && $errors[0] === " " ) ) {
429 $status->fatal( 'backend-fail-delete', $params['src'] );
430 trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
431 }
432 };
433 $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd );
434 } else { // immediate write
435 $this->trapWarnings();
436 $ok = unlink( $source );
437 $this->untrapWarnings();
438 if ( !$ok ) {
439 $status->fatal( 'backend-fail-delete', $params['src'] );
440
441 return $status;
442 }
443 }
444
445 return $status;
446 }
447
454 protected function doPrepareInternal( $fullCont, $dirRel, array $params ) {
455 $status = $this->newStatus();
456 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
457 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
458 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
459 $existed = is_dir( $dir ); // already there?
460 // Create the directory and its parents as needed...
461 $this->trapWarnings();
462 if ( !$existed && !mkdir( $dir, $this->dirMode, true ) && !is_dir( $dir ) ) {
463 $this->logger->error( __METHOD__ . ": cannot create directory $dir" );
464 $status->fatal( 'directorycreateerror', $params['dir'] ); // fails on races
465 } elseif ( !is_writable( $dir ) ) {
466 $this->logger->error( __METHOD__ . ": directory $dir is read-only" );
467 $status->fatal( 'directoryreadonlyerror', $params['dir'] );
468 } elseif ( !is_readable( $dir ) ) {
469 $this->logger->error( __METHOD__ . ": directory $dir is not readable" );
470 $status->fatal( 'directorynotreadableerror', $params['dir'] );
471 }
472 $this->untrapWarnings();
473 // Respect any 'noAccess' or 'noListing' flags...
474 if ( is_dir( $dir ) && !$existed ) {
475 $status->merge( $this->doSecureInternal( $fullCont, $dirRel, $params ) );
476 }
477
478 return $status;
479 }
480
481 protected function doSecureInternal( $fullCont, $dirRel, array $params ) {
482 $status = $this->newStatus();
483 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
484 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
485 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
486 // Seed new directories with a blank index.html, to prevent crawling...
487 if ( !empty( $params['noListing'] ) && !file_exists( "{$dir}/index.html" ) ) {
488 $this->trapWarnings();
489 $bytes = file_put_contents( "{$dir}/index.html", $this->indexHtmlPrivate() );
490 $this->untrapWarnings();
491 if ( $bytes === false ) {
492 $status->fatal( 'backend-fail-create', $params['dir'] . '/index.html' );
493 }
494 }
495 // Add a .htaccess file to the root of the container...
496 if ( !empty( $params['noAccess'] ) && !file_exists( "{$contRoot}/.htaccess" ) ) {
497 $this->trapWarnings();
498 $bytes = file_put_contents( "{$contRoot}/.htaccess", $this->htaccessPrivate() );
499 $this->untrapWarnings();
500 if ( $bytes === false ) {
501 $storeDir = "mwstore://{$this->name}/{$shortCont}";
502 $status->fatal( 'backend-fail-create', "{$storeDir}/.htaccess" );
503 }
504 }
505
506 return $status;
507 }
508
509 protected function doPublishInternal( $fullCont, $dirRel, array $params ) {
510 $status = $this->newStatus();
511 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
512 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
513 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
514 // Unseed new directories with a blank index.html, to allow crawling...
515 if ( !empty( $params['listing'] ) && is_file( "{$dir}/index.html" ) ) {
516 $exists = ( file_get_contents( "{$dir}/index.html" ) === $this->indexHtmlPrivate() );
517 $this->trapWarnings();
518 if ( $exists && !unlink( "{$dir}/index.html" ) ) { // reverse secure()
519 $status->fatal( 'backend-fail-delete', $params['dir'] . '/index.html' );
520 }
521 $this->untrapWarnings();
522 }
523 // Remove the .htaccess file from the root of the container...
524 if ( !empty( $params['access'] ) && is_file( "{$contRoot}/.htaccess" ) ) {
525 $exists = ( file_get_contents( "{$contRoot}/.htaccess" ) === $this->htaccessPrivate() );
526 $this->trapWarnings();
527 if ( $exists && !unlink( "{$contRoot}/.htaccess" ) ) { // reverse secure()
528 $storeDir = "mwstore://{$this->name}/{$shortCont}";
529 $status->fatal( 'backend-fail-delete', "{$storeDir}/.htaccess" );
530 }
531 $this->untrapWarnings();
532 }
533
534 return $status;
535 }
536
537 protected function doCleanInternal( $fullCont, $dirRel, array $params ) {
538 $status = $this->newStatus();
539 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
540 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
541 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
542 $this->trapWarnings();
543 if ( is_dir( $dir ) ) {
544 rmdir( $dir ); // remove directory if empty
545 }
546 $this->untrapWarnings();
547
548 return $status;
549 }
550
551 protected function doGetFileStat( array $params ) {
552 $source = $this->resolveToFSPath( $params['src'] );
553 if ( $source === null ) {
554 return false; // invalid storage path
555 }
556
557 $this->trapWarnings(); // don't trust 'false' if there were errors
558 $stat = is_file( $source ) ? stat( $source ) : false; // regular files only
559 $hadError = $this->untrapWarnings();
560
561 if ( $stat ) {
562 $ct = new ConvertibleTimestamp( $stat['mtime'] );
563
564 return [
565 'mtime' => $ct->getTimestamp( TS_MW ),
566 'size' => $stat['size']
567 ];
568 } elseif ( !$hadError ) {
569 return false; // file does not exist
570 } else {
571 return null; // failure
572 }
573 }
574
575 protected function doClearCache( array $paths = null ) {
576 clearstatcache(); // clear the PHP file stat cache
577 }
578
579 protected function doDirectoryExists( $fullCont, $dirRel, array $params ) {
580 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
581 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
582 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
583
584 $this->trapWarnings(); // don't trust 'false' if there were errors
585 $exists = is_dir( $dir );
586 $hadError = $this->untrapWarnings();
587
588 return $hadError ? null : $exists;
589 }
590
598 public function getDirectoryListInternal( $fullCont, $dirRel, array $params ) {
599 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
600 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
601 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
602 $exists = is_dir( $dir );
603 if ( !$exists ) {
604 $this->logger->warning( __METHOD__ . "() given directory does not exist: '$dir'\n" );
605
606 return []; // nothing under this dir
607 } elseif ( !is_readable( $dir ) ) {
608 $this->logger->warning( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
609
610 return null; // bad permissions?
611 }
612
613 return new FSFileBackendDirList( $dir, $params );
614 }
615
623 public function getFileListInternal( $fullCont, $dirRel, array $params ) {
624 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
625 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
626 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
627 $exists = is_dir( $dir );
628 if ( !$exists ) {
629 $this->logger->warning( __METHOD__ . "() given directory does not exist: '$dir'\n" );
630
631 return []; // nothing under this dir
632 } elseif ( !is_readable( $dir ) ) {
633 $this->logger->warning( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
634
635 return null; // bad permissions?
636 }
637
638 return new FSFileBackendFileList( $dir, $params );
639 }
640
641 protected function doGetLocalReferenceMulti( array $params ) {
642 $fsFiles = []; // (path => FSFile)
643
644 foreach ( $params['srcs'] as $src ) {
645 $source = $this->resolveToFSPath( $src );
646 if ( $source === null || !is_file( $source ) ) {
647 $fsFiles[$src] = null; // invalid path or file does not exist
648 } else {
649 $fsFiles[$src] = new FSFile( $source );
650 }
651 }
652
653 return $fsFiles;
654 }
655
656 protected function doGetLocalCopyMulti( array $params ) {
657 $tmpFiles = []; // (path => TempFSFile)
658
659 foreach ( $params['srcs'] as $src ) {
660 $source = $this->resolveToFSPath( $src );
661 if ( $source === null ) {
662 $tmpFiles[$src] = null; // invalid path
663 } else {
664 // Create a new temporary file with the same extension...
666 $tmpFile = TempFSFile::factory( 'localcopy_', $ext, $this->tmpDirectory );
667 if ( !$tmpFile ) {
668 $tmpFiles[$src] = null;
669 } else {
670 $tmpPath = $tmpFile->getPath();
671 // Copy the source file over the temp file
672 $this->trapWarnings();
673 $ok = copy( $source, $tmpPath );
674 $this->untrapWarnings();
675 if ( !$ok ) {
676 $tmpFiles[$src] = null;
677 } else {
678 $this->chmod( $tmpPath );
679 $tmpFiles[$src] = $tmpFile;
680 }
681 }
682 }
683 }
684
685 return $tmpFiles;
686 }
687
688 protected function directoriesAreVirtual() {
689 return false;
690 }
691
697 protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
698 $statuses = [];
699
700 $pipes = [];
701 foreach ( $fileOpHandles as $index => $fileOpHandle ) {
702 $pipes[$index] = popen( "{$fileOpHandle->cmd} 2>&1", 'r' );
703 }
704
705 $errs = [];
706 foreach ( $pipes as $index => $pipe ) {
707 // Result will be empty on success in *NIX. On Windows,
708 // it may be something like " 1 file(s) [copied|moved].".
709 $errs[$index] = stream_get_contents( $pipe );
710 fclose( $pipe );
711 }
712
713 foreach ( $fileOpHandles as $index => $fileOpHandle ) {
714 $status = $this->newStatus();
715 $function = $fileOpHandle->call;
716 $function( $errs[$index], $status, $fileOpHandle->params, $fileOpHandle->cmd );
717 $statuses[$index] = $status;
718 if ( $status->isOK() && $fileOpHandle->chmodPath ) {
719 $this->chmod( $fileOpHandle->chmodPath );
720 }
721 }
722
723 clearstatcache(); // files changed
724 return $statuses;
725 }
726
733 protected function chmod( $path ) {
734 $this->trapWarnings();
735 $ok = chmod( $path, $this->fileMode );
736 $this->untrapWarnings();
737
738 return $ok;
739 }
740
746 protected function indexHtmlPrivate() {
747 return '';
748 }
749
755 protected function htaccessPrivate() {
756 return "Deny from all\n";
757 }
758
765 protected function cleanPathSlashes( $path ) {
766 return $this->isWindows ? strtr( $path, '/', '\\' ) : $path;
767 }
768
772 protected function trapWarnings() {
773 $this->hadWarningErrors[] = false; // push to stack
774 set_error_handler( [ $this, 'handleWarning' ], E_WARNING );
775 }
776
782 protected function untrapWarnings() {
783 restore_error_handler(); // restore previous handler
784 return array_pop( $this->hadWarningErrors ); // pop from stack
785 }
786
793 public function handleWarning( $errno, $errstr ) {
794 $this->logger->error( $errstr ); // more detailed error logging
795 $this->hadWarningErrors[count( $this->hadWarningErrors ) - 1] = true;
796
797 return true; // suppress from PHP handler
798 }
799}
800
805 public $cmd; // string; shell command
806 public $chmodPath; // string; file to chmod
807
815 public function __construct(
817 ) {
818 $this->backend = $backend;
819 $this->params = $params;
820 $this->call = $call;
821 $this->cmd = $cmd;
822 $this->chmodPath = $chmodPath;
823 }
824}
825
833abstract class FSFileBackendList implements Iterator {
835 protected $iter;
836
838 protected $suffixStart;
839
841 protected $pos = 0;
842
844 protected $params = [];
845
850 public function __construct( $dir, array $params ) {
851 $path = realpath( $dir ); // normalize
852 if ( $path === false ) {
853 $path = $dir;
854 }
855 $this->suffixStart = strlen( $path ) + 1; // size of "path/to/dir/"
856 $this->params = $params;
857
858 try {
859 $this->iter = $this->initIterator( $path );
860 } catch ( UnexpectedValueException $e ) {
861 $this->iter = null; // bad permissions? deleted?
862 }
863 }
864
871 protected function initIterator( $dir ) {
872 if ( !empty( $this->params['topOnly'] ) ) { // non-recursive
873 # Get an iterator that will get direct sub-nodes
874 return new DirectoryIterator( $dir );
875 } else { // recursive
876 # Get an iterator that will return leaf nodes (non-directories)
877 # RecursiveDirectoryIterator extends FilesystemIterator.
878 # FilesystemIterator::SKIP_DOTS default is inconsistent in PHP 5.3.x.
879 $flags = FilesystemIterator::CURRENT_AS_SELF | FilesystemIterator::SKIP_DOTS;
880
881 return new RecursiveIteratorIterator(
882 new RecursiveDirectoryIterator( $dir, $flags ),
883 RecursiveIteratorIterator::CHILD_FIRST // include dirs
884 );
885 }
886 }
887
892 public function key() {
893 return $this->pos;
894 }
895
900 public function current() {
901 return $this->getRelPath( $this->iter->current()->getPathname() );
902 }
903
908 public function next() {
909 try {
910 $this->iter->next();
911 $this->filterViaNext();
912 } catch ( UnexpectedValueException $e ) { // bad permissions? deleted?
913 throw new FileBackendError( "File iterator gave UnexpectedValueException." );
914 }
915 ++$this->pos;
916 }
917
922 public function rewind() {
923 $this->pos = 0;
924 try {
925 $this->iter->rewind();
926 $this->filterViaNext();
927 } catch ( UnexpectedValueException $e ) { // bad permissions? deleted?
928 throw new FileBackendError( "File iterator gave UnexpectedValueException." );
929 }
930 }
931
936 public function valid() {
937 return $this->iter && $this->iter->valid();
938 }
939
943 protected function filterViaNext() {
944 }
945
953 protected function getRelPath( $dir ) {
954 $path = realpath( $dir );
955 if ( $path === false ) {
956 $path = $dir;
957 }
958
959 return strtr( substr( $path, $this->suffixStart ), '\\', '/' );
960 }
961}
962
964 protected function filterViaNext() {
965 while ( $this->iter->valid() ) {
966 if ( $this->iter->current()->isDot() || !$this->iter->current()->isDir() ) {
967 $this->iter->next(); // skip non-directories and dot files
968 } else {
969 break;
970 }
971 }
972 }
973}
974
976 protected function filterViaNext() {
977 while ( $this->iter->valid() ) {
978 if ( !$this->iter->current()->isFile() ) {
979 $this->iter->next(); // skip non-files and dot files
980 } else {
981 break;
982 }
983 }
984 }
985}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
filterViaNext()
Filter out items by advancing to the next ones.
filterViaNext()
Filter out items by advancing to the next ones.
Wrapper around RecursiveDirectoryIterator/DirectoryIterator that catches exception or does any custom...
getRelPath( $dir)
Return only the relative path and normalize slashes to FileBackend-style.
__construct( $dir, array $params)
filterViaNext()
Filter out items by advancing to the next ones.
initIterator( $dir)
Return an appropriate iterator object to wrap.
Class for a file system (FS) based file backend.
doCopyInternal(array $params)
string $basePath
Directory holding the container directories.
doDirectoryExists( $fullCont, $dirRel, array $params)
__construct(array $config)
getDirectoryListInternal( $fullCont, $dirRel, array $params)
doDeleteInternal(array $params)
indexHtmlPrivate()
Return the text of an index.html file to hide directory listings.
chmod( $path)
Chmod a file, suppressing the warnings.
doExecuteOpHandlesInternal(array $fileOpHandles)
resolveToFSPath( $storagePath)
Get the absolute file system path for a storage path.
doGetFileStat(array $params)
htaccessPrivate()
Return the text of a .htaccess file to make a directory private.
doGetLocalCopyMulti(array $params)
isLegalRelPath( $path)
Sanity check a relative file system path for validity.
untrapWarnings()
Stop listening for E_WARNING errors and return true if any happened.
string $fileOwner
Required OS username to own files.
cleanPathSlashes( $path)
Clean up directory separators for the given OS.
doPublishInternal( $fullCont, $dirRel, array $params)
resolveContainerPath( $container, $relStoragePath)
Resolve a relative storage path, checking if it's allowed by the backend.
array $containerPaths
Map of container names to root paths for custom container paths.
doCleanInternal( $fullCont, $dirRel, array $params)
directoriesAreVirtual()
Is this a key/value store where directories are just virtual? Virtual directories exists in so much a...
doSecureInternal( $fullCont, $dirRel, array $params)
doMoveInternal(array $params)
array $hadWarningErrors
doClearCache(array $paths=null)
Clears any additional stat caches for storage paths.
doStoreInternal(array $params)
doPrepareInternal( $fullCont, $dirRel, array $params)
getFeatures()
Get the a bitfield of extra features supported by the backend medium.
containerFSRoot( $shortCont, $fullCont)
Given the short (unresolved) and full (resolved) name of a container, return the file system path of ...
int $fileMode
File permission mode.
isPathUsableInternal( $storagePath)
Check if a file can be created or changed at a given storage path.
doCreateInternal(array $params)
trapWarnings()
Listen for E_WARNING errors and track whether any happen.
doGetLocalReferenceMulti(array $params)
string $currentUser
OS username running this script.
handleWarning( $errno, $errstr)
int $dirMode
File permission mode.
getFileListInternal( $fullCont, $dirRel, array $params)
__construct(FSFileBackend $backend, array $params, $call, $cmd, $chmodPath=null)
Class representing a non-directory file on the file system.
Definition FSFile.php:29
File backend exception for checked exceptions (e.g.
FileBackendStore helper class for performing asynchronous file operations.
Base class for all backends using particular storage medium.
resolveStoragePathReal( $storagePath)
Like resolveStoragePath() except null values are returned if the container is sharded and the shard c...
const ATTR_UNICODE_PATHS
static splitStoragePath( $storagePath)
Split a storage path into a backend name, a container name, and a relative file path.
static extensionFromPath( $path, $case='lowercase')
Get the final extension from a storage or FS path.
newStatus()
Yields the result of the status wrapper callback on either:
Generic operation result class Has warning/error list, boolean status and arbitrary value.
static factory( $prefix, $extension='', $tmpDirectory=null)
Make a new temporary file on the file system.
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
the array() calling protocol came about after MediaWiki 1.4rc1.
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition hooks.txt:2753
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition hooks.txt:1049
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
Definition hooks.txt:903
returning false will NOT prevent logging $e
Definition hooks.txt:2127
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to copy
Definition LICENSE.txt:11
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
$source
$params