[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/filebackend/ -> FileBackend.php (source)

   1  <?php
   2  /**
   3   * @defgroup FileBackend File backend
   4   *
   5   * File backend is used to interact with file storage systems,
   6   * such as the local file system, NFS, or cloud storage systems.
   7   */
   8  
   9  /**
  10   * Base class for all file backends.
  11   *
  12   * This program is free software; you can redistribute it and/or modify
  13   * it under the terms of the GNU General Public License as published by
  14   * the Free Software Foundation; either version 2 of the License, or
  15   * (at your option) any later version.
  16   *
  17   * This program is distributed in the hope that it will be useful,
  18   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20   * GNU General Public License for more details.
  21   *
  22   * You should have received a copy of the GNU General Public License along
  23   * with this program; if not, write to the Free Software Foundation, Inc.,
  24   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  25   * http://www.gnu.org/copyleft/gpl.html
  26   *
  27   * @file
  28   * @ingroup FileBackend
  29   * @author Aaron Schulz
  30   */
  31  
  32  /**
  33   * @brief Base class for all file backend classes (including multi-write backends).
  34   *
  35   * This class defines the methods as abstract that subclasses must implement.
  36   * Outside callers can assume that all backends will have these functions.
  37   *
  38   * All "storage paths" are of the format "mwstore://<backend>/<container>/<path>".
  39   * The "backend" portion is unique name for MediaWiki to refer to a backend, while
  40   * the "container" portion is a top-level directory of the backend. The "path" portion
  41   * is a relative path that uses UNIX file system (FS) notation, though any particular
  42   * backend may not actually be using a local filesystem. Therefore, the relative paths
  43   * are only virtual.
  44   *
  45   * Backend contents are stored under wiki-specific container names by default.
  46   * Global (qualified) backends are achieved by configuring the "wiki ID" to a constant.
  47   * For legacy reasons, the FSFileBackend class allows manually setting the paths of
  48   * containers to ones that do not respect the "wiki ID".
  49   *
  50   * In key/value (object) stores, containers are the only hierarchy (the rest is emulated).
  51   * FS-based backends are somewhat more restrictive due to the existence of real
  52   * directory files; a regular file cannot have the same name as a directory. Other
  53   * backends with virtual directories may not have this limitation. Callers should
  54   * store files in such a way that no files and directories are under the same path.
  55   *
  56   * In general, this class allows for callers to access storage through the same
  57   * interface, without regard to the underlying storage system. However, calling code
  58   * must follow certain patterns and be aware of certain things to ensure compatibility:
  59   *   - a) Always call prepare() on the parent directory before trying to put a file there;
  60   *        key/value stores only need the container to exist first, but filesystems need
  61   *        all the parent directories to exist first (prepare() is aware of all this)
  62   *   - b) Always call clean() on a directory when it might become empty to avoid empty
  63   *        directory buildup on filesystems; key/value stores never have empty directories,
  64   *        so doing this helps preserve consistency in both cases
  65   *   - c) Likewise, do not rely on the existence of empty directories for anything;
  66   *        calling directoryExists() on a path that prepare() was previously called on
  67   *        will return false for key/value stores if there are no files under that path
  68   *   - d) Never alter the resulting FSFile returned from getLocalReference(), as it could
  69   *        either be a copy of the source file in /tmp or the original source file itself
  70   *   - e) Use a file layout that results in never attempting to store files over directories
  71   *        or directories over files; key/value stores allow this but filesystems do not
  72   *   - f) Use ASCII file names (e.g. base32, IDs, hashes) to avoid Unicode issues in Windows
  73   *   - g) Do not assume that move operations are atomic (difficult with key/value stores)
  74   *   - h) Do not assume that file stat or read operations always have immediate consistency;
  75   *        various methods have a "latest" flag that should always be used if up-to-date
  76   *        information is required (this trades performance for correctness as needed)
  77   *   - i) Do not assume that directory listings have immediate consistency
  78   *
  79   * Methods of subclasses should avoid throwing exceptions at all costs.
  80   * As a corollary, external dependencies should be kept to a minimum.
  81   *
  82   * @ingroup FileBackend
  83   * @since 1.19
  84   */
  85  abstract class FileBackend {
  86      /** @var string Unique backend name */
  87      protected $name;
  88  
  89      /** @var string Unique wiki name */
  90      protected $wikiId;
  91  
  92      /** @var string Read-only explanation message */
  93      protected $readOnly;
  94  
  95      /** @var string When to do operations in parallel */
  96      protected $parallelize;
  97  
  98      /** @var int How many operations can be done in parallel */
  99      protected $concurrency;
 100  
 101      /** @var LockManager */
 102      protected $lockManager;
 103  
 104      /** @var FileJournal */
 105      protected $fileJournal;
 106  
 107      /** Bitfield flags for supported features */
 108      const ATTR_HEADERS = 1; // files can be tagged with standard HTTP headers
 109      const ATTR_METADATA = 2; // files can be stored with metadata key/values
 110      const ATTR_UNICODE_PATHS = 4; // files can have Unicode paths (not just ASCII)
 111  
 112      /**
 113       * Create a new backend instance from configuration.
 114       * This should only be called from within FileBackendGroup.
 115       *
 116       * @param array $config Parameters include:
 117       *   - name        : The unique name of this backend.
 118       *                   This should consist of alphanumberic, '-', and '_' characters.
 119       *                   This name should not be changed after use (e.g. with journaling).
 120       *                   Note that the name is *not* used in actual container names.
 121       *   - wikiId      : Prefix to container names that is unique to this backend.
 122       *                   It should only consist of alphanumberic, '-', and '_' characters.
 123       *                   This ID is what avoids collisions if multiple logical backends
 124       *                   use the same storage system, so this should be set carefully.
 125       *   - lockManager : LockManager object to use for any file locking.
 126       *                   If not provided, then no file locking will be enforced.
 127       *   - fileJournal : FileJournal object to use for logging changes to files.
 128       *                   If not provided, then change journaling will be disabled.
 129       *   - readOnly    : Write operations are disallowed if this is a non-empty string.
 130       *                   It should be an explanation for the backend being read-only.
 131       *   - parallelize : When to do file operations in parallel (when possible).
 132       *                   Allowed values are "implicit", "explicit" and "off".
 133       *   - concurrency : How many file operations can be done in parallel.
 134       * @throws FileBackendException
 135       */
 136  	public function __construct( array $config ) {
 137          $this->name = $config['name'];
 138          $this->wikiId = $config['wikiId']; // e.g. "my_wiki-en_"
 139          if ( !preg_match( '!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) {
 140              throw new FileBackendException( "Backend name '{$this->name}' is invalid." );
 141          } elseif ( !is_string( $this->wikiId ) ) {
 142              throw new FileBackendException( "Backend wiki ID not provided for '{$this->name}'." );
 143          }
 144          $this->lockManager = isset( $config['lockManager'] )
 145              ? $config['lockManager']
 146              : new NullLockManager( array() );
 147          $this->fileJournal = isset( $config['fileJournal'] )
 148              ? $config['fileJournal']
 149              : FileJournal::factory( array( 'class' => 'NullFileJournal' ), $this->name );
 150          $this->readOnly = isset( $config['readOnly'] )
 151              ? (string)$config['readOnly']
 152              : '';
 153          $this->parallelize = isset( $config['parallelize'] )
 154              ? (string)$config['parallelize']
 155              : 'off';
 156          $this->concurrency = isset( $config['concurrency'] )
 157              ? (int)$config['concurrency']
 158              : 50;
 159      }
 160  
 161      /**
 162       * Get the unique backend name.
 163       * We may have multiple different backends of the same type.
 164       * For example, we can have two Swift backends using different proxies.
 165       *
 166       * @return string
 167       */
 168  	final public function getName() {
 169          return $this->name;
 170      }
 171  
 172      /**
 173       * Get the wiki identifier used for this backend (possibly empty).
 174       * Note that this might *not* be in the same format as wfWikiID().
 175       *
 176       * @return string
 177       * @since 1.20
 178       */
 179  	final public function getWikiId() {
 180          return $this->wikiId;
 181      }
 182  
 183      /**
 184       * Check if this backend is read-only
 185       *
 186       * @return bool
 187       */
 188  	final public function isReadOnly() {
 189          return ( $this->readOnly != '' );
 190      }
 191  
 192      /**
 193       * Get an explanatory message if this backend is read-only
 194       *
 195       * @return string|bool Returns false if the backend is not read-only
 196       */
 197  	final public function getReadOnlyReason() {
 198          return ( $this->readOnly != '' ) ? $this->readOnly : false;
 199      }
 200  
 201      /**
 202       * Get the a bitfield of extra features supported by the backend medium
 203       *
 204       * @return int Bitfield of FileBackend::ATTR_* flags
 205       * @since 1.23
 206       */
 207  	public function getFeatures() {
 208          return self::ATTR_UNICODE_PATHS;
 209      }
 210  
 211      /**
 212       * Check if the backend medium supports a field of extra features
 213       *
 214       * @param int $bitfield Bitfield of FileBackend::ATTR_* flags
 215       * @return bool
 216       * @since 1.23
 217       */
 218  	final public function hasFeatures( $bitfield ) {
 219          return ( $this->getFeatures() & $bitfield ) === $bitfield;
 220      }
 221  
 222      /**
 223       * This is the main entry point into the backend for write operations.
 224       * Callers supply an ordered list of operations to perform as a transaction.
 225       * Files will be locked, the stat cache cleared, and then the operations attempted.
 226       * If any serious errors occur, all attempted operations will be rolled back.
 227       *
 228       * $ops is an array of arrays. The outer array holds a list of operations.
 229       * Each inner array is a set of key value pairs that specify an operation.
 230       *
 231       * Supported operations and their parameters. The supported actions are:
 232       *  - create
 233       *  - store
 234       *  - copy
 235       *  - move
 236       *  - delete
 237       *  - describe (since 1.21)
 238       *  - null
 239       *
 240       * a) Create a new file in storage with the contents of a string
 241       * @code
 242       *     array(
 243       *         'op'                  => 'create',
 244       *         'dst'                 => <storage path>,
 245       *         'content'             => <string of new file contents>,
 246       *         'overwrite'           => <boolean>,
 247       *         'overwriteSame'       => <boolean>,
 248       *         'headers'             => <HTTP header name/value map> # since 1.21
 249       *     );
 250       * @endcode
 251       *
 252       * b) Copy a file system file into storage
 253       * @code
 254       *     array(
 255       *         'op'                  => 'store',
 256       *         'src'                 => <file system path>,
 257       *         'dst'                 => <storage path>,
 258       *         'overwrite'           => <boolean>,
 259       *         'overwriteSame'       => <boolean>,
 260       *         'headers'             => <HTTP header name/value map> # since 1.21
 261       *     )
 262       * @endcode
 263       *
 264       * c) Copy a file within storage
 265       * @code
 266       *     array(
 267       *         'op'                  => 'copy',
 268       *         'src'                 => <storage path>,
 269       *         'dst'                 => <storage path>,
 270       *         'overwrite'           => <boolean>,
 271       *         'overwriteSame'       => <boolean>,
 272       *         'ignoreMissingSource' => <boolean>, # since 1.21
 273       *         'headers'             => <HTTP header name/value map> # since 1.21
 274       *     )
 275       * @endcode
 276       *
 277       * d) Move a file within storage
 278       * @code
 279       *     array(
 280       *         'op'                  => 'move',
 281       *         'src'                 => <storage path>,
 282       *         'dst'                 => <storage path>,
 283       *         'overwrite'           => <boolean>,
 284       *         'overwriteSame'       => <boolean>,
 285       *         'ignoreMissingSource' => <boolean>, # since 1.21
 286       *         'headers'             => <HTTP header name/value map> # since 1.21
 287       *     )
 288       * @endcode
 289       *
 290       * e) Delete a file within storage
 291       * @code
 292       *     array(
 293       *         'op'                  => 'delete',
 294       *         'src'                 => <storage path>,
 295       *         'ignoreMissingSource' => <boolean>
 296       *     )
 297       * @endcode
 298       *
 299       * f) Update metadata for a file within storage
 300       * @code
 301       *     array(
 302       *         'op'                  => 'describe',
 303       *         'src'                 => <storage path>,
 304       *         'headers'             => <HTTP header name/value map>
 305       *     )
 306       * @endcode
 307       *
 308       * g) Do nothing (no-op)
 309       * @code
 310       *     array(
 311       *         'op'                  => 'null',
 312       *     )
 313       * @endcode
 314       *
 315       * Boolean flags for operations (operation-specific):
 316       *   - ignoreMissingSource : The operation will simply succeed and do
 317       *                           nothing if the source file does not exist.
 318       *   - overwrite           : Any destination file will be overwritten.
 319       *   - overwriteSame       : If a file already exists at the destination with the
 320       *                           same contents, then do nothing to the destination file
 321       *                           instead of giving an error. This does not compare headers.
 322       *                           This option is ignored if 'overwrite' is already provided.
 323       *   - headers             : If supplied, the result of merging these headers with any
 324       *                           existing source file headers (replacing conflicting ones)
 325       *                           will be set as the destination file headers. Headers are
 326       *                           deleted if their value is set to the empty string. When a
 327       *                           file has headers they are included in responses to GET and
 328       *                           HEAD requests to the backing store for that file.
 329       *                           Header values should be no larger than 255 bytes, except for
 330       *                           Content-Disposition. The system might ignore or truncate any
 331       *                           headers that are too long to store (exact limits will vary).
 332       *                           Backends that don't support metadata ignore this. (since 1.21)
 333       *
 334       * $opts is an associative of boolean flags, including:
 335       *   - force               : Operation precondition errors no longer trigger an abort.
 336       *                           Any remaining operations are still attempted. Unexpected
 337       *                           failures may still cause remaining operations to be aborted.
 338       *   - nonLocking          : No locks are acquired for the operations.
 339       *                           This can increase performance for non-critical writes.
 340       *                           This has no effect unless the 'force' flag is set.
 341       *   - nonJournaled        : Don't log this operation batch in the file journal.
 342       *                           This limits the ability of recovery scripts.
 343       *   - parallelize         : Try to do operations in parallel when possible.
 344       *   - bypassReadOnly      : Allow writes in read-only mode. (since 1.20)
 345       *   - preserveCache       : Don't clear the process cache before checking files.
 346       *                           This should only be used if all entries in the process
 347       *                           cache were added after the files were already locked. (since 1.20)
 348       *
 349       * @remarks Remarks on locking:
 350       * File system paths given to operations should refer to files that are
 351       * already locked or otherwise safe from modification from other processes.
 352       * Normally these files will be new temp files, which should be adequate.
 353       *
 354       * @par Return value:
 355       *
 356       * This returns a Status, which contains all warnings and fatals that occurred
 357       * during the operation. The 'failCount', 'successCount', and 'success' members
 358       * will reflect each operation attempted.
 359       *
 360       * The status will be "OK" unless:
 361       *   - a) unexpected operation errors occurred (network partitions, disk full...)
 362       *   - b) significant operation errors occurred and 'force' was not set
 363       *
 364       * @param array $ops List of operations to execute in order
 365       * @param array $opts Batch operation options
 366       * @return Status
 367       */
 368  	final public function doOperations( array $ops, array $opts = array() ) {
 369          if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
 370              return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
 371          }
 372          if ( !count( $ops ) ) {
 373              return Status::newGood(); // nothing to do
 374          }
 375          if ( empty( $opts['force'] ) ) { // sanity
 376              unset( $opts['nonLocking'] );
 377          }
 378          foreach ( $ops as &$op ) {
 379              if ( isset( $op['disposition'] ) ) { // b/c (MW 1.20)
 380                  $op['headers']['Content-Disposition'] = $op['disposition'];
 381              }
 382          }
 383          $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
 384          return $this->doOperationsInternal( $ops, $opts );
 385      }
 386  
 387      /**
 388       * @see FileBackend::doOperations()
 389       */
 390      abstract protected function doOperationsInternal( array $ops, array $opts );
 391  
 392      /**
 393       * Same as doOperations() except it takes a single operation.
 394       * If you are doing a batch of operations that should either
 395       * all succeed or all fail, then use that function instead.
 396       *
 397       * @see FileBackend::doOperations()
 398       *
 399       * @param array $op Operation
 400       * @param array $opts Operation options
 401       * @return Status
 402       */
 403  	final public function doOperation( array $op, array $opts = array() ) {
 404          return $this->doOperations( array( $op ), $opts );
 405      }
 406  
 407      /**
 408       * Performs a single create operation.
 409       * This sets $params['op'] to 'create' and passes it to doOperation().
 410       *
 411       * @see FileBackend::doOperation()
 412       *
 413       * @param array $params Operation parameters
 414       * @param array $opts Operation options
 415       * @return Status
 416       */
 417  	final public function create( array $params, array $opts = array() ) {
 418          return $this->doOperation( array( 'op' => 'create' ) + $params, $opts );
 419      }
 420  
 421      /**
 422       * Performs a single store operation.
 423       * This sets $params['op'] to 'store' and passes it to doOperation().
 424       *
 425       * @see FileBackend::doOperation()
 426       *
 427       * @param array $params Operation parameters
 428       * @param array $opts Operation options
 429       * @return Status
 430       */
 431  	final public function store( array $params, array $opts = array() ) {
 432          return $this->doOperation( array( 'op' => 'store' ) + $params, $opts );
 433      }
 434  
 435      /**
 436       * Performs a single copy operation.
 437       * This sets $params['op'] to 'copy' and passes it to doOperation().
 438       *
 439       * @see FileBackend::doOperation()
 440       *
 441       * @param array $params Operation parameters
 442       * @param array $opts Operation options
 443       * @return Status
 444       */
 445  	final public function copy( array $params, array $opts = array() ) {
 446          return $this->doOperation( array( 'op' => 'copy' ) + $params, $opts );
 447      }
 448  
 449      /**
 450       * Performs a single move operation.
 451       * This sets $params['op'] to 'move' and passes it to doOperation().
 452       *
 453       * @see FileBackend::doOperation()
 454       *
 455       * @param array $params Operation parameters
 456       * @param array $opts Operation options
 457       * @return Status
 458       */
 459  	final public function move( array $params, array $opts = array() ) {
 460          return $this->doOperation( array( 'op' => 'move' ) + $params, $opts );
 461      }
 462  
 463      /**
 464       * Performs a single delete operation.
 465       * This sets $params['op'] to 'delete' and passes it to doOperation().
 466       *
 467       * @see FileBackend::doOperation()
 468       *
 469       * @param array $params Operation parameters
 470       * @param array $opts Operation options
 471       * @return Status
 472       */
 473  	final public function delete( array $params, array $opts = array() ) {
 474          return $this->doOperation( array( 'op' => 'delete' ) + $params, $opts );
 475      }
 476  
 477      /**
 478       * Performs a single describe operation.
 479       * This sets $params['op'] to 'describe' and passes it to doOperation().
 480       *
 481       * @see FileBackend::doOperation()
 482       *
 483       * @param array $params Operation parameters
 484       * @param array $opts Operation options
 485       * @return Status
 486       * @since 1.21
 487       */
 488  	final public function describe( array $params, array $opts = array() ) {
 489          return $this->doOperation( array( 'op' => 'describe' ) + $params, $opts );
 490      }
 491  
 492      /**
 493       * Perform a set of independent file operations on some files.
 494       *
 495       * This does no locking, nor journaling, and possibly no stat calls.
 496       * Any destination files that already exist will be overwritten.
 497       * This should *only* be used on non-original files, like cache files.
 498       *
 499       * Supported operations and their parameters:
 500       *  - create
 501       *  - store
 502       *  - copy
 503       *  - move
 504       *  - delete
 505       *  - describe (since 1.21)
 506       *  - null
 507       *
 508       * a) Create a new file in storage with the contents of a string
 509       * @code
 510       *     array(
 511       *         'op'                  => 'create',
 512       *         'dst'                 => <storage path>,
 513       *         'content'             => <string of new file contents>,
 514       *         'headers'             => <HTTP header name/value map> # since 1.21
 515       *     )
 516       * @endcode
 517       *
 518       * b) Copy a file system file into storage
 519       * @code
 520       *     array(
 521       *         'op'                  => 'store',
 522       *         'src'                 => <file system path>,
 523       *         'dst'                 => <storage path>,
 524       *         'headers'             => <HTTP header name/value map> # since 1.21
 525       *     )
 526       * @endcode
 527       *
 528       * c) Copy a file within storage
 529       * @code
 530       *     array(
 531       *         'op'                  => 'copy',
 532       *         'src'                 => <storage path>,
 533       *         'dst'                 => <storage path>,
 534       *         'ignoreMissingSource' => <boolean>, # since 1.21
 535       *         'headers'             => <HTTP header name/value map> # since 1.21
 536       *     )
 537       * @endcode
 538       *
 539       * d) Move a file within storage
 540       * @code
 541       *     array(
 542       *         'op'                  => 'move',
 543       *         'src'                 => <storage path>,
 544       *         'dst'                 => <storage path>,
 545       *         'ignoreMissingSource' => <boolean>, # since 1.21
 546       *         'headers'             => <HTTP header name/value map> # since 1.21
 547       *     )
 548       * @endcode
 549       *
 550       * e) Delete a file within storage
 551       * @code
 552       *     array(
 553       *         'op'                  => 'delete',
 554       *         'src'                 => <storage path>,
 555       *         'ignoreMissingSource' => <boolean>
 556       *     )
 557       * @endcode
 558       *
 559       * f) Update metadata for a file within storage
 560       * @code
 561       *     array(
 562       *         'op'                  => 'describe',
 563       *         'src'                 => <storage path>,
 564       *         'headers'             => <HTTP header name/value map>
 565       *     )
 566       * @endcode
 567       *
 568       * g) Do nothing (no-op)
 569       * @code
 570       *     array(
 571       *         'op'                  => 'null',
 572       *     )
 573       * @endcode
 574       *
 575       * @par Boolean flags for operations (operation-specific):
 576       *   - ignoreMissingSource : The operation will simply succeed and do
 577       *                           nothing if the source file does not exist.
 578       *   - headers             : If supplied with a header name/value map, the backend will
 579       *                           reply with these headers when GETs/HEADs of the destination
 580       *                           file are made. Header values should be smaller than 256 bytes.
 581       *                           Content-Disposition headers can be longer, though the system
 582       *                           might ignore or truncate ones that are too long to store.
 583       *                           Existing headers will remain, but these will replace any
 584       *                           conflicting previous headers, and headers will be removed
 585       *                           if they are set to an empty string.
 586       *                           Backends that don't support metadata ignore this. (since 1.21)
 587       *
 588       * $opts is an associative of boolean flags, including:
 589       *   - bypassReadOnly      : Allow writes in read-only mode (since 1.20)
 590       *
 591       * @par Return value:
 592       * This returns a Status, which contains all warnings and fatals that occurred
 593       * during the operation. The 'failCount', 'successCount', and 'success' members
 594       * will reflect each operation attempted for the given files. The status will be
 595       * considered "OK" as long as no fatal errors occurred.
 596       *
 597       * @param array $ops Set of operations to execute
 598       * @param array $opts Batch operation options
 599       * @return Status
 600       * @since 1.20
 601       */
 602  	final public function doQuickOperations( array $ops, array $opts = array() ) {
 603          if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
 604              return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
 605          }
 606          if ( !count( $ops ) ) {
 607              return Status::newGood(); // nothing to do
 608          }
 609          foreach ( $ops as &$op ) {
 610              $op['overwrite'] = true; // avoids RTTs in key/value stores
 611              if ( isset( $op['disposition'] ) ) { // b/c (MW 1.20)
 612                  $op['headers']['Content-Disposition'] = $op['disposition'];
 613              }
 614          }
 615          $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
 616          return $this->doQuickOperationsInternal( $ops );
 617      }
 618  
 619      /**
 620       * @see FileBackend::doQuickOperations()
 621       * @since 1.20
 622       */
 623      abstract protected function doQuickOperationsInternal( array $ops );
 624  
 625      /**
 626       * Same as doQuickOperations() except it takes a single operation.
 627       * If you are doing a batch of operations, then use that function instead.
 628       *
 629       * @see FileBackend::doQuickOperations()
 630       *
 631       * @param array $op Operation
 632       * @return Status
 633       * @since 1.20
 634       */
 635  	final public function doQuickOperation( array $op ) {
 636          return $this->doQuickOperations( array( $op ) );
 637      }
 638  
 639      /**
 640       * Performs a single quick create operation.
 641       * This sets $params['op'] to 'create' and passes it to doQuickOperation().
 642       *
 643       * @see FileBackend::doQuickOperation()
 644       *
 645       * @param array $params Operation parameters
 646       * @return Status
 647       * @since 1.20
 648       */
 649  	final public function quickCreate( array $params ) {
 650          return $this->doQuickOperation( array( 'op' => 'create' ) + $params );
 651      }
 652  
 653      /**
 654       * Performs a single quick store operation.
 655       * This sets $params['op'] to 'store' and passes it to doQuickOperation().
 656       *
 657       * @see FileBackend::doQuickOperation()
 658       *
 659       * @param array $params Operation parameters
 660       * @return Status
 661       * @since 1.20
 662       */
 663  	final public function quickStore( array $params ) {
 664          return $this->doQuickOperation( array( 'op' => 'store' ) + $params );
 665      }
 666  
 667      /**
 668       * Performs a single quick copy operation.
 669       * This sets $params['op'] to 'copy' and passes it to doQuickOperation().
 670       *
 671       * @see FileBackend::doQuickOperation()
 672       *
 673       * @param array $params Operation parameters
 674       * @return Status
 675       * @since 1.20
 676       */
 677  	final public function quickCopy( array $params ) {
 678          return $this->doQuickOperation( array( 'op' => 'copy' ) + $params );
 679      }
 680  
 681      /**
 682       * Performs a single quick move operation.
 683       * This sets $params['op'] to 'move' and passes it to doQuickOperation().
 684       *
 685       * @see FileBackend::doQuickOperation()
 686       *
 687       * @param array $params Operation parameters
 688       * @return Status
 689       * @since 1.20
 690       */
 691  	final public function quickMove( array $params ) {
 692          return $this->doQuickOperation( array( 'op' => 'move' ) + $params );
 693      }
 694  
 695      /**
 696       * Performs a single quick delete operation.
 697       * This sets $params['op'] to 'delete' and passes it to doQuickOperation().
 698       *
 699       * @see FileBackend::doQuickOperation()
 700       *
 701       * @param array $params Operation parameters
 702       * @return Status
 703       * @since 1.20
 704       */
 705  	final public function quickDelete( array $params ) {
 706          return $this->doQuickOperation( array( 'op' => 'delete' ) + $params );
 707      }
 708  
 709      /**
 710       * Performs a single quick describe operation.
 711       * This sets $params['op'] to 'describe' and passes it to doQuickOperation().
 712       *
 713       * @see FileBackend::doQuickOperation()
 714       *
 715       * @param array $params Operation parameters
 716       * @return Status
 717       * @since 1.21
 718       */
 719  	final public function quickDescribe( array $params ) {
 720          return $this->doQuickOperation( array( 'op' => 'describe' ) + $params );
 721      }
 722  
 723      /**
 724       * Concatenate a list of storage files into a single file system file.
 725       * The target path should refer to a file that is already locked or
 726       * otherwise safe from modification from other processes. Normally,
 727       * the file will be a new temp file, which should be adequate.
 728       *
 729       * @param array $params Operation parameters, include:
 730       *   - srcs        : ordered source storage paths (e.g. chunk1, chunk2, ...)
 731       *   - dst         : file system path to 0-byte temp file
 732       *   - parallelize : try to do operations in parallel when possible
 733       * @return Status
 734       */
 735      abstract public function concatenate( array $params );
 736  
 737      /**
 738       * Prepare a storage directory for usage.
 739       * This will create any required containers and parent directories.
 740       * Backends using key/value stores only need to create the container.
 741       *
 742       * The 'noAccess' and 'noListing' parameters works the same as in secure(),
 743       * except they are only applied *if* the directory/container had to be created.
 744       * These flags should always be set for directories that have private files.
 745       * However, setting them is not guaranteed to actually do anything.
 746       * Additional server configuration may be needed to achieve the desired effect.
 747       *
 748       * @param array $params Parameters include:
 749       *   - dir            : storage directory
 750       *   - noAccess       : try to deny file access (since 1.20)
 751       *   - noListing      : try to deny file listing (since 1.20)
 752       *   - bypassReadOnly : allow writes in read-only mode (since 1.20)
 753       * @return Status
 754       */
 755  	final public function prepare( array $params ) {
 756          if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
 757              return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
 758          }
 759          $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
 760          return $this->doPrepare( $params );
 761      }
 762  
 763      /**
 764       * @see FileBackend::prepare()
 765       */
 766      abstract protected function doPrepare( array $params );
 767  
 768      /**
 769       * Take measures to block web access to a storage directory and
 770       * the container it belongs to. FS backends might add .htaccess
 771       * files whereas key/value store backends might revoke container
 772       * access to the storage user representing end-users in web requests.
 773       *
 774       * This is not guaranteed to actually make files or listings publically hidden.
 775       * Additional server configuration may be needed to achieve the desired effect.
 776       *
 777       * @param array $params Parameters include:
 778       *   - dir            : storage directory
 779       *   - noAccess       : try to deny file access
 780       *   - noListing      : try to deny file listing
 781       *   - bypassReadOnly : allow writes in read-only mode (since 1.20)
 782       * @return Status
 783       */
 784  	final public function secure( array $params ) {
 785          if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
 786              return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
 787          }
 788          $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
 789          return $this->doSecure( $params );
 790      }
 791  
 792      /**
 793       * @see FileBackend::secure()
 794       */
 795      abstract protected function doSecure( array $params );
 796  
 797      /**
 798       * Remove measures to block web access to a storage directory and
 799       * the container it belongs to. FS backends might remove .htaccess
 800       * files whereas key/value store backends might grant container
 801       * access to the storage user representing end-users in web requests.
 802       * This essentially can undo the result of secure() calls.
 803       *
 804       * This is not guaranteed to actually make files or listings publically viewable.
 805       * Additional server configuration may be needed to achieve the desired effect.
 806       *
 807       * @param array $params Parameters include:
 808       *   - dir            : storage directory
 809       *   - access         : try to allow file access
 810       *   - listing        : try to allow file listing
 811       *   - bypassReadOnly : allow writes in read-only mode (since 1.20)
 812       * @return Status
 813       * @since 1.20
 814       */
 815  	final public function publish( array $params ) {
 816          if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
 817              return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
 818          }
 819          $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
 820          return $this->doPublish( $params );
 821      }
 822  
 823      /**
 824       * @see FileBackend::publish()
 825       */
 826      abstract protected function doPublish( array $params );
 827  
 828      /**
 829       * Delete a storage directory if it is empty.
 830       * Backends using key/value stores may do nothing unless the directory
 831       * is that of an empty container, in which case it will be deleted.
 832       *
 833       * @param array $params Parameters include:
 834       *   - dir            : storage directory
 835       *   - recursive      : recursively delete empty subdirectories first (since 1.20)
 836       *   - bypassReadOnly : allow writes in read-only mode (since 1.20)
 837       * @return Status
 838       */
 839  	final public function clean( array $params ) {
 840          if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
 841              return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
 842          }
 843          $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
 844          return $this->doClean( $params );
 845      }
 846  
 847      /**
 848       * @see FileBackend::clean()
 849       */
 850      abstract protected function doClean( array $params );
 851  
 852      /**
 853       * Enter file operation scope.
 854       * This just makes PHP ignore user aborts/disconnects until the return
 855       * value leaves scope. This returns null and does nothing in CLI mode.
 856       *
 857       * @return ScopedCallback|null
 858       */
 859  	final protected function getScopedPHPBehaviorForOps() {
 860          if ( PHP_SAPI != 'cli' ) { // http://bugs.php.net/bug.php?id=47540
 861              $old = ignore_user_abort( true ); // avoid half-finished operations
 862              return new ScopedCallback( function () use ( $old ) {
 863                  ignore_user_abort( $old );
 864              } );
 865          }
 866  
 867          return null;
 868      }
 869  
 870      /**
 871       * Check if a file exists at a storage path in the backend.
 872       * This returns false if only a directory exists at the path.
 873       *
 874       * @param array $params Parameters include:
 875       *   - src    : source storage path
 876       *   - latest : use the latest available data
 877       * @return bool|null Returns null on failure
 878       */
 879      abstract public function fileExists( array $params );
 880  
 881      /**
 882       * Get the last-modified timestamp of the file at a storage path.
 883       *
 884       * @param array $params Parameters include:
 885       *   - src    : source storage path
 886       *   - latest : use the latest available data
 887       * @return string|bool TS_MW timestamp or false on failure
 888       */
 889      abstract public function getFileTimestamp( array $params );
 890  
 891      /**
 892       * Get the contents of a file at a storage path in the backend.
 893       * This should be avoided for potentially large files.
 894       *
 895       * @param array $params Parameters include:
 896       *   - src    : source storage path
 897       *   - latest : use the latest available data
 898       * @return string|bool Returns false on failure
 899       */
 900  	final public function getFileContents( array $params ) {
 901          $contents = $this->getFileContentsMulti(
 902              array( 'srcs' => array( $params['src'] ) ) + $params );
 903  
 904          return $contents[$params['src']];
 905      }
 906  
 907      /**
 908       * Like getFileContents() except it takes an array of storage paths
 909       * and returns a map of storage paths to strings (or null on failure).
 910       * The map keys (paths) are in the same order as the provided list of paths.
 911       *
 912       * @see FileBackend::getFileContents()
 913       *
 914       * @param array $params Parameters include:
 915       *   - srcs        : list of source storage paths
 916       *   - latest      : use the latest available data
 917       *   - parallelize : try to do operations in parallel when possible
 918       * @return array Map of (path name => string or false on failure)
 919       * @since 1.20
 920       */
 921      abstract public function getFileContentsMulti( array $params );
 922  
 923      /**
 924       * Get metadata about a file at a storage path in the backend.
 925       * If the file does not exist, then this returns false.
 926       * Otherwise, the result is an associative array that includes:
 927       *   - headers  : map of HTTP headers used for GET/HEAD requests (name => value)
 928       *   - metadata : map of file metadata (name => value)
 929       * Metadata keys and headers names will be returned in all lower-case.
 930       * Additional values may be included for internal use only.
 931       *
 932       * Use FileBackend::hasFeatures() to check how well this is supported.
 933       *
 934       * @param array $params
 935       * $params include:
 936       *   - src    : source storage path
 937       *   - latest : use the latest available data
 938       * @return array|bool Returns false on failure
 939       * @since 1.23
 940       */
 941      abstract public function getFileXAttributes( array $params );
 942  
 943      /**
 944       * Get the size (bytes) of a file at a storage path in the backend.
 945       *
 946       * @param array $params Parameters include:
 947       *   - src    : source storage path
 948       *   - latest : use the latest available data
 949       * @return int|bool Returns false on failure
 950       */
 951      abstract public function getFileSize( array $params );
 952  
 953      /**
 954       * Get quick information about a file at a storage path in the backend.
 955       * If the file does not exist, then this returns false.
 956       * Otherwise, the result is an associative array that includes:
 957       *   - mtime  : the last-modified timestamp (TS_MW)
 958       *   - size   : the file size (bytes)
 959       * Additional values may be included for internal use only.
 960       *
 961       * @param array $params Parameters include:
 962       *   - src    : source storage path
 963       *   - latest : use the latest available data
 964       * @return array|bool|null Returns null on failure
 965       */
 966      abstract public function getFileStat( array $params );
 967  
 968      /**
 969       * Get a SHA-1 hash of the file at a storage path in the backend.
 970       *
 971       * @param array $params Parameters include:
 972       *   - src    : source storage path
 973       *   - latest : use the latest available data
 974       * @return string|bool Hash string or false on failure
 975       */
 976      abstract public function getFileSha1Base36( array $params );
 977  
 978      /**
 979       * Get the properties of the file at a storage path in the backend.
 980       * This gives the result of FSFile::getProps() on a local copy of the file.
 981       *
 982       * @param array $params Parameters include:
 983       *   - src    : source storage path
 984       *   - latest : use the latest available data
 985       * @return array Returns FSFile::placeholderProps() on failure
 986       */
 987      abstract public function getFileProps( array $params );
 988  
 989      /**
 990       * Stream the file at a storage path in the backend.
 991       * If the file does not exists, an HTTP 404 error will be given.
 992       * Appropriate HTTP headers (Status, Content-Type, Content-Length)
 993       * will be sent if streaming began, while none will be sent otherwise.
 994       * Implementations should flush the output buffer before sending data.
 995       *
 996       * @param array $params Parameters include:
 997       *   - src     : source storage path
 998       *   - headers : list of additional HTTP headers to send on success
 999       *   - latest  : use the latest available data
1000       * @return Status
1001       */
1002      abstract public function streamFile( array $params );
1003  
1004      /**
1005       * Returns a file system file, identical to the file at a storage path.
1006       * The file returned is either:
1007       *   - a) A local copy of the file at a storage path in the backend.
1008       *        The temporary copy will have the same extension as the source.
1009       *   - b) An original of the file at a storage path in the backend.
1010       * Temporary files may be purged when the file object falls out of scope.
1011       *
1012       * Write operations should *never* be done on this file as some backends
1013       * may do internal tracking or may be instances of FileBackendMultiWrite.
1014       * In that later case, there are copies of the file that must stay in sync.
1015       * Additionally, further calls to this function may return the same file.
1016       *
1017       * @param array $params Parameters include:
1018       *   - src    : source storage path
1019       *   - latest : use the latest available data
1020       * @return FSFile|null Returns null on failure
1021       */
1022  	final public function getLocalReference( array $params ) {
1023          $fsFiles = $this->getLocalReferenceMulti(
1024              array( 'srcs' => array( $params['src'] ) ) + $params );
1025  
1026          return $fsFiles[$params['src']];
1027      }
1028  
1029      /**
1030       * Like getLocalReference() except it takes an array of storage paths
1031       * and returns a map of storage paths to FSFile objects (or null on failure).
1032       * The map keys (paths) are in the same order as the provided list of paths.
1033       *
1034       * @see FileBackend::getLocalReference()
1035       *
1036       * @param array $params Parameters include:
1037       *   - srcs        : list of source storage paths
1038       *   - latest      : use the latest available data
1039       *   - parallelize : try to do operations in parallel when possible
1040       * @return array Map of (path name => FSFile or null on failure)
1041       * @since 1.20
1042       */
1043      abstract public function getLocalReferenceMulti( array $params );
1044  
1045      /**
1046       * Get a local copy on disk of the file at a storage path in the backend.
1047       * The temporary copy will have the same file extension as the source.
1048       * Temporary files may be purged when the file object falls out of scope.
1049       *
1050       * @param array $params Parameters include:
1051       *   - src    : source storage path
1052       *   - latest : use the latest available data
1053       * @return TempFSFile|null Returns null on failure
1054       */
1055  	final public function getLocalCopy( array $params ) {
1056          $tmpFiles = $this->getLocalCopyMulti(
1057              array( 'srcs' => array( $params['src'] ) ) + $params );
1058  
1059          return $tmpFiles[$params['src']];
1060      }
1061  
1062      /**
1063       * Like getLocalCopy() except it takes an array of storage paths and
1064       * returns a map of storage paths to TempFSFile objects (or null on failure).
1065       * The map keys (paths) are in the same order as the provided list of paths.
1066       *
1067       * @see FileBackend::getLocalCopy()
1068       *
1069       * @param array $params Parameters include:
1070       *   - srcs        : list of source storage paths
1071       *   - latest      : use the latest available data
1072       *   - parallelize : try to do operations in parallel when possible
1073       * @return array Map of (path name => TempFSFile or null on failure)
1074       * @since 1.20
1075       */
1076      abstract public function getLocalCopyMulti( array $params );
1077  
1078      /**
1079       * Return an HTTP URL to a given file that requires no authentication to use.
1080       * The URL may be pre-authenticated (via some token in the URL) and temporary.
1081       * This will return null if the backend cannot make an HTTP URL for the file.
1082       *
1083       * This is useful for key/value stores when using scripts that seek around
1084       * large files and those scripts (and the backend) support HTTP Range headers.
1085       * Otherwise, one would need to use getLocalReference(), which involves loading
1086       * the entire file on to local disk.
1087       *
1088       * @param array $params Parameters include:
1089       *   - src : source storage path
1090       *   - ttl : lifetime (seconds) if pre-authenticated; default is 1 day
1091       * @return string|null
1092       * @since 1.21
1093       */
1094      abstract public function getFileHttpUrl( array $params );
1095  
1096      /**
1097       * Check if a directory exists at a given storage path.
1098       * Backends using key/value stores will check if the path is a
1099       * virtual directory, meaning there are files under the given directory.
1100       *
1101       * Storage backends with eventual consistency might return stale data.
1102       *
1103       * @param array $params Parameters include:
1104       *   - dir : storage directory
1105       * @return bool|null Returns null on failure
1106       * @since 1.20
1107       */
1108      abstract public function directoryExists( array $params );
1109  
1110      /**
1111       * Get an iterator to list *all* directories under a storage directory.
1112       * If the directory is of the form "mwstore://backend/container",
1113       * then all directories in the container will be listed.
1114       * If the directory is of form "mwstore://backend/container/dir",
1115       * then all directories directly under that directory will be listed.
1116       * Results will be storage directories relative to the given directory.
1117       *
1118       * Storage backends with eventual consistency might return stale data.
1119       *
1120       * Failures during iteration can result in FileBackendError exceptions (since 1.22).
1121       *
1122       * @param array $params Parameters include:
1123       *   - dir     : storage directory
1124       *   - topOnly : only return direct child dirs of the directory
1125       * @return Traversable|array|null Returns null on failure
1126       * @since 1.20
1127       */
1128      abstract public function getDirectoryList( array $params );
1129  
1130      /**
1131       * Same as FileBackend::getDirectoryList() except only lists
1132       * directories that are immediately under the given directory.
1133       *
1134       * Storage backends with eventual consistency might return stale data.
1135       *
1136       * Failures during iteration can result in FileBackendError exceptions (since 1.22).
1137       *
1138       * @param array $params Parameters include:
1139       *   - dir : storage directory
1140       * @return Traversable|array|null Returns null on failure
1141       * @since 1.20
1142       */
1143  	final public function getTopDirectoryList( array $params ) {
1144          return $this->getDirectoryList( array( 'topOnly' => true ) + $params );
1145      }
1146  
1147      /**
1148       * Get an iterator to list *all* stored files under a storage directory.
1149       * If the directory is of the form "mwstore://backend/container",
1150       * then all files in the container will be listed.
1151       * If the directory is of form "mwstore://backend/container/dir",
1152       * then all files under that directory will be listed.
1153       * Results will be storage paths relative to the given directory.
1154       *
1155       * Storage backends with eventual consistency might return stale data.
1156       *
1157       * Failures during iteration can result in FileBackendError exceptions (since 1.22).
1158       *
1159       * @param array $params Parameters include:
1160       *   - dir        : storage directory
1161       *   - topOnly    : only return direct child files of the directory (since 1.20)
1162       *   - adviseStat : set to true if stat requests will be made on the files (since 1.22)
1163       * @return Traversable|array|null Returns null on failure
1164       */
1165      abstract public function getFileList( array $params );
1166  
1167      /**
1168       * Same as FileBackend::getFileList() except only lists
1169       * files that are immediately under the given directory.
1170       *
1171       * Storage backends with eventual consistency might return stale data.
1172       *
1173       * Failures during iteration can result in FileBackendError exceptions (since 1.22).
1174       *
1175       * @param array $params Parameters include:
1176       *   - dir        : storage directory
1177       *   - adviseStat : set to true if stat requests will be made on the files (since 1.22)
1178       * @return Traversable|array|null Returns null on failure
1179       * @since 1.20
1180       */
1181  	final public function getTopFileList( array $params ) {
1182          return $this->getFileList( array( 'topOnly' => true ) + $params );
1183      }
1184  
1185      /**
1186       * Preload persistent file stat cache and property cache into in-process cache.
1187       * This should be used when stat calls will be made on a known list of a many files.
1188       *
1189       * @see FileBackend::getFileStat()
1190       *
1191       * @param array $paths Storage paths
1192       */
1193      abstract public function preloadCache( array $paths );
1194  
1195      /**
1196       * Invalidate any in-process file stat and property cache.
1197       * If $paths is given, then only the cache for those files will be cleared.
1198       *
1199       * @see FileBackend::getFileStat()
1200       *
1201       * @param array $paths Storage paths (optional)
1202       */
1203      abstract public function clearCache( array $paths = null );
1204  
1205      /**
1206       * Preload file stat information (concurrently if possible) into in-process cache.
1207       * This should be used when stat calls will be made on a known list of a many files.
1208       *
1209       * @see FileBackend::getFileStat()
1210       *
1211       * @param array $params Parameters include:
1212       *   - srcs        : list of source storage paths
1213       *   - latest      : use the latest available data
1214       * @return bool All requests proceeded without I/O errors (since 1.24)
1215       * @since 1.23
1216       */
1217      abstract public function preloadFileStat( array $params );
1218  
1219      /**
1220       * Lock the files at the given storage paths in the backend.
1221       * This will either lock all the files or none (on failure).
1222       *
1223       * Callers should consider using getScopedFileLocks() instead.
1224       *
1225       * @param array $paths Storage paths
1226       * @param int $type LockManager::LOCK_* constant
1227       * @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.24)
1228       * @return Status
1229       */
1230  	final public function lockFiles( array $paths, $type, $timeout = 0 ) {
1231          $paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
1232  
1233          return $this->lockManager->lock( $paths, $type, $timeout );
1234      }
1235  
1236      /**
1237       * Unlock the files at the given storage paths in the backend.
1238       *
1239       * @param array $paths Storage paths
1240       * @param int $type LockManager::LOCK_* constant
1241       * @return Status
1242       */
1243  	final public function unlockFiles( array $paths, $type ) {
1244          $paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
1245  
1246          return $this->lockManager->unlock( $paths, $type );
1247      }
1248  
1249      /**
1250       * Lock the files at the given storage paths in the backend.
1251       * This will either lock all the files or none (on failure).
1252       * On failure, the status object will be updated with errors.
1253       *
1254       * Once the return value goes out scope, the locks will be released and
1255       * the status updated. Unlock fatals will not change the status "OK" value.
1256       *
1257       * @see ScopedLock::factory()
1258       *
1259       * @param array $paths List of storage paths or map of lock types to path lists
1260       * @param int|string $type LockManager::LOCK_* constant or "mixed"
1261       * @param Status $status Status to update on lock/unlock
1262       * @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.24)
1263       * @return ScopedLock|null Returns null on failure
1264       */
1265  	final public function getScopedFileLocks( array $paths, $type, Status $status, $timeout = 0 ) {
1266          if ( $type === 'mixed' ) {
1267              foreach ( $paths as &$typePaths ) {
1268                  $typePaths = array_map( 'FileBackend::normalizeStoragePath', $typePaths );
1269              }
1270          } else {
1271              $paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
1272          }
1273  
1274          return ScopedLock::factory( $this->lockManager, $paths, $type, $status, $timeout );
1275      }
1276  
1277      /**
1278       * Get an array of scoped locks needed for a batch of file operations.
1279       *
1280       * Normally, FileBackend::doOperations() handles locking, unless
1281       * the 'nonLocking' param is passed in. This function is useful if you
1282       * want the files to be locked for a broader scope than just when the
1283       * files are changing. For example, if you need to update DB metadata,
1284       * you may want to keep the files locked until finished.
1285       *
1286       * @see FileBackend::doOperations()
1287       *
1288       * @param array $ops List of file operations to FileBackend::doOperations()
1289       * @param Status $status Status to update on lock/unlock
1290       * @return array List of ScopedFileLocks or null values
1291       * @since 1.20
1292       */
1293      abstract public function getScopedLocksForOps( array $ops, Status $status );
1294  
1295      /**
1296       * Get the root storage path of this backend.
1297       * All container paths are "subdirectories" of this path.
1298       *
1299       * @return string Storage path
1300       * @since 1.20
1301       */
1302  	final public function getRootStoragePath() {
1303          return "mwstore://{$this->name}";
1304      }
1305  
1306      /**
1307       * Get the storage path for the given container for this backend
1308       *
1309       * @param string $container Container name
1310       * @return string Storage path
1311       * @since 1.21
1312       */
1313  	final public function getContainerStoragePath( $container ) {
1314          return $this->getRootStoragePath() . "/{$container}";
1315      }
1316  
1317      /**
1318       * Get the file journal object for this backend
1319       *
1320       * @return FileJournal
1321       */
1322  	final public function getJournal() {
1323          return $this->fileJournal;
1324      }
1325  
1326      /**
1327       * Check if a given path is a "mwstore://" path.
1328       * This does not do any further validation or any existence checks.
1329       *
1330       * @param string $path
1331       * @return bool
1332       */
1333  	final public static function isStoragePath( $path ) {
1334          return ( strpos( $path, 'mwstore://' ) === 0 );
1335      }
1336  
1337      /**
1338       * Split a storage path into a backend name, a container name,
1339       * and a relative file path. The relative path may be the empty string.
1340       * This does not do any path normalization or traversal checks.
1341       *
1342       * @param string $storagePath
1343       * @return array (backend, container, rel object) or (null, null, null)
1344       */
1345  	final public static function splitStoragePath( $storagePath ) {
1346          if ( self::isStoragePath( $storagePath ) ) {
1347              // Remove the "mwstore://" prefix and split the path
1348              $parts = explode( '/', substr( $storagePath, 10 ), 3 );
1349              if ( count( $parts ) >= 2 && $parts[0] != '' && $parts[1] != '' ) {
1350                  if ( count( $parts ) == 3 ) {
1351                      return $parts; // e.g. "backend/container/path"
1352                  } else {
1353                      return array( $parts[0], $parts[1], '' ); // e.g. "backend/container"
1354                  }
1355              }
1356          }
1357  
1358          return array( null, null, null );
1359      }
1360  
1361      /**
1362       * Normalize a storage path by cleaning up directory separators.
1363       * Returns null if the path is not of the format of a valid storage path.
1364       *
1365       * @param string $storagePath
1366       * @return string|null
1367       */
1368  	final public static function normalizeStoragePath( $storagePath ) {
1369          list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath );
1370          if ( $relPath !== null ) { // must be for this backend
1371              $relPath = self::normalizeContainerPath( $relPath );
1372              if ( $relPath !== null ) {
1373                  return ( $relPath != '' )
1374                      ? "mwstore://{$backend}/{$container}/{$relPath}"
1375                      : "mwstore://{$backend}/{$container}";
1376              }
1377          }
1378  
1379          return null;
1380      }
1381  
1382      /**
1383       * Get the parent storage directory of a storage path.
1384       * This returns a path like "mwstore://backend/container",
1385       * "mwstore://backend/container/...", or null if there is no parent.
1386       *
1387       * @param string $storagePath
1388       * @return string|null
1389       */
1390  	final public static function parentStoragePath( $storagePath ) {
1391          $storagePath = dirname( $storagePath );
1392          list( , , $rel ) = self::splitStoragePath( $storagePath );
1393  
1394          return ( $rel === null ) ? null : $storagePath;
1395      }
1396  
1397      /**
1398       * Get the final extension from a storage or FS path
1399       *
1400       * @param string $path
1401       * @param string $case One of (rawcase, uppercase, lowercase) (since 1.24)
1402       * @return string
1403       */
1404  	final public static function extensionFromPath( $path, $case = 'lowercase' ) {
1405          $i = strrpos( $path, '.' );
1406          $ext = $i ? substr( $path, $i + 1 ) : '';
1407  
1408          if ( $case === 'lowercase' ) {
1409              $ext = strtolower( $ext );
1410          } elseif ( $case === 'uppercase' ) {
1411              $ext = strtoupper( $ext );
1412          }
1413  
1414          return $ext;
1415      }
1416  
1417      /**
1418       * Check if a relative path has no directory traversals
1419       *
1420       * @param string $path
1421       * @return bool
1422       * @since 1.20
1423       */
1424  	final public static function isPathTraversalFree( $path ) {
1425          return ( self::normalizeContainerPath( $path ) !== null );
1426      }
1427  
1428      /**
1429       * Build a Content-Disposition header value per RFC 6266.
1430       *
1431       * @param string $type One of (attachment, inline)
1432       * @param string $filename Suggested file name (should not contain slashes)
1433       * @throws FileBackendError
1434       * @return string
1435       * @since 1.20
1436       */
1437  	final public static function makeContentDisposition( $type, $filename = '' ) {
1438          $parts = array();
1439  
1440          $type = strtolower( $type );
1441          if ( !in_array( $type, array( 'inline', 'attachment' ) ) ) {
1442              throw new FileBackendError( "Invalid Content-Disposition type '$type'." );
1443          }
1444          $parts[] = $type;
1445  
1446          if ( strlen( $filename ) ) {
1447              $parts[] = "filename*=UTF-8''" . rawurlencode( basename( $filename ) );
1448          }
1449  
1450          return implode( ';', $parts );
1451      }
1452  
1453      /**
1454       * Validate and normalize a relative storage path.
1455       * Null is returned if the path involves directory traversal.
1456       * Traversal is insecure for FS backends and broken for others.
1457       *
1458       * This uses the same traversal protection as Title::secureAndSplit().
1459       *
1460       * @param string $path Storage path relative to a container
1461       * @return string|null
1462       */
1463  	final protected static function normalizeContainerPath( $path ) {
1464          // Normalize directory separators
1465          $path = strtr( $path, '\\', '/' );
1466          // Collapse any consecutive directory separators
1467          $path = preg_replace( '![/]{2,}!', '/', $path );
1468          // Remove any leading directory separator
1469          $path = ltrim( $path, '/' );
1470          // Use the same traversal protection as Title::secureAndSplit()
1471          if ( strpos( $path, '.' ) !== false ) {
1472              if (
1473                  $path === '.' ||
1474                  $path === '..' ||
1475                  strpos( $path, './' ) === 0 ||
1476                  strpos( $path, '../' ) === 0 ||
1477                  strpos( $path, '/./' ) !== false ||
1478                  strpos( $path, '/../' ) !== false
1479              ) {
1480                  return null;
1481              }
1482          }
1483  
1484          return $path;
1485      }
1486  }
1487  
1488  /**
1489   * Generic file backend exception for checked and unexpected (e.g. config) exceptions
1490   *
1491   * @ingroup FileBackend
1492   * @since 1.23
1493   */
1494  class FileBackendException extends MWException {
1495  }
1496  
1497  /**
1498   * File backend exception for checked exceptions (e.g. I/O errors)
1499   *
1500   * @ingroup FileBackend
1501   * @since 1.22
1502   */
1503  class FileBackendError extends FileBackendException {
1504  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1