MediaWiki  1.29.1
FSFileBackend.php
Go to the documentation of this file.
1 <?php
24 use 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 
833 abstract 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 }
FileBackend\splitStoragePath
static splitStoragePath( $storagePath)
Split a storage path into a backend name, a container name, and a relative file path.
Definition: FileBackend.php:1448
FSFileBackendList\initIterator
initIterator( $dir)
Return an appropriate iterator object to wrap.
Definition: FSFileBackend.php:871
FSFileBackend\__construct
__construct(array $config)
Definition: FSFileBackend.php:75
FSFileBackend\doCleanInternal
doCleanInternal( $fullCont, $dirRel, array $params)
Definition: FSFileBackend.php:537
FSFileOpHandle
Definition: FSFileBackend.php:804
FSFileBackendFileList\filterViaNext
filterViaNext()
Filter out items by advancing to the next ones.
Definition: FSFileBackend.php:976
StatusValue
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: StatusValue.php:42
FSFileBackend\doStoreInternal
doStoreInternal(array $params)
Definition: FSFileBackend.php:248
FSFileBackend\doClearCache
doClearCache(array $paths=null)
Clears any additional stat caches for storage paths.
Definition: FSFileBackend.php:575
FSFileBackend\doPublishInternal
doPublishInternal( $fullCont, $dirRel, array $params)
Definition: FSFileBackend.php:509
FSFileBackend\$currentUser
string $currentUser
OS username running this script.
Definition: FSFileBackend.php:60
captcha-old.count
count
Definition: captcha-old.py:225
FSFileBackend\doPrepareInternal
doPrepareInternal( $fullCont, $dirRel, array $params)
Definition: FSFileBackend.php:454
FSFileBackendList\current
current()
Definition: FSFileBackend.php:900
FileBackendError
File backend exception for checked exceptions (e.g.
Definition: FileBackendError.php:8
$status
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
use
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 use
Definition: MIT-LICENSE.txt:10
FileBackend\extensionFromPath
static extensionFromPath( $path, $case='lowercase')
Get the final extension from a storage or FS path.
Definition: FileBackend.php:1507
FSFileBackend\$dirMode
int $dirMode
File permission mode.
Definition: FSFileBackend.php:52
$params
$params
Definition: styleTest.css.php:40
FSFileBackend\$hadWarningErrors
array $hadWarningErrors
Definition: FSFileBackend.php:63
FSFileBackend\htaccessPrivate
htaccessPrivate()
Return the text of a .htaccess file to make a directory private.
Definition: FSFileBackend.php:755
FSFileBackend\doGetFileStat
doGetFileStat(array $params)
Definition: FSFileBackend.php:551
FSFileBackend\directoriesAreVirtual
directoriesAreVirtual()
Is this a key/value store where directories are just virtual? Virtual directories exists in so much a...
Definition: FSFileBackend.php:688
FSFileBackend\$fileOwner
string $fileOwner
Required OS username to own files.
Definition: FSFileBackend.php:55
php
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:35
FSFileBackend\doGetLocalReferenceMulti
doGetLocalReferenceMulti(array $params)
Definition: FSFileBackend.php:641
FSFileBackend\$basePath
string $basePath
Directory holding the container directories.
Definition: FSFileBackend.php:44
FSFileBackendDirList\filterViaNext
filterViaNext()
Filter out items by advancing to the next ones.
Definition: FSFileBackend.php:964
FSFileBackend\doDirectoryExists
doDirectoryExists( $fullCont, $dirRel, array $params)
Definition: FSFileBackend.php:579
FSFileBackend\getDirectoryListInternal
getDirectoryListInternal( $fullCont, $dirRel, array $params)
Definition: FSFileBackend.php:598
FSFileBackend\getFeatures
getFeatures()
Get the a bitfield of extra features supported by the backend medium.
Definition: FSFileBackend.php:102
FSFileBackendFileList
Definition: FSFileBackend.php:975
FSFileBackendList\getRelPath
getRelPath( $dir)
Return only the relative path and normalize slashes to FileBackend-style.
Definition: FSFileBackend.php:953
FSFileBackend\containerFSRoot
containerFSRoot( $shortCont, $fullCont)
Given the short (unresolved) and full (resolved) name of a container, return the file system path of ...
Definition: FSFileBackend.php:144
FileBackendStoreOpHandle\$backend
FileBackendStore $backend
Definition: FileBackendStore.php:1867
FSFileBackendDirList
Definition: FSFileBackend.php:963
FileBackend\ATTR_UNICODE_PATHS
const ATTR_UNICODE_PATHS
Definition: FileBackend.php:131
FSFileBackend\doSecureInternal
doSecureInternal( $fullCont, $dirRel, array $params)
Definition: FSFileBackend.php:481
FSFileBackendList
Wrapper around RecursiveDirectoryIterator/DirectoryIterator that catches exception or does any custom...
Definition: FSFileBackend.php:833
FSFileBackend\doExecuteOpHandlesInternal
doExecuteOpHandlesInternal(array $fileOpHandles)
Definition: FSFileBackend.php:697
list
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
$dir
$dir
Definition: Autoload.php:8
FSFileBackend\$containerPaths
array $containerPaths
Map of container names to root paths for custom container paths.
Definition: FSFileBackend.php:47
FSFileBackend\resolveToFSPath
resolveToFSPath( $storagePath)
Get the absolute file system path for a storage path.
Definition: FSFileBackend.php:160
FSFileBackendList\$iter
Iterator $iter
Definition: FSFileBackend.php:835
TempFSFile\factory
static factory( $prefix, $extension='', $tmpDirectory=null)
Make a new temporary file on the file system.
Definition: TempFSFile.php:55
FSFileBackend\doDeleteInternal
doDeleteInternal(array $params)
Definition: FSFileBackend.php:404
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2122
FSFileBackend\$fileMode
int $fileMode
File permission mode.
Definition: FSFileBackend.php:50
FSFileBackendList\valid
valid()
Definition: FSFileBackend.php:936
FSFileBackend\resolveContainerPath
resolveContainerPath( $container, $relStoragePath)
Resolve a relative storage path, checking if it's allowed by the backend.
Definition: FSFileBackend.php:106
FSFileBackendList\$pos
int $pos
Definition: FSFileBackend.php:841
FileBackendStore\resolveStoragePathReal
resolveStoragePathReal( $storagePath)
Like resolveStoragePath() except null values are returned if the container is sharded and the shard c...
Definition: FileBackendStore.php:1470
FileBackendStore
Base class for all backends using particular storage medium.
Definition: FileBackendStore.php:39
FileBackendStoreOpHandle
FileBackendStore helper class for performing asynchronous file operations.
Definition: FileBackendStore.php:1863
FSFile
Class representing a non-directory file on the file system.
Definition: FSFile.php:29
$handler
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:783
FSFileBackend\$isWindows
bool $isWindows
Definition: FSFileBackend.php:58
FSFileBackendList\next
next()
Definition: FSFileBackend.php:908
FSFileBackend
Class for a file system (FS) based file backend.
Definition: FSFileBackend.php:42
FSFileBackendList\key
key()
Definition: FSFileBackend.php:892
FSFileBackend\indexHtmlPrivate
indexHtmlPrivate()
Return the text of an index.html file to hide directory listings.
Definition: FSFileBackend.php:746
$ext
$ext
Definition: NoLocalSettings.php:25
FSFileBackend\cleanPathSlashes
cleanPathSlashes( $path)
Clean up directory separators for the given OS.
Definition: FSFileBackend.php:765
$path
$path
Definition: NoLocalSettings.php:26
FSFileOpHandle\__construct
__construct(FSFileBackend $backend, array $params, $call, $cmd, $chmodPath=null)
Definition: FSFileBackend.php:815
as
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
Definition: distributors.txt:9
FSFileBackend\doCreateInternal
doCreateInternal(array $params)
Definition: FSFileBackend.php:195
FSFileBackend\chmod
chmod( $path)
Chmod a file, suppressing the warnings.
Definition: FSFileBackend.php:733
FSFileBackendList\$suffixStart
int $suffixStart
Definition: FSFileBackend.php:838
FileBackendStoreOpHandle\$params
array $params
Definition: FileBackendStore.php:1865
$source
$source
Definition: mwdoc-filter.php:45
FSFileBackend\isLegalRelPath
isLegalRelPath( $path)
Sanity check a relative file system path for validity.
Definition: FSFileBackend.php:124
FSFileBackendList\rewind
rewind()
Definition: FSFileBackend.php:922
FSFileBackend\isPathUsableInternal
isPathUsableInternal( $storagePath)
Check if a file can be created or changed at a given storage path.
Definition: FSFileBackend.php:174
FileBackend\copy
copy(array $params, array $opts=[])
Performs a single copy operation.
Definition: FileBackend.php:503
FSFileBackend\handleWarning
handleWarning( $errno, $errstr)
Definition: FSFileBackend.php:793
FSFileBackend\untrapWarnings
untrapWarnings()
Stop listening for E_WARNING errors and return true if any happened.
Definition: FSFileBackend.php:782
FSFileBackend\doGetLocalCopyMulti
doGetLocalCopyMulti(array $params)
Definition: FSFileBackend.php:656
FSFileOpHandle\$cmd
$cmd
Definition: FSFileBackend.php:805
FSFileBackend\doCopyInternal
doCopyInternal(array $params)
Definition: FSFileBackend.php:291
FSFileBackendList\$params
array $params
Definition: FSFileBackend.php:844
FSFileBackend\getFileListInternal
getFileListInternal( $fullCont, $dirRel, array $params)
Definition: FSFileBackend.php:623
FSFileBackend\trapWarnings
trapWarnings()
Listen for E_WARNING errors and track whether any happen.
Definition: FSFileBackend.php:772
FileBackendStoreOpHandle\$call
$call
Definition: FileBackendStore.php:1871
$flags
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2749
FileBackend\newStatus
newStatus()
Yields the result of the status wrapper callback on either:
Definition: FileBackend.php:1598
array
the array() calling protocol came about after MediaWiki 1.4rc1.
FSFileBackendList\__construct
__construct( $dir, array $params)
Definition: FSFileBackend.php:850
FSFileBackend\doMoveInternal
doMoveInternal(array $params)
Definition: FSFileBackend.php:351
FSFileOpHandle\$chmodPath
$chmodPath
Definition: FSFileBackend.php:806
FSFileBackendList\filterViaNext
filterViaNext()
Filter out items by advancing to the next ones.
Definition: FSFileBackend.php:943