MediaWiki REL1_27
FSFileBackend.php
Go to the documentation of this file.
1<?php
43 protected $basePath;
44
46 protected $containerPaths = [];
47
49 protected $fileMode;
50
52 protected $fileOwner;
53
55 protected $currentUser;
56
58 protected $hadWarningErrors = [];
59
69 public function __construct( array $config ) {
70 parent::__construct( $config );
71
72 // Remove any possible trailing slash from directories
73 if ( isset( $config['basePath'] ) ) {
74 $this->basePath = rtrim( $config['basePath'], '/' ); // remove trailing slash
75 } else {
76 $this->basePath = null; // none; containers must have explicit paths
77 }
78
79 if ( isset( $config['containerPaths'] ) ) {
80 $this->containerPaths = (array)$config['containerPaths'];
81 foreach ( $this->containerPaths as &$path ) {
82 $path = rtrim( $path, '/' ); // remove trailing slash
83 }
84 }
85
86 $this->fileMode = isset( $config['fileMode'] ) ? $config['fileMode'] : 0644;
87 if ( isset( $config['fileOwner'] ) && function_exists( 'posix_getuid' ) ) {
88 $this->fileOwner = $config['fileOwner'];
89 // cache this, assuming it doesn't change
90 $this->currentUser = posix_getpwuid( posix_getuid() )['name'];
91 }
92 }
93
94 public function getFeatures() {
96 }
97
98 protected function resolveContainerPath( $container, $relStoragePath ) {
99 // Check that container has a root directory
100 if ( isset( $this->containerPaths[$container] ) || isset( $this->basePath ) ) {
101 // Check for sane relative paths (assume the base paths are OK)
102 if ( $this->isLegalRelPath( $relStoragePath ) ) {
103 return $relStoragePath;
104 }
105 }
106
107 return null;
108 }
109
116 protected function isLegalRelPath( $path ) {
117 // Check for file names longer than 255 chars
118 if ( preg_match( '![^/]{256}!', $path ) ) { // ext3/NTFS
119 return false;
120 }
121 if ( wfIsWindows() ) { // NTFS
122 return !preg_match( '![:*?"<>|]!', $path );
123 } else {
124 return true;
125 }
126 }
127
136 protected function containerFSRoot( $shortCont, $fullCont ) {
137 if ( isset( $this->containerPaths[$shortCont] ) ) {
138 return $this->containerPaths[$shortCont];
139 } elseif ( isset( $this->basePath ) ) {
140 return "{$this->basePath}/{$fullCont}";
141 }
142
143 return null; // no container base path defined
144 }
145
152 protected function resolveToFSPath( $storagePath ) {
153 list( $fullCont, $relPath ) = $this->resolveStoragePathReal( $storagePath );
154 if ( $relPath === null ) {
155 return null; // invalid
156 }
157 list( , $shortCont, ) = FileBackend::splitStoragePath( $storagePath );
158 $fsPath = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
159 if ( $relPath != '' ) {
160 $fsPath .= "/{$relPath}";
161 }
162
163 return $fsPath;
164 }
165
166 public function isPathUsableInternal( $storagePath ) {
167 $fsPath = $this->resolveToFSPath( $storagePath );
168 if ( $fsPath === null ) {
169 return false; // invalid
170 }
171 $parentDir = dirname( $fsPath );
172
173 if ( file_exists( $fsPath ) ) {
174 $ok = is_file( $fsPath ) && is_writable( $fsPath );
175 } else {
176 $ok = is_dir( $parentDir ) && is_writable( $parentDir );
177 }
178
179 if ( $this->fileOwner !== null && $this->currentUser !== $this->fileOwner ) {
180 $ok = false;
181 trigger_error( __METHOD__ . ": PHP process owner is not '{$this->fileOwner}'." );
182 }
183
184 return $ok;
185 }
186
187 protected function doCreateInternal( array $params ) {
188 $status = Status::newGood();
189
190 $dest = $this->resolveToFSPath( $params['dst'] );
191 if ( $dest === null ) {
192 $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
193
194 return $status;
195 }
196
197 if ( !empty( $params['async'] ) ) { // deferred
198 $tempFile = TempFSFile::factory( 'create_', 'tmp' );
199 if ( !$tempFile ) {
200 $status->fatal( 'backend-fail-create', $params['dst'] );
201
202 return $status;
203 }
204 $this->trapWarnings();
205 $bytes = file_put_contents( $tempFile->getPath(), $params['content'] );
206 $this->untrapWarnings();
207 if ( $bytes === false ) {
208 $status->fatal( 'backend-fail-create', $params['dst'] );
209
210 return $status;
211 }
212 $cmd = implode( ' ', [
213 wfIsWindows() ? 'COPY /B /Y' : 'cp', // (binary, overwrite)
214 wfEscapeShellArg( $this->cleanPathSlashes( $tempFile->getPath() ) ),
215 wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
216 ] );
217 $handler = function ( $errors, Status $status, array $params, $cmd ) {
218 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
219 $status->fatal( 'backend-fail-create', $params['dst'] );
220 trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
221 }
222 };
223 $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd, $dest );
224 $tempFile->bind( $status->value );
225 } else { // immediate write
226 $this->trapWarnings();
227 $bytes = file_put_contents( $dest, $params['content'] );
228 $this->untrapWarnings();
229 if ( $bytes === false ) {
230 $status->fatal( 'backend-fail-create', $params['dst'] );
231
232 return $status;
233 }
234 $this->chmod( $dest );
235 }
236
237 return $status;
238 }
239
240 protected function doStoreInternal( array $params ) {
241 $status = Status::newGood();
242
243 $dest = $this->resolveToFSPath( $params['dst'] );
244 if ( $dest === null ) {
245 $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
246
247 return $status;
248 }
249
250 if ( !empty( $params['async'] ) ) { // deferred
251 $cmd = implode( ' ', [
252 wfIsWindows() ? 'COPY /B /Y' : 'cp', // (binary, overwrite)
253 wfEscapeShellArg( $this->cleanPathSlashes( $params['src'] ) ),
254 wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
255 ] );
256 $handler = function ( $errors, Status $status, array $params, $cmd ) {
257 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
258 $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
259 trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
260 }
261 };
262 $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd, $dest );
263 } else { // immediate write
264 $this->trapWarnings();
265 $ok = copy( $params['src'], $dest );
266 $this->untrapWarnings();
267 // In some cases (at least over NFS), copy() returns true when it fails
268 if ( !$ok || ( filesize( $params['src'] ) !== filesize( $dest ) ) ) {
269 if ( $ok ) { // PHP bug
270 unlink( $dest ); // remove broken file
271 trigger_error( __METHOD__ . ": copy() failed but returned true." );
272 }
273 $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
274
275 return $status;
276 }
277 $this->chmod( $dest );
278 }
279
280 return $status;
281 }
282
283 protected function doCopyInternal( array $params ) {
284 $status = Status::newGood();
285
286 $source = $this->resolveToFSPath( $params['src'] );
287 if ( $source === null ) {
288 $status->fatal( 'backend-fail-invalidpath', $params['src'] );
289
290 return $status;
291 }
292
293 $dest = $this->resolveToFSPath( $params['dst'] );
294 if ( $dest === null ) {
295 $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
296
297 return $status;
298 }
299
300 if ( !is_file( $source ) ) {
301 if ( empty( $params['ignoreMissingSource'] ) ) {
302 $status->fatal( 'backend-fail-copy', $params['src'] );
303 }
304
305 return $status; // do nothing; either OK or bad status
306 }
307
308 if ( !empty( $params['async'] ) ) { // deferred
309 $cmd = implode( ' ', [
310 wfIsWindows() ? 'COPY /B /Y' : 'cp', // (binary, overwrite)
312 wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
313 ] );
314 $handler = function ( $errors, Status $status, array $params, $cmd ) {
315 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
316 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
317 trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
318 }
319 };
320 $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd, $dest );
321 } else { // immediate write
322 $this->trapWarnings();
323 $ok = ( $source === $dest ) ? true : copy( $source, $dest );
324 $this->untrapWarnings();
325 // In some cases (at least over NFS), copy() returns true when it fails
326 if ( !$ok || ( filesize( $source ) !== filesize( $dest ) ) ) {
327 if ( $ok ) { // PHP bug
328 $this->trapWarnings();
329 unlink( $dest ); // remove broken file
330 $this->untrapWarnings();
331 trigger_error( __METHOD__ . ": copy() failed but returned true." );
332 }
333 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
334
335 return $status;
336 }
337 $this->chmod( $dest );
338 }
339
340 return $status;
341 }
342
343 protected function doMoveInternal( array $params ) {
344 $status = Status::newGood();
345
346 $source = $this->resolveToFSPath( $params['src'] );
347 if ( $source === null ) {
348 $status->fatal( 'backend-fail-invalidpath', $params['src'] );
349
350 return $status;
351 }
352
353 $dest = $this->resolveToFSPath( $params['dst'] );
354 if ( $dest === null ) {
355 $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
356
357 return $status;
358 }
359
360 if ( !is_file( $source ) ) {
361 if ( empty( $params['ignoreMissingSource'] ) ) {
362 $status->fatal( 'backend-fail-move', $params['src'] );
363 }
364
365 return $status; // do nothing; either OK or bad status
366 }
367
368 if ( !empty( $params['async'] ) ) { // deferred
369 $cmd = implode( ' ', [
370 wfIsWindows() ? 'MOVE /Y' : 'mv', // (overwrite)
372 wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
373 ] );
374 $handler = function ( $errors, Status $status, array $params, $cmd ) {
375 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
376 $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
377 trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
378 }
379 };
380 $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd );
381 } else { // immediate write
382 $this->trapWarnings();
383 $ok = ( $source === $dest ) ? true : rename( $source, $dest );
384 $this->untrapWarnings();
385 clearstatcache(); // file no longer at source
386 if ( !$ok ) {
387 $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
388
389 return $status;
390 }
391 }
392
393 return $status;
394 }
395
396 protected function doDeleteInternal( array $params ) {
397 $status = Status::newGood();
398
399 $source = $this->resolveToFSPath( $params['src'] );
400 if ( $source === null ) {
401 $status->fatal( 'backend-fail-invalidpath', $params['src'] );
402
403 return $status;
404 }
405
406 if ( !is_file( $source ) ) {
407 if ( empty( $params['ignoreMissingSource'] ) ) {
408 $status->fatal( 'backend-fail-delete', $params['src'] );
409 }
410
411 return $status; // do nothing; either OK or bad status
412 }
413
414 if ( !empty( $params['async'] ) ) { // deferred
415 $cmd = implode( ' ', [
416 wfIsWindows() ? 'DEL' : 'unlink',
418 ] );
419 $handler = function ( $errors, Status $status, array $params, $cmd ) {
420 if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
421 $status->fatal( 'backend-fail-delete', $params['src'] );
422 trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
423 }
424 };
425 $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd );
426 } else { // immediate write
427 $this->trapWarnings();
428 $ok = unlink( $source );
429 $this->untrapWarnings();
430 if ( !$ok ) {
431 $status->fatal( 'backend-fail-delete', $params['src'] );
432
433 return $status;
434 }
435 }
436
437 return $status;
438 }
439
446 protected function doPrepareInternal( $fullCont, $dirRel, array $params ) {
447 $status = Status::newGood();
448 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
449 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
450 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
451 $existed = is_dir( $dir ); // already there?
452 // Create the directory and its parents as needed...
453 $this->trapWarnings();
454 if ( !wfMkdirParents( $dir ) ) {
455 wfDebugLog( 'FSFileBackend', __METHOD__ . ": cannot create directory $dir" );
456 $status->fatal( 'directorycreateerror', $params['dir'] ); // fails on races
457 } elseif ( !is_writable( $dir ) ) {
458 wfDebugLog( 'FSFileBackend', __METHOD__ . ": directory $dir is read-only" );
459 $status->fatal( 'directoryreadonlyerror', $params['dir'] );
460 } elseif ( !is_readable( $dir ) ) {
461 wfDebugLog( 'FSFileBackend', __METHOD__ . ": directory $dir is not readable" );
462 $status->fatal( 'directorynotreadableerror', $params['dir'] );
463 }
464 $this->untrapWarnings();
465 // Respect any 'noAccess' or 'noListing' flags...
466 if ( is_dir( $dir ) && !$existed ) {
467 $status->merge( $this->doSecureInternal( $fullCont, $dirRel, $params ) );
468 }
469
470 return $status;
471 }
472
473 protected function doSecureInternal( $fullCont, $dirRel, array $params ) {
474 $status = Status::newGood();
475 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
476 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
477 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
478 // Seed new directories with a blank index.html, to prevent crawling...
479 if ( !empty( $params['noListing'] ) && !file_exists( "{$dir}/index.html" ) ) {
480 $this->trapWarnings();
481 $bytes = file_put_contents( "{$dir}/index.html", $this->indexHtmlPrivate() );
482 $this->untrapWarnings();
483 if ( $bytes === false ) {
484 $status->fatal( 'backend-fail-create', $params['dir'] . '/index.html' );
485 }
486 }
487 // Add a .htaccess file to the root of the container...
488 if ( !empty( $params['noAccess'] ) && !file_exists( "{$contRoot}/.htaccess" ) ) {
489 $this->trapWarnings();
490 $bytes = file_put_contents( "{$contRoot}/.htaccess", $this->htaccessPrivate() );
491 $this->untrapWarnings();
492 if ( $bytes === false ) {
493 $storeDir = "mwstore://{$this->name}/{$shortCont}";
494 $status->fatal( 'backend-fail-create', "{$storeDir}/.htaccess" );
495 }
496 }
497
498 return $status;
499 }
500
501 protected function doPublishInternal( $fullCont, $dirRel, array $params ) {
502 $status = Status::newGood();
503 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
504 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
505 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
506 // Unseed new directories with a blank index.html, to allow crawling...
507 if ( !empty( $params['listing'] ) && is_file( "{$dir}/index.html" ) ) {
508 $exists = ( file_get_contents( "{$dir}/index.html" ) === $this->indexHtmlPrivate() );
509 $this->trapWarnings();
510 if ( $exists && !unlink( "{$dir}/index.html" ) ) { // reverse secure()
511 $status->fatal( 'backend-fail-delete', $params['dir'] . '/index.html' );
512 }
513 $this->untrapWarnings();
514 }
515 // Remove the .htaccess file from the root of the container...
516 if ( !empty( $params['access'] ) && is_file( "{$contRoot}/.htaccess" ) ) {
517 $exists = ( file_get_contents( "{$contRoot}/.htaccess" ) === $this->htaccessPrivate() );
518 $this->trapWarnings();
519 if ( $exists && !unlink( "{$contRoot}/.htaccess" ) ) { // reverse secure()
520 $storeDir = "mwstore://{$this->name}/{$shortCont}";
521 $status->fatal( 'backend-fail-delete', "{$storeDir}/.htaccess" );
522 }
523 $this->untrapWarnings();
524 }
525
526 return $status;
527 }
528
529 protected function doCleanInternal( $fullCont, $dirRel, array $params ) {
530 $status = Status::newGood();
531 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
532 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
533 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
534 $this->trapWarnings();
535 if ( is_dir( $dir ) ) {
536 rmdir( $dir ); // remove directory if empty
537 }
538 $this->untrapWarnings();
539
540 return $status;
541 }
542
543 protected function doGetFileStat( array $params ) {
544 $source = $this->resolveToFSPath( $params['src'] );
545 if ( $source === null ) {
546 return false; // invalid storage path
547 }
548
549 $this->trapWarnings(); // don't trust 'false' if there were errors
550 $stat = is_file( $source ) ? stat( $source ) : false; // regular files only
551 $hadError = $this->untrapWarnings();
552
553 if ( $stat ) {
554 return [
555 'mtime' => wfTimestamp( TS_MW, $stat['mtime'] ),
556 'size' => $stat['size']
557 ];
558 } elseif ( !$hadError ) {
559 return false; // file does not exist
560 } else {
561 return null; // failure
562 }
563 }
564
565 protected function doClearCache( array $paths = null ) {
566 clearstatcache(); // clear the PHP file stat cache
567 }
568
569 protected function doDirectoryExists( $fullCont, $dirRel, array $params ) {
570 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
571 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
572 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
573
574 $this->trapWarnings(); // don't trust 'false' if there were errors
575 $exists = is_dir( $dir );
576 $hadError = $this->untrapWarnings();
577
578 return $hadError ? null : $exists;
579 }
580
588 public function getDirectoryListInternal( $fullCont, $dirRel, array $params ) {
589 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
590 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
591 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
592 $exists = is_dir( $dir );
593 if ( !$exists ) {
594 wfDebug( __METHOD__ . "() given directory does not exist: '$dir'\n" );
595
596 return []; // nothing under this dir
597 } elseif ( !is_readable( $dir ) ) {
598 wfDebug( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
599
600 return null; // bad permissions?
601 }
602
603 return new FSFileBackendDirList( $dir, $params );
604 }
605
613 public function getFileListInternal( $fullCont, $dirRel, array $params ) {
614 list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
615 $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
616 $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
617 $exists = is_dir( $dir );
618 if ( !$exists ) {
619 wfDebug( __METHOD__ . "() given directory does not exist: '$dir'\n" );
620
621 return []; // nothing under this dir
622 } elseif ( !is_readable( $dir ) ) {
623 wfDebug( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
624
625 return null; // bad permissions?
626 }
627
628 return new FSFileBackendFileList( $dir, $params );
629 }
630
631 protected function doGetLocalReferenceMulti( array $params ) {
632 $fsFiles = []; // (path => FSFile)
633
634 foreach ( $params['srcs'] as $src ) {
635 $source = $this->resolveToFSPath( $src );
636 if ( $source === null || !is_file( $source ) ) {
637 $fsFiles[$src] = null; // invalid path or file does not exist
638 } else {
639 $fsFiles[$src] = new FSFile( $source );
640 }
641 }
642
643 return $fsFiles;
644 }
645
646 protected function doGetLocalCopyMulti( array $params ) {
647 $tmpFiles = []; // (path => TempFSFile)
648
649 foreach ( $params['srcs'] as $src ) {
650 $source = $this->resolveToFSPath( $src );
651 if ( $source === null ) {
652 $tmpFiles[$src] = null; // invalid path
653 } else {
654 // Create a new temporary file with the same extension...
656 $tmpFile = TempFSFile::factory( 'localcopy_', $ext );
657 if ( !$tmpFile ) {
658 $tmpFiles[$src] = null;
659 } else {
660 $tmpPath = $tmpFile->getPath();
661 // Copy the source file over the temp file
662 $this->trapWarnings();
663 $ok = copy( $source, $tmpPath );
664 $this->untrapWarnings();
665 if ( !$ok ) {
666 $tmpFiles[$src] = null;
667 } else {
668 $this->chmod( $tmpPath );
669 $tmpFiles[$src] = $tmpFile;
670 }
671 }
672 }
673 }
674
675 return $tmpFiles;
676 }
677
678 protected function directoriesAreVirtual() {
679 return false;
680 }
681
687 protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
688 $statuses = [];
689
690 $pipes = [];
691 foreach ( $fileOpHandles as $index => $fileOpHandle ) {
692 $pipes[$index] = popen( "{$fileOpHandle->cmd} 2>&1", 'r' );
693 }
694
695 $errs = [];
696 foreach ( $pipes as $index => $pipe ) {
697 // Result will be empty on success in *NIX. On Windows,
698 // it may be something like " 1 file(s) [copied|moved].".
699 $errs[$index] = stream_get_contents( $pipe );
700 fclose( $pipe );
701 }
702
703 foreach ( $fileOpHandles as $index => $fileOpHandle ) {
704 $status = Status::newGood();
705 $function = $fileOpHandle->call;
706 $function( $errs[$index], $status, $fileOpHandle->params, $fileOpHandle->cmd );
707 $statuses[$index] = $status;
708 if ( $status->isOK() && $fileOpHandle->chmodPath ) {
709 $this->chmod( $fileOpHandle->chmodPath );
710 }
711 }
712
713 clearstatcache(); // files changed
714 return $statuses;
715 }
716
723 protected function chmod( $path ) {
724 $this->trapWarnings();
725 $ok = chmod( $path, $this->fileMode );
726 $this->untrapWarnings();
727
728 return $ok;
729 }
730
736 protected function indexHtmlPrivate() {
737 return '';
738 }
739
745 protected function htaccessPrivate() {
746 return "Deny from all\n";
747 }
748
755 protected function cleanPathSlashes( $path ) {
756 return wfIsWindows() ? strtr( $path, '/', '\\' ) : $path;
757 }
758
762 protected function trapWarnings() {
763 $this->hadWarningErrors[] = false; // push to stack
764 set_error_handler( [ $this, 'handleWarning' ], E_WARNING );
765 }
766
772 protected function untrapWarnings() {
773 restore_error_handler(); // restore previous handler
774 return array_pop( $this->hadWarningErrors ); // pop from stack
775 }
776
783 public function handleWarning( $errno, $errstr ) {
784 wfDebugLog( 'FSFileBackend', $errstr ); // more detailed error logging
785 $this->hadWarningErrors[count( $this->hadWarningErrors ) - 1] = true;
786
787 return true; // suppress from PHP handler
788 }
789}
790
795 public $cmd; // string; shell command
796 public $chmodPath; // string; file to chmod
797
805 public function __construct(
807 ) {
808 $this->backend = $backend;
809 $this->params = $params;
810 $this->call = $call;
811 $this->cmd = $cmd;
812 $this->chmodPath = $chmodPath;
813 }
814}
815
823abstract class FSFileBackendList implements Iterator {
825 protected $iter;
826
828 protected $suffixStart;
829
831 protected $pos = 0;
832
834 protected $params = [];
835
840 public function __construct( $dir, array $params ) {
841 $path = realpath( $dir ); // normalize
842 if ( $path === false ) {
843 $path = $dir;
844 }
845 $this->suffixStart = strlen( $path ) + 1; // size of "path/to/dir/"
846 $this->params = $params;
847
848 try {
849 $this->iter = $this->initIterator( $path );
850 } catch ( UnexpectedValueException $e ) {
851 $this->iter = null; // bad permissions? deleted?
852 }
853 }
854
861 protected function initIterator( $dir ) {
862 if ( !empty( $this->params['topOnly'] ) ) { // non-recursive
863 # Get an iterator that will get direct sub-nodes
864 return new DirectoryIterator( $dir );
865 } else { // recursive
866 # Get an iterator that will return leaf nodes (non-directories)
867 # RecursiveDirectoryIterator extends FilesystemIterator.
868 # FilesystemIterator::SKIP_DOTS default is inconsistent in PHP 5.3.x.
869 $flags = FilesystemIterator::CURRENT_AS_SELF | FilesystemIterator::SKIP_DOTS;
870
871 return new RecursiveIteratorIterator(
872 new RecursiveDirectoryIterator( $dir, $flags ),
873 RecursiveIteratorIterator::CHILD_FIRST // include dirs
874 );
875 }
876 }
877
882 public function key() {
883 return $this->pos;
884 }
885
890 public function current() {
891 return $this->getRelPath( $this->iter->current()->getPathname() );
892 }
893
898 public function next() {
899 try {
900 $this->iter->next();
901 $this->filterViaNext();
902 } catch ( UnexpectedValueException $e ) { // bad permissions? deleted?
903 throw new FileBackendError( "File iterator gave UnexpectedValueException." );
904 }
905 ++$this->pos;
906 }
907
912 public function rewind() {
913 $this->pos = 0;
914 try {
915 $this->iter->rewind();
916 $this->filterViaNext();
917 } catch ( UnexpectedValueException $e ) { // bad permissions? deleted?
918 throw new FileBackendError( "File iterator gave UnexpectedValueException." );
919 }
920 }
921
926 public function valid() {
927 return $this->iter && $this->iter->valid();
928 }
929
933 protected function filterViaNext() {
934 }
935
943 protected function getRelPath( $dir ) {
944 $path = realpath( $dir );
945 if ( $path === false ) {
946 $path = $dir;
947 }
948
949 return strtr( substr( $path, $this->suffixStart ), '\\', '/' );
950 }
951}
952
954 protected function filterViaNext() {
955 while ( $this->iter->valid() ) {
956 if ( $this->iter->current()->isDot() || !$this->iter->current()->isDir() ) {
957 $this->iter->next(); // skip non-directories and dot files
958 } else {
959 break;
960 }
961 }
962 }
963}
964
966 protected function filterViaNext() {
967 while ( $this->iter->valid() ) {
968 if ( !$this->iter->current()->isFile() ) {
969 $this->iter->next(); // skip non-files and dot files
970 } else {
971 break;
972 }
973 }
974 }
975}
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfEscapeShellArg()
Windows-compatible version of escapeshellarg() Windows doesn't recognise single-quotes in the shell,...
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
wfIsWindows()
Check if the operating system is Windows.
wfMkdirParents( $dir, $mode=null, $caller=null)
Make directory, and make all parent directories if they don't exist.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
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)
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.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:40
static factory( $prefix, $extension='')
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
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist 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:1007
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:2555
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:885
returning false will NOT prevent logging $e
Definition hooks.txt:1940
if(count( $args)==0) $dir
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