MediaWiki  1.28.0
FSFileBackend.php
Go to the documentation of this file.
1 <?php
43  protected $basePath;
44 
46  protected $containerPaths = [];
47 
49  protected $fileMode;
51  protected $dirMode;
52 
54  protected $fileOwner;
55 
57  protected $isWindows;
59  protected $currentUser;
60 
62  protected $hadWarningErrors = [];
63 
74  public function __construct( array $config ) {
75  parent::__construct( $config );
76 
77  $this->isWindows = ( strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN' );
78  // Remove any possible trailing slash from directories
79  if ( isset( $config['basePath'] ) ) {
80  $this->basePath = rtrim( $config['basePath'], '/' ); // remove trailing slash
81  } else {
82  $this->basePath = null; // none; containers must have explicit paths
83  }
84 
85  if ( isset( $config['containerPaths'] ) ) {
86  $this->containerPaths = (array)$config['containerPaths'];
87  foreach ( $this->containerPaths as &$path ) {
88  $path = rtrim( $path, '/' ); // remove trailing slash
89  }
90  }
91 
92  $this->fileMode = isset( $config['fileMode'] ) ? $config['fileMode'] : 0644;
93  $this->dirMode = isset( $config['directoryMode'] ) ? $config['directoryMode'] : 0777;
94  if ( isset( $config['fileOwner'] ) && function_exists( 'posix_getuid' ) ) {
95  $this->fileOwner = $config['fileOwner'];
96  // cache this, assuming it doesn't change
97  $this->currentUser = posix_getpwuid( posix_getuid() )['name'];
98  }
99  }
100 
101  public function getFeatures() {
102  return !$this->isWindows ? FileBackend::ATTR_UNICODE_PATHS : 0;
103  }
104 
105  protected function resolveContainerPath( $container, $relStoragePath ) {
106  // Check that container has a root directory
107  if ( isset( $this->containerPaths[$container] ) || isset( $this->basePath ) ) {
108  // Check for sane relative paths (assume the base paths are OK)
109  if ( $this->isLegalRelPath( $relStoragePath ) ) {
110  return $relStoragePath;
111  }
112  }
113 
114  return null;
115  }
116 
123  protected function isLegalRelPath( $path ) {
124  // Check for file names longer than 255 chars
125  if ( preg_match( '![^/]{256}!', $path ) ) { // ext3/NTFS
126  return false;
127  }
128  if ( $this->isWindows ) { // NTFS
129  return !preg_match( '![:*?"<>|]!', $path );
130  } else {
131  return true;
132  }
133  }
134 
143  protected function containerFSRoot( $shortCont, $fullCont ) {
144  if ( isset( $this->containerPaths[$shortCont] ) ) {
145  return $this->containerPaths[$shortCont];
146  } elseif ( isset( $this->basePath ) ) {
147  return "{$this->basePath}/{$fullCont}";
148  }
149 
150  return null; // no container base path defined
151  }
152 
159  protected function resolveToFSPath( $storagePath ) {
160  list( $fullCont, $relPath ) = $this->resolveStoragePathReal( $storagePath );
161  if ( $relPath === null ) {
162  return null; // invalid
163  }
164  list( , $shortCont, ) = FileBackend::splitStoragePath( $storagePath );
165  $fsPath = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
166  if ( $relPath != '' ) {
167  $fsPath .= "/{$relPath}";
168  }
169 
170  return $fsPath;
171  }
172 
173  public function isPathUsableInternal( $storagePath ) {
174  $fsPath = $this->resolveToFSPath( $storagePath );
175  if ( $fsPath === null ) {
176  return false; // invalid
177  }
178  $parentDir = dirname( $fsPath );
179 
180  if ( file_exists( $fsPath ) ) {
181  $ok = is_file( $fsPath ) && is_writable( $fsPath );
182  } else {
183  $ok = is_dir( $parentDir ) && is_writable( $parentDir );
184  }
185 
186  if ( $this->fileOwner !== null && $this->currentUser !== $this->fileOwner ) {
187  $ok = false;
188  trigger_error( __METHOD__ . ": PHP process owner is not '{$this->fileOwner}'." );
189  }
190 
191  return $ok;
192  }
193 
194  protected function doCreateInternal( array $params ) {
195  $status = $this->newStatus();
196 
197  $dest = $this->resolveToFSPath( $params['dst'] );
198  if ( $dest === null ) {
199  $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
200 
201  return $status;
202  }
203 
204  if ( !empty( $params['async'] ) ) { // deferred
205  $tempFile = TempFSFile::factory( 'create_', 'tmp', $this->tmpDirectory );
206  if ( !$tempFile ) {
207  $status->fatal( 'backend-fail-create', $params['dst'] );
208 
209  return $status;
210  }
211  $this->trapWarnings();
212  $bytes = file_put_contents( $tempFile->getPath(), $params['content'] );
213  $this->untrapWarnings();
214  if ( $bytes === false ) {
215  $status->fatal( 'backend-fail-create', $params['dst'] );
216 
217  return $status;
218  }
219  $cmd = implode( ' ', [
220  $this->isWindows ? 'COPY /B /Y' : 'cp', // (binary, overwrite)
221  escapeshellarg( $this->cleanPathSlashes( $tempFile->getPath() ) ),
222  escapeshellarg( $this->cleanPathSlashes( $dest ) )
223  ] );
224  $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
225  if ( $errors !== '' && !( $this->isWindows && $errors[0] === " " ) ) {
226  $status->fatal( 'backend-fail-create', $params['dst'] );
227  trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
228  }
229  };
230  $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd, $dest );
231  $tempFile->bind( $status->value );
232  } else { // immediate write
233  $this->trapWarnings();
234  $bytes = file_put_contents( $dest, $params['content'] );
235  $this->untrapWarnings();
236  if ( $bytes === false ) {
237  $status->fatal( 'backend-fail-create', $params['dst'] );
238 
239  return $status;
240  }
241  $this->chmod( $dest );
242  }
243 
244  return $status;
245  }
246 
247  protected function doStoreInternal( array $params ) {
248  $status = $this->newStatus();
249 
250  $dest = $this->resolveToFSPath( $params['dst'] );
251  if ( $dest === null ) {
252  $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
253 
254  return $status;
255  }
256 
257  if ( !empty( $params['async'] ) ) { // deferred
258  $cmd = implode( ' ', [
259  $this->isWindows ? 'COPY /B /Y' : 'cp', // (binary, overwrite)
260  escapeshellarg( $this->cleanPathSlashes( $params['src'] ) ),
261  escapeshellarg( $this->cleanPathSlashes( $dest ) )
262  ] );
263  $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
264  if ( $errors !== '' && !( $this->isWindows && $errors[0] === " " ) ) {
265  $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
266  trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
267  }
268  };
269  $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd, $dest );
270  } else { // immediate write
271  $this->trapWarnings();
272  $ok = copy( $params['src'], $dest );
273  $this->untrapWarnings();
274  // In some cases (at least over NFS), copy() returns true when it fails
275  if ( !$ok || ( filesize( $params['src'] ) !== filesize( $dest ) ) ) {
276  if ( $ok ) { // PHP bug
277  unlink( $dest ); // remove broken file
278  trigger_error( __METHOD__ . ": copy() failed but returned true." );
279  }
280  $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
281 
282  return $status;
283  }
284  $this->chmod( $dest );
285  }
286 
287  return $status;
288  }
289 
290  protected function doCopyInternal( array $params ) {
291  $status = $this->newStatus();
292 
293  $source = $this->resolveToFSPath( $params['src'] );
294  if ( $source === null ) {
295  $status->fatal( 'backend-fail-invalidpath', $params['src'] );
296 
297  return $status;
298  }
299 
300  $dest = $this->resolveToFSPath( $params['dst'] );
301  if ( $dest === null ) {
302  $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
303 
304  return $status;
305  }
306 
307  if ( !is_file( $source ) ) {
308  if ( empty( $params['ignoreMissingSource'] ) ) {
309  $status->fatal( 'backend-fail-copy', $params['src'] );
310  }
311 
312  return $status; // do nothing; either OK or bad status
313  }
314 
315  if ( !empty( $params['async'] ) ) { // deferred
316  $cmd = implode( ' ', [
317  $this->isWindows ? 'COPY /B /Y' : 'cp', // (binary, overwrite)
318  escapeshellarg( $this->cleanPathSlashes( $source ) ),
319  escapeshellarg( $this->cleanPathSlashes( $dest ) )
320  ] );
321  $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
322  if ( $errors !== '' && !( $this->isWindows && $errors[0] === " " ) ) {
323  $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
324  trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
325  }
326  };
327  $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd, $dest );
328  } else { // immediate write
329  $this->trapWarnings();
330  $ok = ( $source === $dest ) ? true : copy( $source, $dest );
331  $this->untrapWarnings();
332  // In some cases (at least over NFS), copy() returns true when it fails
333  if ( !$ok || ( filesize( $source ) !== filesize( $dest ) ) ) {
334  if ( $ok ) { // PHP bug
335  $this->trapWarnings();
336  unlink( $dest ); // remove broken file
337  $this->untrapWarnings();
338  trigger_error( __METHOD__ . ": copy() failed but returned true." );
339  }
340  $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
341 
342  return $status;
343  }
344  $this->chmod( $dest );
345  }
346 
347  return $status;
348  }
349 
350  protected function doMoveInternal( array $params ) {
351  $status = $this->newStatus();
352 
353  $source = $this->resolveToFSPath( $params['src'] );
354  if ( $source === null ) {
355  $status->fatal( 'backend-fail-invalidpath', $params['src'] );
356 
357  return $status;
358  }
359 
360  $dest = $this->resolveToFSPath( $params['dst'] );
361  if ( $dest === null ) {
362  $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
363 
364  return $status;
365  }
366 
367  if ( !is_file( $source ) ) {
368  if ( empty( $params['ignoreMissingSource'] ) ) {
369  $status->fatal( 'backend-fail-move', $params['src'] );
370  }
371 
372  return $status; // do nothing; either OK or bad status
373  }
374 
375  if ( !empty( $params['async'] ) ) { // deferred
376  $cmd = implode( ' ', [
377  $this->isWindows ? 'MOVE /Y' : 'mv', // (overwrite)
378  escapeshellarg( $this->cleanPathSlashes( $source ) ),
379  escapeshellarg( $this->cleanPathSlashes( $dest ) )
380  ] );
381  $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
382  if ( $errors !== '' && !( $this->isWindows && $errors[0] === " " ) ) {
383  $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
384  trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
385  }
386  };
387  $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd );
388  } else { // immediate write
389  $this->trapWarnings();
390  $ok = ( $source === $dest ) ? true : rename( $source, $dest );
391  $this->untrapWarnings();
392  clearstatcache(); // file no longer at source
393  if ( !$ok ) {
394  $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
395 
396  return $status;
397  }
398  }
399 
400  return $status;
401  }
402 
403  protected function doDeleteInternal( array $params ) {
404  $status = $this->newStatus();
405 
406  $source = $this->resolveToFSPath( $params['src'] );
407  if ( $source === null ) {
408  $status->fatal( 'backend-fail-invalidpath', $params['src'] );
409 
410  return $status;
411  }
412 
413  if ( !is_file( $source ) ) {
414  if ( empty( $params['ignoreMissingSource'] ) ) {
415  $status->fatal( 'backend-fail-delete', $params['src'] );
416  }
417 
418  return $status; // do nothing; either OK or bad status
419  }
420 
421  if ( !empty( $params['async'] ) ) { // deferred
422  $cmd = implode( ' ', [
423  $this->isWindows ? 'DEL' : 'unlink',
424  escapeshellarg( $this->cleanPathSlashes( $source ) )
425  ] );
426  $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
427  if ( $errors !== '' && !( $this->isWindows && $errors[0] === " " ) ) {
428  $status->fatal( 'backend-fail-delete', $params['src'] );
429  trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
430  }
431  };
432  $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd );
433  } else { // immediate write
434  $this->trapWarnings();
435  $ok = unlink( $source );
436  $this->untrapWarnings();
437  if ( !$ok ) {
438  $status->fatal( 'backend-fail-delete', $params['src'] );
439 
440  return $status;
441  }
442  }
443 
444  return $status;
445  }
446 
453  protected function doPrepareInternal( $fullCont, $dirRel, array $params ) {
454  $status = $this->newStatus();
455  list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
456  $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
457  $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
458  $existed = is_dir( $dir ); // already there?
459  // Create the directory and its parents as needed...
460  $this->trapWarnings();
461  if ( !$existed && !mkdir( $dir, $this->dirMode, true ) && !is_dir( $dir ) ) {
462  $this->logger->error( __METHOD__ . ": cannot create directory $dir" );
463  $status->fatal( 'directorycreateerror', $params['dir'] ); // fails on races
464  } elseif ( !is_writable( $dir ) ) {
465  $this->logger->error( __METHOD__ . ": directory $dir is read-only" );
466  $status->fatal( 'directoryreadonlyerror', $params['dir'] );
467  } elseif ( !is_readable( $dir ) ) {
468  $this->logger->error( __METHOD__ . ": directory $dir is not readable" );
469  $status->fatal( 'directorynotreadableerror', $params['dir'] );
470  }
471  $this->untrapWarnings();
472  // Respect any 'noAccess' or 'noListing' flags...
473  if ( is_dir( $dir ) && !$existed ) {
474  $status->merge( $this->doSecureInternal( $fullCont, $dirRel, $params ) );
475  }
476 
477  return $status;
478  }
479 
480  protected function doSecureInternal( $fullCont, $dirRel, array $params ) {
481  $status = $this->newStatus();
482  list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
483  $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
484  $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
485  // Seed new directories with a blank index.html, to prevent crawling...
486  if ( !empty( $params['noListing'] ) && !file_exists( "{$dir}/index.html" ) ) {
487  $this->trapWarnings();
488  $bytes = file_put_contents( "{$dir}/index.html", $this->indexHtmlPrivate() );
489  $this->untrapWarnings();
490  if ( $bytes === false ) {
491  $status->fatal( 'backend-fail-create', $params['dir'] . '/index.html' );
492  }
493  }
494  // Add a .htaccess file to the root of the container...
495  if ( !empty( $params['noAccess'] ) && !file_exists( "{$contRoot}/.htaccess" ) ) {
496  $this->trapWarnings();
497  $bytes = file_put_contents( "{$contRoot}/.htaccess", $this->htaccessPrivate() );
498  $this->untrapWarnings();
499  if ( $bytes === false ) {
500  $storeDir = "mwstore://{$this->name}/{$shortCont}";
501  $status->fatal( 'backend-fail-create', "{$storeDir}/.htaccess" );
502  }
503  }
504 
505  return $status;
506  }
507 
508  protected function doPublishInternal( $fullCont, $dirRel, array $params ) {
509  $status = $this->newStatus();
510  list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
511  $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
512  $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
513  // Unseed new directories with a blank index.html, to allow crawling...
514  if ( !empty( $params['listing'] ) && is_file( "{$dir}/index.html" ) ) {
515  $exists = ( file_get_contents( "{$dir}/index.html" ) === $this->indexHtmlPrivate() );
516  $this->trapWarnings();
517  if ( $exists && !unlink( "{$dir}/index.html" ) ) { // reverse secure()
518  $status->fatal( 'backend-fail-delete', $params['dir'] . '/index.html' );
519  }
520  $this->untrapWarnings();
521  }
522  // Remove the .htaccess file from the root of the container...
523  if ( !empty( $params['access'] ) && is_file( "{$contRoot}/.htaccess" ) ) {
524  $exists = ( file_get_contents( "{$contRoot}/.htaccess" ) === $this->htaccessPrivate() );
525  $this->trapWarnings();
526  if ( $exists && !unlink( "{$contRoot}/.htaccess" ) ) { // reverse secure()
527  $storeDir = "mwstore://{$this->name}/{$shortCont}";
528  $status->fatal( 'backend-fail-delete', "{$storeDir}/.htaccess" );
529  }
530  $this->untrapWarnings();
531  }
532 
533  return $status;
534  }
535 
536  protected function doCleanInternal( $fullCont, $dirRel, array $params ) {
537  $status = $this->newStatus();
538  list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
539  $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
540  $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
541  $this->trapWarnings();
542  if ( is_dir( $dir ) ) {
543  rmdir( $dir ); // remove directory if empty
544  }
545  $this->untrapWarnings();
546 
547  return $status;
548  }
549 
550  protected function doGetFileStat( array $params ) {
551  $source = $this->resolveToFSPath( $params['src'] );
552  if ( $source === null ) {
553  return false; // invalid storage path
554  }
555 
556  $this->trapWarnings(); // don't trust 'false' if there were errors
557  $stat = is_file( $source ) ? stat( $source ) : false; // regular files only
558  $hadError = $this->untrapWarnings();
559 
560  if ( $stat ) {
561  $ct = new ConvertibleTimestamp( $stat['mtime'] );
562 
563  return [
564  'mtime' => $ct->getTimestamp( TS_MW ),
565  'size' => $stat['size']
566  ];
567  } elseif ( !$hadError ) {
568  return false; // file does not exist
569  } else {
570  return null; // failure
571  }
572  }
573 
574  protected function doClearCache( array $paths = null ) {
575  clearstatcache(); // clear the PHP file stat cache
576  }
577 
578  protected function doDirectoryExists( $fullCont, $dirRel, array $params ) {
579  list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
580  $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
581  $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
582 
583  $this->trapWarnings(); // don't trust 'false' if there were errors
584  $exists = is_dir( $dir );
585  $hadError = $this->untrapWarnings();
586 
587  return $hadError ? null : $exists;
588  }
589 
597  public function getDirectoryListInternal( $fullCont, $dirRel, array $params ) {
598  list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
599  $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
600  $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
601  $exists = is_dir( $dir );
602  if ( !$exists ) {
603  $this->logger->warning( __METHOD__ . "() given directory does not exist: '$dir'\n" );
604 
605  return []; // nothing under this dir
606  } elseif ( !is_readable( $dir ) ) {
607  $this->logger->warning( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
608 
609  return null; // bad permissions?
610  }
611 
612  return new FSFileBackendDirList( $dir, $params );
613  }
614 
622  public function getFileListInternal( $fullCont, $dirRel, array $params ) {
623  list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
624  $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
625  $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
626  $exists = is_dir( $dir );
627  if ( !$exists ) {
628  $this->logger->warning( __METHOD__ . "() given directory does not exist: '$dir'\n" );
629 
630  return []; // nothing under this dir
631  } elseif ( !is_readable( $dir ) ) {
632  $this->logger->warning( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
633 
634  return null; // bad permissions?
635  }
636 
637  return new FSFileBackendFileList( $dir, $params );
638  }
639 
640  protected function doGetLocalReferenceMulti( array $params ) {
641  $fsFiles = []; // (path => FSFile)
642 
643  foreach ( $params['srcs'] as $src ) {
644  $source = $this->resolveToFSPath( $src );
645  if ( $source === null || !is_file( $source ) ) {
646  $fsFiles[$src] = null; // invalid path or file does not exist
647  } else {
648  $fsFiles[$src] = new FSFile( $source );
649  }
650  }
651 
652  return $fsFiles;
653  }
654 
655  protected function doGetLocalCopyMulti( array $params ) {
656  $tmpFiles = []; // (path => TempFSFile)
657 
658  foreach ( $params['srcs'] as $src ) {
659  $source = $this->resolveToFSPath( $src );
660  if ( $source === null ) {
661  $tmpFiles[$src] = null; // invalid path
662  } else {
663  // Create a new temporary file with the same extension...
665  $tmpFile = TempFSFile::factory( 'localcopy_', $ext, $this->tmpDirectory );
666  if ( !$tmpFile ) {
667  $tmpFiles[$src] = null;
668  } else {
669  $tmpPath = $tmpFile->getPath();
670  // Copy the source file over the temp file
671  $this->trapWarnings();
672  $ok = copy( $source, $tmpPath );
673  $this->untrapWarnings();
674  if ( !$ok ) {
675  $tmpFiles[$src] = null;
676  } else {
677  $this->chmod( $tmpPath );
678  $tmpFiles[$src] = $tmpFile;
679  }
680  }
681  }
682  }
683 
684  return $tmpFiles;
685  }
686 
687  protected function directoriesAreVirtual() {
688  return false;
689  }
690 
696  protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
697  $statuses = [];
698 
699  $pipes = [];
700  foreach ( $fileOpHandles as $index => $fileOpHandle ) {
701  $pipes[$index] = popen( "{$fileOpHandle->cmd} 2>&1", 'r' );
702  }
703 
704  $errs = [];
705  foreach ( $pipes as $index => $pipe ) {
706  // Result will be empty on success in *NIX. On Windows,
707  // it may be something like " 1 file(s) [copied|moved].".
708  $errs[$index] = stream_get_contents( $pipe );
709  fclose( $pipe );
710  }
711 
712  foreach ( $fileOpHandles as $index => $fileOpHandle ) {
713  $status = $this->newStatus();
714  $function = $fileOpHandle->call;
715  $function( $errs[$index], $status, $fileOpHandle->params, $fileOpHandle->cmd );
716  $statuses[$index] = $status;
717  if ( $status->isOK() && $fileOpHandle->chmodPath ) {
718  $this->chmod( $fileOpHandle->chmodPath );
719  }
720  }
721 
722  clearstatcache(); // files changed
723  return $statuses;
724  }
725 
732  protected function chmod( $path ) {
733  $this->trapWarnings();
734  $ok = chmod( $path, $this->fileMode );
735  $this->untrapWarnings();
736 
737  return $ok;
738  }
739 
745  protected function indexHtmlPrivate() {
746  return '';
747  }
748 
754  protected function htaccessPrivate() {
755  return "Deny from all\n";
756  }
757 
764  protected function cleanPathSlashes( $path ) {
765  return $this->isWindows ? strtr( $path, '/', '\\' ) : $path;
766  }
767 
771  protected function trapWarnings() {
772  $this->hadWarningErrors[] = false; // push to stack
773  set_error_handler( [ $this, 'handleWarning' ], E_WARNING );
774  }
775 
781  protected function untrapWarnings() {
782  restore_error_handler(); // restore previous handler
783  return array_pop( $this->hadWarningErrors ); // pop from stack
784  }
785 
792  public function handleWarning( $errno, $errstr ) {
793  $this->logger->error( $errstr ); // more detailed error logging
794  $this->hadWarningErrors[count( $this->hadWarningErrors ) - 1] = true;
795 
796  return true; // suppress from PHP handler
797  }
798 }
799 
804  public $cmd; // string; shell command
805  public $chmodPath; // string; file to chmod
806 
814  public function __construct(
816  ) {
817  $this->backend = $backend;
818  $this->params = $params;
819  $this->call = $call;
820  $this->cmd = $cmd;
821  $this->chmodPath = $chmodPath;
822  }
823 }
824 
832 abstract class FSFileBackendList implements Iterator {
834  protected $iter;
835 
837  protected $suffixStart;
838 
840  protected $pos = 0;
841 
843  protected $params = [];
844 
849  public function __construct( $dir, array $params ) {
850  $path = realpath( $dir ); // normalize
851  if ( $path === false ) {
852  $path = $dir;
853  }
854  $this->suffixStart = strlen( $path ) + 1; // size of "path/to/dir/"
855  $this->params = $params;
856 
857  try {
858  $this->iter = $this->initIterator( $path );
859  } catch ( UnexpectedValueException $e ) {
860  $this->iter = null; // bad permissions? deleted?
861  }
862  }
863 
870  protected function initIterator( $dir ) {
871  if ( !empty( $this->params['topOnly'] ) ) { // non-recursive
872  # Get an iterator that will get direct sub-nodes
873  return new DirectoryIterator( $dir );
874  } else { // recursive
875  # Get an iterator that will return leaf nodes (non-directories)
876  # RecursiveDirectoryIterator extends FilesystemIterator.
877  # FilesystemIterator::SKIP_DOTS default is inconsistent in PHP 5.3.x.
878  $flags = FilesystemIterator::CURRENT_AS_SELF | FilesystemIterator::SKIP_DOTS;
879 
880  return new RecursiveIteratorIterator(
881  new RecursiveDirectoryIterator( $dir, $flags ),
882  RecursiveIteratorIterator::CHILD_FIRST // include dirs
883  );
884  }
885  }
886 
891  public function key() {
892  return $this->pos;
893  }
894 
899  public function current() {
900  return $this->getRelPath( $this->iter->current()->getPathname() );
901  }
902 
907  public function next() {
908  try {
909  $this->iter->next();
910  $this->filterViaNext();
911  } catch ( UnexpectedValueException $e ) { // bad permissions? deleted?
912  throw new FileBackendError( "File iterator gave UnexpectedValueException." );
913  }
914  ++$this->pos;
915  }
916 
921  public function rewind() {
922  $this->pos = 0;
923  try {
924  $this->iter->rewind();
925  $this->filterViaNext();
926  } catch ( UnexpectedValueException $e ) { // bad permissions? deleted?
927  throw new FileBackendError( "File iterator gave UnexpectedValueException." );
928  }
929  }
930 
935  public function valid() {
936  return $this->iter && $this->iter->valid();
937  }
938 
942  protected function filterViaNext() {
943  }
944 
952  protected function getRelPath( $dir ) {
953  $path = realpath( $dir );
954  if ( $path === false ) {
955  $path = $dir;
956  }
957 
958  return strtr( substr( $path, $this->suffixStart ), '\\', '/' );
959  }
960 }
961 
963  protected function filterViaNext() {
964  while ( $this->iter->valid() ) {
965  if ( $this->iter->current()->isDot() || !$this->iter->current()->isDir() ) {
966  $this->iter->next(); // skip non-directories and dot files
967  } else {
968  break;
969  }
970  }
971  }
972 }
973 
975  protected function filterViaNext() {
976  while ( $this->iter->valid() ) {
977  if ( !$this->iter->current()->isFile() ) {
978  $this->iter->next(); // skip non-files and dot files
979  } else {
980  break;
981  }
982  }
983  }
984 }
newStatus()
Yields the result of the status wrapper callback on either:
Library for creating, parsing, and converting timestamps.
untrapWarnings()
Stop listening for E_WARNING errors and return true if any happened.
containerFSRoot($shortCont, $fullCont)
Given the short (unresolved) and full (resolved) name of a container, return the file system path of ...
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
the array() calling protocol came about after MediaWiki 1.4rc1.
if(count($args)==0) $dir
doDeleteInternal(array $params)
int $fileMode
File permission mode.
doMoveInternal(array $params)
doGetFileStat(array $params)
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException'returning false will NOT prevent logging $e
Definition: hooks.txt:2102
filterViaNext()
Filter out items by advancing to the next ones.
resolveToFSPath($storagePath)
Get the absolute file system path for a storage path.
resolveContainerPath($container, $relStoragePath)
$source
doExecuteOpHandlesInternal(array $fileOpHandles)
doGetLocalCopyMulti(array $params)
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2703
array $containerPaths
Map of container names to root paths for custom container paths.
__construct($dir, array $params)
static extensionFromPath($path, $case= 'lowercase')
Get the final extension from a storage or FS path.
static factory($prefix, $extension= '', $tmpDirectory=null)
Make a new temporary file on the file system.
Definition: TempFSFile.php:55
__construct(array $config)
chmod($path)
Chmod a file, suppressing the warnings.
isPathUsableInternal($storagePath)
Wrapper around RecursiveDirectoryIterator/DirectoryIterator that catches exception or does any custom...
File backend exception for checked exceptions (e.g.
indexHtmlPrivate()
Return the text of an index.html file to hide directory listings.
cleanPathSlashes($path)
Clean up directory separators for the given OS.
$params
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
Definition: defines.php:11
const ATTR_UNICODE_PATHS
doStoreInternal(array $params)
htaccessPrivate()
Return the text of a .htaccess file to make a directory private.
getFileListInternal($fullCont, $dirRel, array $params)
resolveStoragePathReal($storagePath)
Like resolveStoragePath() except null values are returned if the container is sharded and the shard c...
doDirectoryExists($fullCont, $dirRel, array $params)
initIterator($dir)
Return an appropriate iterator object to wrap.
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
doCopyInternal(array $params)
Base class for all backends using particular storage medium.
trapWarnings()
Listen for E_WARNING errors and track whether any happen.
doPrepareInternal($fullCont, $dirRel, array $params)
__construct(FSFileBackend $backend, array $params, $call, $cmd, $chmodPath=null)
string $basePath
Directory holding the container directories.
doPublishInternal($fullCont, $dirRel, array $params)
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
isLegalRelPath($path)
Sanity check a relative file system path for validity.
doCreateInternal(array $params)
Class representing a non-directory file on the file system.
Definition: FSFile.php:29
int $dirMode
File permission mode.
copy(array $params, array $opts=[])
Performs a single copy operation.
getRelPath($dir)
Return only the relative path and normalize slashes to FileBackend-style.
string $currentUser
OS username running this script.
handleWarning($errno, $errstr)
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:1046
doSecureInternal($fullCont, $dirRel, array $params)
doGetLocalReferenceMulti(array $params)
getDirectoryListInternal($fullCont, $dirRel, array $params)
static splitStoragePath($storagePath)
Split a storage path into a backend name, a container name, and a relative file path.
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:802
Class for a file system (FS) based file backend.
FileBackendStore helper class for performing asynchronous file operations.
array $hadWarningErrors
doClearCache(array $paths=null)
string $fileOwner
Required OS username to own files.
doCleanInternal($fullCont, $dirRel, array $params)