2011-12-20 03:52:06 +00:00
|
|
|
<?php
|
2012-02-08 15:51:16 +00:00
|
|
|
/**
|
|
|
|
|
* @defgroup FileBackend File backend
|
|
|
|
|
* @ingroup FileRepo
|
|
|
|
|
*
|
2012-02-09 17:54:14 +00:00
|
|
|
* File backend is used to interact with file storage systems,
|
|
|
|
|
* such as the local file system, NFS, or cloud storage systems.
|
2012-02-08 15:51:16 +00:00
|
|
|
*/
|
|
|
|
|
|
2011-12-20 03:52:06 +00:00
|
|
|
/**
|
|
|
|
|
* @file
|
|
|
|
|
* @ingroup FileBackend
|
|
|
|
|
* @author Aaron Schulz
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
2012-03-03 19:14:50 +00:00
|
|
|
* @brief Base class for all file backend classes (including multi-write backends).
|
2012-02-08 15:51:16 +00:00
|
|
|
*
|
2011-12-20 03:52:06 +00:00
|
|
|
* This class defines the methods as abstract that subclasses must implement.
|
|
|
|
|
* Outside callers can assume that all backends will have these functions.
|
|
|
|
|
*
|
2012-02-19 23:40:02 +00:00
|
|
|
* All "storage paths" are of the format "mwstore://<backend>/<container>/<path>".
|
|
|
|
|
* The <path> portion is a relative path that uses UNIX file system (FS) notation,
|
|
|
|
|
* though any particular backend may not actually be using a local filesystem.
|
|
|
|
|
* Therefore, the relative paths are only virtual.
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
2012-01-07 01:33:23 +00:00
|
|
|
* Backend contents are stored under wiki-specific container names by default.
|
|
|
|
|
* For legacy reasons, this has no effect for the FS backend class, and per-wiki
|
|
|
|
|
* segregation must be done by setting the container paths appropriately.
|
|
|
|
|
*
|
2011-12-20 03:52:06 +00:00
|
|
|
* FS-based backends are somewhat more restrictive due to the existence of real
|
|
|
|
|
* directory files; a regular file cannot have the same name as a directory. Other
|
2011-12-27 02:24:27 +00:00
|
|
|
* backends with virtual directories may not have this limitation. Callers should
|
2012-01-07 01:33:23 +00:00
|
|
|
* store files in such a way that no files and directories are under the same path.
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
|
|
|
|
* Methods should avoid throwing exceptions at all costs.
|
|
|
|
|
* As a corollary, external dependencies should be kept to a minimum.
|
|
|
|
|
*
|
|
|
|
|
* @ingroup FileBackend
|
|
|
|
|
* @since 1.19
|
|
|
|
|
*/
|
2012-01-29 22:22:28 +00:00
|
|
|
abstract class FileBackend {
|
2012-01-29 21:28:31 +00:00
|
|
|
protected $name; // string; unique backend name
|
|
|
|
|
protected $wikiId; // string; unique wiki name
|
|
|
|
|
protected $readOnly; // string; read-only explanation message
|
2011-12-20 03:52:06 +00:00
|
|
|
/** @var LockManager */
|
|
|
|
|
protected $lockManager;
|
|
|
|
|
|
|
|
|
|
/**
|
2012-01-07 01:33:23 +00:00
|
|
|
* Create a new backend instance from configuration.
|
2011-12-20 03:52:06 +00:00
|
|
|
* This should only be called from within FileBackendGroup.
|
|
|
|
|
*
|
|
|
|
|
* $config includes:
|
2012-01-05 06:18:36 +00:00
|
|
|
* 'name' : The unique name of this backend.
|
2012-02-03 18:05:33 +00:00
|
|
|
* This should consist of alphanumberic, '-', and '_' characters.
|
2012-03-01 21:55:22 +00:00
|
|
|
* This name should not be changed after use.
|
2012-01-05 06:18:36 +00:00
|
|
|
* 'wikiId' : Prefix to container names that is unique to this wiki.
|
Revert r107309, r113601, r113704, r113742, r113792, r113838, r113859, r113893, r113894, r113952, r114047, r114252, r114256, r114257. This reverts the remaining 'new' revisions in core.
All of these revisions are tagged with 'gerritmigration' and will be resubmitted into Gerrit after the Gerrit switchover. See also http://lists.wikimedia.org/pipermail/wikitech-l/2012-March/059124.html
2012-03-21 00:16:50 +00:00
|
|
|
* This should consist of alphanumberic, '-', and '_' characters.
|
2012-01-05 06:18:36 +00:00
|
|
|
* 'lockManager' : Registered name of a file lock manager to use.
|
2012-01-04 02:15:07 +00:00
|
|
|
* 'readOnly' : Write operations are disallowed if this is a non-empty string.
|
|
|
|
|
* It should be an explanation for the backend being read-only.
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
|
|
|
|
* @param $config Array
|
|
|
|
|
*/
|
|
|
|
|
public function __construct( array $config ) {
|
|
|
|
|
$this->name = $config['name'];
|
2012-03-01 21:55:22 +00:00
|
|
|
if ( !preg_match( '!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) {
|
|
|
|
|
throw new MWException( "Backend name `{$this->name}` is invalid." );
|
|
|
|
|
}
|
2012-01-23 21:56:00 +00:00
|
|
|
$this->wikiId = isset( $config['wikiId'] )
|
|
|
|
|
? $config['wikiId']
|
|
|
|
|
: wfWikiID(); // e.g. "my_wiki-en_"
|
2012-02-06 05:25:26 +00:00
|
|
|
$this->lockManager = ( $config['lockManager'] instanceof LockManager )
|
|
|
|
|
? $config['lockManager']
|
|
|
|
|
: LockManagerGroup::singleton()->get( $config['lockManager'] );
|
2012-01-04 02:15:07 +00:00
|
|
|
$this->readOnly = isset( $config['readOnly'] )
|
|
|
|
|
? (string)$config['readOnly']
|
|
|
|
|
: '';
|
2011-12-20 03:52:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the unique backend name.
|
|
|
|
|
* We may have multiple different backends of the same type.
|
|
|
|
|
* For example, we can have two Swift backends using different proxies.
|
|
|
|
|
*
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
final public function getName() {
|
|
|
|
|
return $this->name;
|
|
|
|
|
}
|
|
|
|
|
|
2012-02-06 05:25:26 +00:00
|
|
|
/**
|
|
|
|
|
* Check if this backend is read-only
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
final public function isReadOnly() {
|
|
|
|
|
return ( $this->readOnly != '' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get an explanatory message if this backend is read-only
|
|
|
|
|
*
|
2012-03-14 21:04:28 +00:00
|
|
|
* @return string|bool Returns false if the backend is not read-only
|
2012-02-06 05:25:26 +00:00
|
|
|
*/
|
|
|
|
|
final public function getReadOnlyReason() {
|
|
|
|
|
return ( $this->readOnly != '' ) ? $this->readOnly : false;
|
|
|
|
|
}
|
|
|
|
|
|
2011-12-20 03:52:06 +00:00
|
|
|
/**
|
|
|
|
|
* This is the main entry point into the backend for write operations.
|
|
|
|
|
* Callers supply an ordered list of operations to perform as a transaction.
|
2012-01-26 21:05:03 +00:00
|
|
|
* Files will be locked, the stat cache cleared, and then the operations attempted.
|
2011-12-20 03:52:06 +00:00
|
|
|
* If any serious errors occur, all attempted operations will be rolled back.
|
|
|
|
|
*
|
|
|
|
|
* $ops is an array of arrays. The outer array holds a list of operations.
|
|
|
|
|
* Each inner array is a set of key value pairs that specify an operation.
|
|
|
|
|
*
|
|
|
|
|
* Supported operations and their parameters:
|
|
|
|
|
* a) Create a new file in storage with the contents of a string
|
|
|
|
|
* array(
|
|
|
|
|
* 'op' => 'create',
|
|
|
|
|
* 'dst' => <storage path>,
|
|
|
|
|
* 'content' => <string of new file contents>,
|
2012-01-19 02:24:49 +00:00
|
|
|
* 'overwrite' => <boolean>,
|
2011-12-20 03:52:06 +00:00
|
|
|
* 'overwriteSame' => <boolean>
|
|
|
|
|
* )
|
|
|
|
|
* b) Copy a file system file into storage
|
|
|
|
|
* array(
|
|
|
|
|
* 'op' => 'store',
|
|
|
|
|
* 'src' => <file system path>,
|
|
|
|
|
* 'dst' => <storage path>,
|
2012-01-19 02:24:49 +00:00
|
|
|
* 'overwrite' => <boolean>,
|
2011-12-20 03:52:06 +00:00
|
|
|
* 'overwriteSame' => <boolean>
|
|
|
|
|
* )
|
|
|
|
|
* c) Copy a file within storage
|
|
|
|
|
* array(
|
|
|
|
|
* 'op' => 'copy',
|
|
|
|
|
* 'src' => <storage path>,
|
|
|
|
|
* 'dst' => <storage path>,
|
2012-01-19 02:24:49 +00:00
|
|
|
* 'overwrite' => <boolean>,
|
2011-12-20 03:52:06 +00:00
|
|
|
* 'overwriteSame' => <boolean>
|
|
|
|
|
* )
|
|
|
|
|
* d) Move a file within storage
|
|
|
|
|
* array(
|
|
|
|
|
* 'op' => 'move',
|
|
|
|
|
* 'src' => <storage path>,
|
|
|
|
|
* 'dst' => <storage path>,
|
2012-01-19 02:24:49 +00:00
|
|
|
* 'overwrite' => <boolean>,
|
2011-12-20 03:52:06 +00:00
|
|
|
* 'overwriteSame' => <boolean>
|
|
|
|
|
* )
|
|
|
|
|
* e) Delete a file within storage
|
|
|
|
|
* array(
|
|
|
|
|
* 'op' => 'delete',
|
|
|
|
|
* 'src' => <storage path>,
|
|
|
|
|
* 'ignoreMissingSource' => <boolean>
|
|
|
|
|
* )
|
2012-01-08 09:25:15 +00:00
|
|
|
* f) Do nothing (no-op)
|
2011-12-20 03:52:06 +00:00
|
|
|
* array(
|
|
|
|
|
* 'op' => 'null',
|
|
|
|
|
* )
|
|
|
|
|
*
|
|
|
|
|
* Boolean flags for operations (operation-specific):
|
|
|
|
|
* 'ignoreMissingSource' : The operation will simply succeed and do
|
|
|
|
|
* nothing if the source file does not exist.
|
2012-01-19 02:24:49 +00:00
|
|
|
* 'overwrite' : Any destination file will be overwritten.
|
2011-12-20 03:52:06 +00:00
|
|
|
* 'overwriteSame' : An error will not be given if a file already
|
|
|
|
|
* exists at the destination that has the same
|
|
|
|
|
* contents as the new contents to be written there.
|
|
|
|
|
*
|
2011-12-23 09:43:28 +00:00
|
|
|
* $opts is an associative of boolean flags, including:
|
2012-01-08 08:40:00 +00:00
|
|
|
* 'force' : Errors that would normally cause a rollback do not.
|
2011-12-23 09:43:28 +00:00
|
|
|
* The remaining operations are still attempted if any fail.
|
2011-12-20 03:52:06 +00:00
|
|
|
* 'nonLocking' : No locks are acquired for the operations.
|
|
|
|
|
* This can increase performance for non-critical writes.
|
2012-01-08 08:40:00 +00:00
|
|
|
* This has no effect unless the 'force' flag is set.
|
2011-12-23 09:43:28 +00:00
|
|
|
* 'allowStale' : Don't require the latest available data.
|
|
|
|
|
* This can increase performance for non-critical writes.
|
2012-01-08 08:40:00 +00:00
|
|
|
* This has no effect unless the 'force' flag is set.
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
2012-01-26 21:05:03 +00:00
|
|
|
* Remarks on locking:
|
2012-01-15 22:45:14 +00:00
|
|
|
* File system paths given to operations should refer to files that are
|
2012-01-26 21:05:03 +00:00
|
|
|
* already locked or otherwise safe from modification from other processes.
|
2012-01-15 22:45:14 +00:00
|
|
|
* Normally these files will be new temp files, which should be adequate.
|
|
|
|
|
*
|
2011-12-20 03:52:06 +00:00
|
|
|
* Return value:
|
|
|
|
|
* This returns a Status, which contains all warnings and fatals that occured
|
|
|
|
|
* during the operation. The 'failCount', 'successCount', and 'success' members
|
2012-03-08 22:31:04 +00:00
|
|
|
* will reflect each operation attempted. The status will be "OK" unless:
|
|
|
|
|
* a) unexpected operation errors occurred (network partitions, disk full...)
|
|
|
|
|
* b) significant operation errors occured and 'force' was not set
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
|
|
|
|
* @param $ops Array List of operations to execute in order
|
|
|
|
|
* @param $opts Array Batch operation options
|
|
|
|
|
* @return Status
|
|
|
|
|
*/
|
2011-12-23 09:43:28 +00:00
|
|
|
final public function doOperations( array $ops, array $opts = array() ) {
|
2012-02-06 05:25:26 +00:00
|
|
|
if ( $this->isReadOnly() ) {
|
2012-01-04 02:15:07 +00:00
|
|
|
return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
|
|
|
|
|
}
|
2012-01-08 08:40:00 +00:00
|
|
|
if ( empty( $opts['force'] ) ) { // sanity
|
2011-12-23 09:43:28 +00:00
|
|
|
unset( $opts['nonLocking'] );
|
|
|
|
|
unset( $opts['allowStale'] );
|
|
|
|
|
}
|
|
|
|
|
return $this->doOperationsInternal( $ops, $opts );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-01-29 22:22:28 +00:00
|
|
|
* @see FileBackend::doOperations()
|
2011-12-23 09:43:28 +00:00
|
|
|
*/
|
|
|
|
|
abstract protected function doOperationsInternal( array $ops, array $opts );
|
2011-12-20 03:52:06 +00:00
|
|
|
|
|
|
|
|
/**
|
2011-12-21 09:16:28 +00:00
|
|
|
* Same as doOperations() except it takes a single operation.
|
|
|
|
|
* If you are doing a batch of operations that should either
|
|
|
|
|
* all succeed or all fail, then use that function instead.
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
2012-01-29 22:22:28 +00:00
|
|
|
* @see FileBackend::doOperations()
|
2011-12-21 09:16:28 +00:00
|
|
|
*
|
|
|
|
|
* @param $op Array Operation
|
|
|
|
|
* @param $opts Array Operation options
|
2011-12-20 03:52:06 +00:00
|
|
|
* @return Status
|
|
|
|
|
*/
|
|
|
|
|
final public function doOperation( array $op, array $opts = array() ) {
|
|
|
|
|
return $this->doOperations( array( $op ), $opts );
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-08 09:25:15 +00:00
|
|
|
/**
|
|
|
|
|
* Performs a single create operation.
|
|
|
|
|
* This sets $params['op'] to 'create' and passes it to doOperation().
|
|
|
|
|
*
|
2012-01-29 22:22:28 +00:00
|
|
|
* @see FileBackend::doOperation()
|
2012-01-08 09:25:15 +00:00
|
|
|
*
|
|
|
|
|
* @param $params Array Operation parameters
|
|
|
|
|
* @param $opts Array Operation options
|
|
|
|
|
* @return Status
|
|
|
|
|
*/
|
|
|
|
|
final public function create( array $params, array $opts = array() ) {
|
|
|
|
|
$params['op'] = 'create';
|
|
|
|
|
return $this->doOperation( $params, $opts );
|
|
|
|
|
}
|
|
|
|
|
|
2011-12-21 09:16:28 +00:00
|
|
|
/**
|
|
|
|
|
* Performs a single store operation.
|
|
|
|
|
* This sets $params['op'] to 'store' and passes it to doOperation().
|
|
|
|
|
*
|
2012-01-29 22:22:28 +00:00
|
|
|
* @see FileBackend::doOperation()
|
2011-12-21 09:16:28 +00:00
|
|
|
*
|
|
|
|
|
* @param $params Array Operation parameters
|
|
|
|
|
* @param $opts Array Operation options
|
|
|
|
|
* @return Status
|
|
|
|
|
*/
|
|
|
|
|
final public function store( array $params, array $opts = array() ) {
|
|
|
|
|
$params['op'] = 'store';
|
|
|
|
|
return $this->doOperation( $params, $opts );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Performs a single copy operation.
|
|
|
|
|
* This sets $params['op'] to 'copy' and passes it to doOperation().
|
|
|
|
|
*
|
2012-01-29 22:22:28 +00:00
|
|
|
* @see FileBackend::doOperation()
|
2011-12-21 09:16:28 +00:00
|
|
|
*
|
|
|
|
|
* @param $params Array Operation parameters
|
|
|
|
|
* @param $opts Array Operation options
|
|
|
|
|
* @return Status
|
|
|
|
|
*/
|
|
|
|
|
final public function copy( array $params, array $opts = array() ) {
|
|
|
|
|
$params['op'] = 'copy';
|
|
|
|
|
return $this->doOperation( $params, $opts );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Performs a single move operation.
|
|
|
|
|
* This sets $params['op'] to 'move' and passes it to doOperation().
|
|
|
|
|
*
|
2012-01-29 22:22:28 +00:00
|
|
|
* @see FileBackend::doOperation()
|
2011-12-21 09:16:28 +00:00
|
|
|
*
|
|
|
|
|
* @param $params Array Operation parameters
|
|
|
|
|
* @param $opts Array Operation options
|
|
|
|
|
* @return Status
|
|
|
|
|
*/
|
|
|
|
|
final public function move( array $params, array $opts = array() ) {
|
|
|
|
|
$params['op'] = 'move';
|
|
|
|
|
return $this->doOperation( $params, $opts );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Performs a single delete operation.
|
|
|
|
|
* This sets $params['op'] to 'delete' and passes it to doOperation().
|
|
|
|
|
*
|
2012-01-29 22:22:28 +00:00
|
|
|
* @see FileBackend::doOperation()
|
2011-12-21 09:16:28 +00:00
|
|
|
*
|
|
|
|
|
* @param $params Array Operation parameters
|
|
|
|
|
* @param $opts Array Operation options
|
|
|
|
|
* @return Status
|
|
|
|
|
*/
|
|
|
|
|
final public function delete( array $params, array $opts = array() ) {
|
|
|
|
|
$params['op'] = 'delete';
|
|
|
|
|
return $this->doOperation( $params, $opts );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-01-29 21:28:31 +00:00
|
|
|
* Concatenate a list of storage files into a single file system file.
|
|
|
|
|
* The target path should refer to a file that is already locked or
|
|
|
|
|
* otherwise safe from modification from other processes. Normally,
|
|
|
|
|
* the file will be a new temp file, which should be adequate.
|
2012-01-08 09:25:15 +00:00
|
|
|
* $params include:
|
|
|
|
|
* srcs : ordered source storage paths (e.g. chunk1, chunk2, ...)
|
|
|
|
|
* dst : file system path to 0-byte temp file
|
2011-12-21 09:16:28 +00:00
|
|
|
*
|
|
|
|
|
* @param $params Array Operation parameters
|
|
|
|
|
* @return Status
|
|
|
|
|
*/
|
2012-01-08 09:25:15 +00:00
|
|
|
abstract public function concatenate( array $params );
|
2011-12-21 09:16:28 +00:00
|
|
|
|
2011-12-20 03:52:06 +00:00
|
|
|
/**
|
2012-01-17 05:34:01 +00:00
|
|
|
* Prepare a storage directory for usage.
|
|
|
|
|
* This will create any required containers and parent directories.
|
|
|
|
|
* Backends using key/value stores only need to create the container.
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
|
|
|
|
* $params include:
|
|
|
|
|
* dir : storage directory
|
|
|
|
|
*
|
|
|
|
|
* @param $params Array
|
|
|
|
|
* @return Status
|
|
|
|
|
*/
|
2012-01-09 00:20:28 +00:00
|
|
|
final public function prepare( array $params ) {
|
2012-02-06 05:25:26 +00:00
|
|
|
if ( $this->isReadOnly() ) {
|
2012-01-09 00:20:28 +00:00
|
|
|
return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
|
|
|
|
|
}
|
|
|
|
|
return $this->doPrepare( $params );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-01-29 22:22:28 +00:00
|
|
|
* @see FileBackend::prepare()
|
2012-01-09 00:20:28 +00:00
|
|
|
*/
|
|
|
|
|
abstract protected function doPrepare( array $params );
|
2011-12-20 03:52:06 +00:00
|
|
|
|
|
|
|
|
/**
|
2012-01-13 04:32:28 +00:00
|
|
|
* Take measures to block web access to a storage directory and
|
2011-12-20 03:52:06 +00:00
|
|
|
* the container it belongs to. FS backends might add .htaccess
|
2012-01-17 05:34:01 +00:00
|
|
|
* files whereas key/value store backends might restrict container
|
|
|
|
|
* access to the auth user that represents end-users in web request.
|
2011-12-20 03:52:06 +00:00
|
|
|
* This is not guaranteed to actually do anything.
|
|
|
|
|
*
|
|
|
|
|
* $params include:
|
|
|
|
|
* dir : storage directory
|
|
|
|
|
* noAccess : try to deny file access
|
|
|
|
|
* noListing : try to deny file listing
|
|
|
|
|
*
|
|
|
|
|
* @param $params Array
|
|
|
|
|
* @return Status
|
|
|
|
|
*/
|
2012-01-09 00:20:28 +00:00
|
|
|
final public function secure( array $params ) {
|
2012-02-06 05:25:26 +00:00
|
|
|
if ( $this->isReadOnly() ) {
|
2012-01-09 00:20:28 +00:00
|
|
|
return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
|
|
|
|
|
}
|
2012-01-13 23:30:46 +00:00
|
|
|
$status = $this->doPrepare( $params ); // dir must exist to restrict it
|
|
|
|
|
if ( $status->isOK() ) {
|
|
|
|
|
$status->merge( $this->doSecure( $params ) );
|
|
|
|
|
}
|
|
|
|
|
return $status;
|
2012-01-09 00:20:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-01-29 22:22:28 +00:00
|
|
|
* @see FileBackend::secure()
|
2012-01-09 00:20:28 +00:00
|
|
|
*/
|
|
|
|
|
abstract protected function doSecure( array $params );
|
2011-12-20 03:52:06 +00:00
|
|
|
|
|
|
|
|
/**
|
2012-01-17 05:34:01 +00:00
|
|
|
* Delete a storage directory if it is empty.
|
|
|
|
|
* Backends using key/value stores may do nothing unless the directory
|
|
|
|
|
* is that of an empty container, in which case it should be deleted.
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
|
|
|
|
* $params include:
|
|
|
|
|
* dir : storage directory
|
|
|
|
|
*
|
|
|
|
|
* @param $params Array
|
|
|
|
|
* @return Status
|
|
|
|
|
*/
|
2012-01-09 00:20:28 +00:00
|
|
|
final public function clean( array $params ) {
|
2012-02-06 05:25:26 +00:00
|
|
|
if ( $this->isReadOnly() ) {
|
2012-01-09 00:20:28 +00:00
|
|
|
return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
|
|
|
|
|
}
|
|
|
|
|
return $this->doClean( $params );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-01-29 22:22:28 +00:00
|
|
|
* @see FileBackend::clean()
|
2012-01-09 00:20:28 +00:00
|
|
|
*/
|
|
|
|
|
abstract protected function doClean( array $params );
|
2011-12-20 03:52:06 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if a file exists at a storage path in the backend.
|
2012-01-04 08:07:52 +00:00
|
|
|
* This returns false if only a directory exists at the path.
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
|
|
|
|
* $params include:
|
2011-12-23 09:43:28 +00:00
|
|
|
* src : source storage path
|
|
|
|
|
* latest : use the latest available data
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
|
|
|
|
* @param $params Array
|
2011-12-26 23:35:40 +00:00
|
|
|
* @return bool|null Returns null on failure
|
2011-12-20 03:52:06 +00:00
|
|
|
*/
|
|
|
|
|
abstract public function fileExists( array $params );
|
|
|
|
|
|
|
|
|
|
/**
|
2012-01-04 02:15:07 +00:00
|
|
|
* Get the last-modified timestamp of the file at a storage path.
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
|
|
|
|
* $params include:
|
2011-12-23 09:43:28 +00:00
|
|
|
* src : source storage path
|
|
|
|
|
* latest : use the latest available data
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
|
|
|
|
* @param $params Array
|
2012-02-09 18:01:54 +00:00
|
|
|
* @return string|bool TS_MW timestamp or false on failure
|
2011-12-20 03:52:06 +00:00
|
|
|
*/
|
2012-01-04 02:15:07 +00:00
|
|
|
abstract public function getFileTimestamp( array $params );
|
2011-12-20 03:52:06 +00:00
|
|
|
|
|
|
|
|
/**
|
2012-01-04 02:15:07 +00:00
|
|
|
* Get the contents of a file at a storage path in the backend.
|
|
|
|
|
* This should be avoided for potentially large files.
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
|
|
|
|
* $params include:
|
2011-12-23 09:43:28 +00:00
|
|
|
* src : source storage path
|
|
|
|
|
* latest : use the latest available data
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
|
|
|
|
* @param $params Array
|
2012-02-09 18:01:54 +00:00
|
|
|
* @return string|bool Returns false on failure
|
2011-12-20 03:52:06 +00:00
|
|
|
*/
|
2012-01-04 02:15:07 +00:00
|
|
|
abstract public function getFileContents( array $params );
|
|
|
|
|
|
2012-01-08 08:40:00 +00:00
|
|
|
/**
|
|
|
|
|
* Get the size (bytes) of a file at a storage path in the backend.
|
|
|
|
|
*
|
|
|
|
|
* $params include:
|
|
|
|
|
* src : source storage path
|
|
|
|
|
* latest : use the latest available data
|
|
|
|
|
*
|
|
|
|
|
* @param $params Array
|
2012-02-09 18:01:54 +00:00
|
|
|
* @return integer|bool Returns false on failure
|
2012-01-08 08:40:00 +00:00
|
|
|
*/
|
|
|
|
|
abstract public function getFileSize( array $params );
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get quick information about a file at a storage path in the backend.
|
2012-01-10 02:18:27 +00:00
|
|
|
* If the file does not exist, then this returns false.
|
|
|
|
|
* Otherwise, the result is an associative array that includes:
|
|
|
|
|
* mtime : the last-modified timestamp (TS_MW)
|
|
|
|
|
* size : the file size (bytes)
|
2012-01-19 23:18:03 +00:00
|
|
|
* Additional values may be included for internal use only.
|
2012-01-08 08:40:00 +00:00
|
|
|
*
|
|
|
|
|
* $params include:
|
|
|
|
|
* src : source storage path
|
|
|
|
|
* latest : use the latest available data
|
|
|
|
|
*
|
|
|
|
|
* @param $params Array
|
2012-02-09 18:01:54 +00:00
|
|
|
* @return Array|bool|null Returns null on failure
|
2012-01-08 08:40:00 +00:00
|
|
|
*/
|
|
|
|
|
abstract public function getFileStat( array $params );
|
|
|
|
|
|
2012-01-04 02:15:07 +00:00
|
|
|
/**
|
|
|
|
|
* Get a SHA-1 hash of the file at a storage path in the backend.
|
|
|
|
|
*
|
|
|
|
|
* $params include:
|
|
|
|
|
* src : source storage path
|
|
|
|
|
* latest : use the latest available data
|
|
|
|
|
*
|
|
|
|
|
* @param $params Array
|
2012-02-09 18:01:54 +00:00
|
|
|
* @return string|bool Hash string or false on failure
|
2012-01-04 02:15:07 +00:00
|
|
|
*/
|
|
|
|
|
abstract public function getFileSha1Base36( array $params );
|
2011-12-20 03:52:06 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the properties of the file at a storage path in the backend.
|
|
|
|
|
* Returns FSFile::placeholderProps() on failure.
|
|
|
|
|
*
|
|
|
|
|
* $params include:
|
2011-12-23 09:43:28 +00:00
|
|
|
* src : source storage path
|
|
|
|
|
* latest : use the latest available data
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
|
|
|
|
* @param $params Array
|
|
|
|
|
* @return Array
|
|
|
|
|
*/
|
|
|
|
|
abstract public function getFileProps( array $params );
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Stream the file at a storage path in the backend.
|
2012-01-08 08:40:00 +00:00
|
|
|
* If the file does not exists, a 404 error will be given.
|
2011-12-20 03:52:06 +00:00
|
|
|
* Appropriate HTTP headers (Status, Content-Type, Content-Length)
|
|
|
|
|
* must be sent if streaming began, while none should be sent otherwise.
|
|
|
|
|
* Implementations should flush the output buffer before sending data.
|
|
|
|
|
*
|
|
|
|
|
* $params include:
|
|
|
|
|
* src : source storage path
|
|
|
|
|
* headers : additional HTTP headers to send on success
|
2011-12-23 09:43:28 +00:00
|
|
|
* latest : use the latest available data
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
|
|
|
|
* @param $params Array
|
|
|
|
|
* @return Status
|
|
|
|
|
*/
|
|
|
|
|
abstract public function streamFile( array $params );
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a file system file, identical to the file at a storage path.
|
|
|
|
|
* The file returned is either:
|
|
|
|
|
* a) A local copy of the file at a storage path in the backend.
|
|
|
|
|
* The temporary copy will have the same extension as the source.
|
|
|
|
|
* b) An original of the file at a storage path in the backend.
|
|
|
|
|
* Temporary files may be purged when the file object falls out of scope.
|
|
|
|
|
*
|
|
|
|
|
* Write operations should *never* be done on this file as some backends
|
|
|
|
|
* may do internal tracking or may be instances of FileBackendMultiWrite.
|
|
|
|
|
* In that later case, there are copies of the file that must stay in sync.
|
2012-01-29 19:23:26 +00:00
|
|
|
* Additionally, further calls to this function may return the same file.
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
|
|
|
|
* $params include:
|
2011-12-23 09:43:28 +00:00
|
|
|
* src : source storage path
|
|
|
|
|
* latest : use the latest available data
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
|
|
|
|
* @param $params Array
|
|
|
|
|
* @return FSFile|null Returns null on failure
|
|
|
|
|
*/
|
|
|
|
|
abstract public function getLocalReference( array $params );
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a local copy on disk of the file at a storage path in the backend.
|
|
|
|
|
* The temporary copy will have the same file extension as the source.
|
|
|
|
|
* Temporary files may be purged when the file object falls out of scope.
|
|
|
|
|
*
|
|
|
|
|
* $params include:
|
2011-12-23 09:43:28 +00:00
|
|
|
* src : source storage path
|
|
|
|
|
* latest : use the latest available data
|
2011-12-20 03:52:06 +00:00
|
|
|
*
|
|
|
|
|
* @param $params Array
|
|
|
|
|
* @return TempFSFile|null Returns null on failure
|
|
|
|
|
*/
|
|
|
|
|
abstract public function getLocalCopy( array $params );
|
|
|
|
|
|
2011-12-23 09:43:28 +00:00
|
|
|
/**
|
2012-01-07 01:33:23 +00:00
|
|
|
* Get an iterator to list out all stored files under a storage directory.
|
2012-02-08 22:13:03 +00:00
|
|
|
* If the directory is of the form "mwstore://backend/container",
|
|
|
|
|
* then all files in the container should be listed.
|
|
|
|
|
* If the directory is of form "mwstore://backend/container/dir",
|
|
|
|
|
* then all files under that container directory should be listed.
|
2011-12-23 09:43:28 +00:00
|
|
|
* Results should be storage paths relative to the given directory.
|
|
|
|
|
*
|
2012-02-08 22:13:03 +00:00
|
|
|
* Storage backends with eventual consistency might return stale data.
|
|
|
|
|
*
|
2011-12-23 09:43:28 +00:00
|
|
|
* $params include:
|
|
|
|
|
* dir : storage path directory
|
|
|
|
|
*
|
|
|
|
|
* @return Traversable|Array|null Returns null on failure
|
|
|
|
|
*/
|
|
|
|
|
abstract public function getFileList( array $params );
|
|
|
|
|
|
2012-01-08 22:10:53 +00:00
|
|
|
/**
|
|
|
|
|
* Invalidate any in-process file existence and property cache.
|
|
|
|
|
* If $paths is given, then only the cache for those files will be cleared.
|
|
|
|
|
*
|
2012-01-20 21:55:15 +00:00
|
|
|
* @param $paths Array Storage paths (optional)
|
2012-01-12 19:41:18 +00:00
|
|
|
* @return void
|
2012-01-08 22:10:53 +00:00
|
|
|
*/
|
2012-01-26 21:05:03 +00:00
|
|
|
public function clearCache( array $paths = null ) {}
|
2012-01-08 22:10:53 +00:00
|
|
|
|
2011-12-20 03:52:06 +00:00
|
|
|
/**
|
|
|
|
|
* Lock the files at the given storage paths in the backend.
|
|
|
|
|
* This will either lock all the files or none (on failure).
|
|
|
|
|
*
|
|
|
|
|
* Callers should consider using getScopedFileLocks() instead.
|
|
|
|
|
*
|
|
|
|
|
* @param $paths Array Storage paths
|
2011-12-22 23:18:57 +00:00
|
|
|
* @param $type integer LockManager::LOCK_* constant
|
2011-12-20 03:52:06 +00:00
|
|
|
* @return Status
|
|
|
|
|
*/
|
|
|
|
|
final public function lockFiles( array $paths, $type ) {
|
|
|
|
|
return $this->lockManager->lock( $paths, $type );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Unlock the files at the given storage paths in the backend.
|
|
|
|
|
*
|
|
|
|
|
* @param $paths Array Storage paths
|
2011-12-22 23:18:57 +00:00
|
|
|
* @param $type integer LockManager::LOCK_* constant
|
2011-12-20 03:52:06 +00:00
|
|
|
* @return Status
|
|
|
|
|
*/
|
|
|
|
|
final public function unlockFiles( array $paths, $type ) {
|
|
|
|
|
return $this->lockManager->unlock( $paths, $type );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Lock the files at the given storage paths in the backend.
|
|
|
|
|
* This will either lock all the files or none (on failure).
|
|
|
|
|
* On failure, the status object will be updated with errors.
|
|
|
|
|
*
|
|
|
|
|
* Once the return value goes out scope, the locks will be released and
|
|
|
|
|
* the status updated. Unlock fatals will not change the status "OK" value.
|
|
|
|
|
*
|
|
|
|
|
* @param $paths Array Storage paths
|
2011-12-22 23:18:57 +00:00
|
|
|
* @param $type integer LockManager::LOCK_* constant
|
2011-12-20 03:52:06 +00:00
|
|
|
* @param $status Status Status to update on lock/unlock
|
|
|
|
|
* @return ScopedLock|null Returns null on failure
|
|
|
|
|
*/
|
|
|
|
|
final public function getScopedFileLocks( array $paths, $type, Status $status ) {
|
|
|
|
|
return ScopedLock::factory( $this->lockManager, $paths, $type, $status );
|
|
|
|
|
}
|
2012-01-29 21:28:31 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if a given path is a "mwstore://" path.
|
|
|
|
|
* This does not do any further validation or any existence checks.
|
|
|
|
|
*
|
|
|
|
|
* @param $path string
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
final public static function isStoragePath( $path ) {
|
|
|
|
|
return ( strpos( $path, 'mwstore://' ) === 0 );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Split a storage path into a backend name, a container name,
|
|
|
|
|
* and a relative file path. The relative path may be the empty string.
|
|
|
|
|
* This does not do any path normalization or traversal checks.
|
|
|
|
|
*
|
|
|
|
|
* @param $storagePath string
|
|
|
|
|
* @return Array (backend, container, rel object) or (null, null, null)
|
|
|
|
|
*/
|
|
|
|
|
final public static function splitStoragePath( $storagePath ) {
|
|
|
|
|
if ( self::isStoragePath( $storagePath ) ) {
|
|
|
|
|
// Remove the "mwstore://" prefix and split the path
|
|
|
|
|
$parts = explode( '/', substr( $storagePath, 10 ), 3 );
|
|
|
|
|
if ( count( $parts ) >= 2 && $parts[0] != '' && $parts[1] != '' ) {
|
|
|
|
|
if ( count( $parts ) == 3 ) {
|
|
|
|
|
return $parts; // e.g. "backend/container/path"
|
|
|
|
|
} else {
|
|
|
|
|
return array( $parts[0], $parts[1], '' ); // e.g. "backend/container"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return array( null, null, null );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Normalize a storage path by cleaning up directory separators.
|
|
|
|
|
* Returns null if the path is not of the format of a valid storage path.
|
|
|
|
|
*
|
|
|
|
|
* @param $storagePath string
|
|
|
|
|
* @return string|null
|
|
|
|
|
*/
|
|
|
|
|
final public static function normalizeStoragePath( $storagePath ) {
|
|
|
|
|
list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath );
|
|
|
|
|
if ( $relPath !== null ) { // must be for this backend
|
|
|
|
|
$relPath = self::normalizeContainerPath( $relPath );
|
|
|
|
|
if ( $relPath !== null ) {
|
|
|
|
|
return ( $relPath != '' )
|
|
|
|
|
? "mwstore://{$backend}/{$container}/{$relPath}"
|
|
|
|
|
: "mwstore://{$backend}/{$container}";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-03 19:14:50 +00:00
|
|
|
/**
|
|
|
|
|
* Get the parent storage directory of a storage path.
|
|
|
|
|
* This returns a path like "mwstore://backend/container",
|
|
|
|
|
* "mwstore://backend/container/...", or null if there is no parent.
|
|
|
|
|
*
|
|
|
|
|
* @param $storagePath string
|
|
|
|
|
* @return string|null
|
|
|
|
|
*/
|
|
|
|
|
final public static function parentStoragePath( $storagePath ) {
|
|
|
|
|
$storagePath = dirname( $storagePath );
|
|
|
|
|
list( $b, $cont, $rel ) = self::splitStoragePath( $storagePath );
|
|
|
|
|
return ( $rel === null ) ? null : $storagePath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the final extension from a storage or FS path
|
|
|
|
|
*
|
|
|
|
|
* @param $path string
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
final public static function extensionFromPath( $path ) {
|
|
|
|
|
$i = strrpos( $path, '.' );
|
|
|
|
|
return strtolower( $i ? substr( $path, $i + 1 ) : '' );
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-29 21:28:31 +00:00
|
|
|
/**
|
|
|
|
|
* Validate and normalize a relative storage path.
|
|
|
|
|
* Null is returned if the path involves directory traversal.
|
|
|
|
|
* Traversal is insecure for FS backends and broken for others.
|
|
|
|
|
*
|
|
|
|
|
* @param $path string Storage path relative to a container
|
|
|
|
|
* @return string|null
|
|
|
|
|
*/
|
|
|
|
|
final protected static function normalizeContainerPath( $path ) {
|
|
|
|
|
// Normalize directory separators
|
|
|
|
|
$path = strtr( $path, '\\', '/' );
|
|
|
|
|
// Collapse any consecutive directory separators
|
|
|
|
|
$path = preg_replace( '![/]{2,}!', '/', $path );
|
|
|
|
|
// Remove any leading directory separator
|
|
|
|
|
$path = ltrim( $path, '/' );
|
|
|
|
|
// Use the same traversal protection as Title::secureAndSplit()
|
|
|
|
|
if ( strpos( $path, '.' ) !== false ) {
|
|
|
|
|
if (
|
|
|
|
|
$path === '.' ||
|
|
|
|
|
$path === '..' ||
|
|
|
|
|
strpos( $path, './' ) === 0 ||
|
|
|
|
|
strpos( $path, '../' ) === 0 ||
|
|
|
|
|
strpos( $path, '/./' ) !== false ||
|
|
|
|
|
strpos( $path, '/../' ) !== false
|
|
|
|
|
) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $path;
|
|
|
|
|
}
|
2011-12-20 03:52:06 +00:00
|
|
|
}
|