Go to the documentation of this file.
31 use Psr\Log\LoggerAwareInterface;
32 use Psr\Log\LoggerInterface;
33 use Psr\Log\NullLogger;
34 use Wikimedia\ScopedCallback;
139 protected const STAT_ABSENT =
false;
142 public const STAT_ERROR =
null;
144 public const LIST_ERROR =
null;
146 public const TEMPURL_ERROR =
null;
148 public const EXISTENCE_ERROR =
null;
151 public const TIMESTAMP_FAIL =
false;
153 public const CONTENT_FAIL =
false;
155 public const XATTRS_FAIL =
false;
157 public const SIZE_FAIL =
false;
159 public const SHA1_FAIL =
false;
197 if ( !array_key_exists(
'name', $config ) ) {
198 throw new InvalidArgumentException(
'Backend name not specified.' );
200 $this->name = $config[
'name'];
201 $this->domainId = $config[
'domainId']
204 if ( !preg_match(
'!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) {
205 throw new InvalidArgumentException(
"Backend name '{$this->name}' is invalid." );
207 if ( !is_string( $this->domainId ) ) {
208 throw new InvalidArgumentException(
209 "Backend domain ID not provided for '{$this->name}'." );
211 $this->lockManager = $config[
'lockManager'] ??
new NullLockManager( [] );
213 $this->readOnly = isset( $config[
'readOnly'] )
214 ? (string)$config[
'readOnly']
216 $this->parallelize = isset( $config[
'parallelize'] )
217 ? (string)$config[
'parallelize']
219 $this->concurrency = isset( $config[
'concurrency'] )
220 ? (int)$config[
'concurrency']
222 $this->obResetFunc = $config[
'obResetFunc'] ?? [ $this,
'resetOutputBuffer' ];
223 $this->streamMimeFunc = $config[
'streamMimeFunc'] ??
null;
225 $this->profiler = $config[
'profiler'] ??
null;
226 if ( !is_callable( $this->profiler ) ) {
227 $this->profiler =
null;
229 $this->logger = $config[
'logger'] ??
new NullLogger();
230 $this->statusWrapper = $config[
'statusWrapper'] ??
null;
232 if ( isset( $config[
'tmpDirectory'] ) ) {
282 return ( $this->readOnly !=
'' );
291 return ( $this->readOnly !=
'' ) ? $this->readOnly :
false;
313 return ( $this->
getFeatures() & $bitfield ) === $bitfield;
469 if ( empty( $opts[
'bypassReadOnly'] ) && $this->
isReadOnly() ) {
470 return $this->
newStatus(
'backend-fail-readonly', $this->name, $this->readOnly );
477 if ( empty( $opts[
'force'] ) ) {
478 unset( $opts[
'nonLocking'] );
482 $scope = ScopedCallback::newScopedIgnoreUserAbort();
506 final public function doOperation( array $op, array $opts = [] ) {
520 final public function create( array $params, array $opts = [] ) {
521 return $this->
doOperation( [
'op' =>
'create' ] + $params, $opts );
534 final public function store( array $params, array $opts = [] ) {
535 return $this->
doOperation( [
'op' =>
'store' ] + $params, $opts );
548 final public function copy( array $params, array $opts = [] ) {
549 return $this->
doOperation( [
'op' =>
'copy' ] + $params, $opts );
562 final public function move( array $params, array $opts = [] ) {
563 return $this->
doOperation( [
'op' =>
'move' ] + $params, $opts );
576 final public function delete( array $params, array $opts = [] ) {
577 return $this->
doOperation( [
'op' =>
'delete' ] + $params, $opts );
591 final public function describe( array $params, array $opts = [] ) {
592 return $this->
doOperation( [
'op' =>
'describe' ] + $params, $opts );
712 if ( empty( $opts[
'bypassReadOnly'] ) && $this->
isReadOnly() ) {
713 return $this->
newStatus(
'backend-fail-readonly', $this->name, $this->readOnly );
720 foreach ( $ops as &$op ) {
721 $op[
'overwrite'] =
true;
725 $scope = ScopedCallback::newScopedIgnoreUserAbort();
765 final public function quickCreate( array $params, array $opts = [] ) {
780 final public function quickStore( array $params, array $opts = [] ) {
795 final public function quickCopy( array $params, array $opts = [] ) {
810 final public function quickMove( array $params, array $opts = [] ) {
825 final public function quickDelete( array $params, array $opts = [] ) {
876 final public function prepare( array $params ) {
877 if ( empty( $params[
'bypassReadOnly'] ) && $this->
isReadOnly() ) {
878 return $this->
newStatus(
'backend-fail-readonly', $this->name, $this->readOnly );
881 $scope = ScopedCallback::newScopedIgnoreUserAbort();
908 final public function secure( array $params ) {
909 if ( empty( $params[
'bypassReadOnly'] ) && $this->
isReadOnly() ) {
910 return $this->
newStatus(
'backend-fail-readonly', $this->name, $this->readOnly );
913 $scope = ScopedCallback::newScopedIgnoreUserAbort();
922 abstract protected function doSecure( array $params );
942 final public function publish( array $params ) {
943 if ( empty( $params[
'bypassReadOnly'] ) && $this->
isReadOnly() ) {
944 return $this->
newStatus(
'backend-fail-readonly', $this->name, $this->readOnly );
947 $scope = ScopedCallback::newScopedIgnoreUserAbort();
969 final public function clean( array $params ) {
970 if ( empty( $params[
'bypassReadOnly'] ) && $this->
isReadOnly() ) {
971 return $this->
newStatus(
'backend-fail-readonly', $this->name, $this->readOnly );
974 $scope = ScopedCallback::newScopedIgnoreUserAbort();
975 return $this->
doClean( $params );
983 abstract protected function doClean( array $params );
1029 return $contents[$params[
'src']];
1164 return $fsFiles[$params[
'src']];
1199 $tmpFiles = $this->
getLocalCopyMulti( [
'srcs' => [ $params[
'src'] ] ] + $params );
1201 return $tmpFiles[$params[
'src']];
1351 return $this->
getFileList( [
'topOnly' =>
true ] + $params );
1402 $paths = array_map(
'FileBackend::normalizeStoragePath', $paths );
1404 return $this->
wrapStatus( $this->lockManager->lock( $paths,
$type, $timeout ) );
1415 $paths = array_map(
'FileBackend::normalizeStoragePath', $paths );
1417 return $this->
wrapStatus( $this->lockManager->unlock( $paths,
$type ) );
1439 if (
$type ===
'mixed' ) {
1440 foreach ( $paths as &$typePaths ) {
1441 $typePaths = array_map(
'FileBackend::normalizeStoragePath', $typePaths );
1444 $paths = array_map(
'FileBackend::normalizeStoragePath', $paths );
1476 return "mwstore://{$this->name}";
1509 foreach ( $ops as &$op ) {
1510 $src = $op[
'src'] ??
null;
1511 if ( $src instanceof
FSFile ) {
1512 $op[
'srcRef'] = $src;
1513 $op[
'src'] = $src->getPath();
1529 return ( strpos(
$path,
'mwstore://' ) === 0 );
1541 if ( self::isStoragePath( $storagePath ) ) {
1543 $parts = explode(
'/', substr( $storagePath, 10 ), 3 );
1544 if ( count( $parts ) >= 2 && $parts[0] !=
'' && $parts[1] !=
'' ) {
1545 if ( count( $parts ) == 3 ) {
1548 return [ $parts[0], $parts[1],
'' ];
1553 return [
null,
null, null ];
1565 if ( $relPath !==
null ) {
1567 if ( $relPath !==
null ) {
1568 return ( $relPath !=
'' )
1569 ?
"mwstore://{$backend}/{$container}/{$relPath}"
1570 :
"mwstore://{$backend}/{$container}";
1589 $storagePath = dirname( $storagePath );
1592 return ( $rel ===
null ) ? null : $storagePath;
1605 $i = strrpos(
$path,
'.' );
1606 $ext = $i ? substr(
$path, $i + 1 ) :
'';
1608 if ( $case ===
'lowercase' ) {
1610 } elseif ( $case ===
'uppercase' ) {
1625 return ( self::normalizeContainerPath(
$path ) !==
null );
1641 if ( !in_array(
$type, [
'inline',
'attachment' ] ) ) {
1642 throw new InvalidArgumentException(
"Invalid Content-Disposition type '$type'." );
1646 if ( strlen( $filename ) ) {
1647 $parts[] =
"filename*=UTF-8''" . rawurlencode( basename( $filename ) );
1650 return implode(
';', $parts );
1667 $path = preg_replace(
'![/]{2,}!',
'/',
$path );
1671 if ( strpos(
$path,
'.' ) !==
false ) {
1675 strpos(
$path,
'./' ) === 0 ||
1676 strpos(
$path,
'../' ) === 0 ||
1677 strpos(
$path,
'/./' ) !==
false ||
1678 strpos(
$path,
'/../' ) !==
false
1696 if ( count(
$args ) ) {
1710 return $this->statusWrapper ? call_user_func( $this->statusWrapper, $sv ) : $sv;
1727 while ( ob_get_status() ) {
1728 if ( !ob_end_clean() ) {
streamFile(array $params)
Stream the content of the file at a storage path in the backend.
static splitStoragePath( $storagePath)
Split a storage path into a backend name, a container name, and a relative file path.
Class for handling resource locking.
TempFSFileFactory $tmpFileFactory
doOperations(array $ops, array $opts=[])
This is the main entry point into the backend for write operations.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
static normalizeContainerPath( $path)
Validate and normalize a relative storage path.
static newFatal( $message,... $parameters)
Factory function for fatal errors.
quickStore(array $params, array $opts=[])
Performs a single quick store operation.
preloadFileStat(array $params)
Preload file stat information (concurrently if possible) into in-process cache.
Base class for all file backend classes (including multi-write backends).
describe(array $params, array $opts=[])
Performs a single describe operation.
unlockFiles(array $paths, $type)
Unlock the files at the given storage paths in the backend.
const ATTR_HEADERS
Bitfield flags for supported features.
directoryExists(array $params)
Check if a directory exists at a given storage path.
getLocalReferenceMulti(array $params)
Like getLocalReference() except it takes an array of storage paths and yields an order-preserved map ...
getFileStat(array $params)
Get quick information about a file at a storage path in the backend.
getFileXAttributes(array $params)
Get metadata about a file at a storage path in the backend.
publish(array $params)
Remove measures to block web access to a storage directory and the container it belongs to.
wrapStatus(StatusValue $sv)
resolveFSFileObjects(array $ops)
Convert FSFile 'src' paths to string paths (with an 'srcRef' field set to the FSFile)
getName()
Get the unique backend name.
getDirectoryList(array $params)
Get an iterator to list all directories under a storage directory.
Simple version of FileJournal that does nothing.
getScopedFileLocks(array $paths, $type, StatusValue $status, $timeout=0)
Lock the files at the given storage paths in the backend.
getDomainId()
Get the domain identifier used for this backend (possibly empty).
doQuickOperationsInternal(array $ops, array $opts)
string $readOnly
Read-only explanation message.
static extensionFromPath( $path, $case='lowercase')
Get the final extension from a storage or FS path.
static normalizeStoragePath( $storagePath)
Normalize a storage path by cleaning up directory separators.
getReadOnlyReason()
Get an explanatory message if this backend is read-only.
getFileHttpUrl(array $params)
Return an HTTP URL to a given file that requires no authentication to use.
doQuickOperation(array $op, array $opts=[])
Same as doQuickOperations() except it takes a single operation.
static factory(LockManager $manager, array $paths, $type, StatusValue $status, $timeout=0)
Get a ScopedLock object representing a lock on resource paths.
setLogger(LoggerInterface $logger)
move(array $params, array $opts=[])
Performs a single move operation.
store(array $params, array $opts=[])
Performs a single store operation.
getFileContentsMulti(array $params)
Like getFileContents() except it takes an array of storage paths and returns an order preserved map o...
doOperation(array $op, array $opts=[])
Same as doOperations() except it takes a single operation.
Simple version of LockManager that only does lock reference counting.
getFileTimestamp(array $params)
Get the last-modified timestamp of the file at a storage path.
getFileSize(array $params)
Get the size (bytes) of a file at a storage path in the backend.
static isStoragePath( $path)
Check if a given path is a "mwstore://" path.
int $concurrency
How many operations can be done in parallel.
create(array $params, array $opts=[])
Performs a single create operation.
Class for handling file operation journaling.
getFileSha1Base36(array $params)
Get a SHA-1 hash of the content of the file at a storage path in the backend.
getTopFileList(array $params)
Same as FileBackend::getFileList() except only lists files that are immediately under the given direc...
__construct(array $config)
Create a new backend instance from configuration.
hasFeatures( $bitfield)
Check if the backend medium supports a field of extra features.
doQuickOperations(array $ops, array $opts=[])
Perform a set of independent file operations on some files.
quickCopy(array $params, array $opts=[])
Performs a single quick copy operation.
quickMove(array $params, array $opts=[])
Performs a single quick move operation.
resetOutputBuffer()
Let's not reset output buffering during tests.
lockFiles(array $paths, $type, $timeout=0)
Lock the files at the given storage paths in the backend.
quickDescribe(array $params, array $opts=[])
Performs a single quick describe operation.
static newGood( $value=null)
Factory function for good results.
getLocalCopyMulti(array $params)
Like getLocalCopy() except it takes an array of storage paths and yields an order preserved-map of st...
fileExists(array $params)
Check if a file exists at a storage path in the backend.
preloadCache(array $paths)
Preload persistent file stat cache and property cache into in-process cache.
string $parallelize
When to do operations in parallel.
quickCreate(array $params, array $opts=[])
Performs a single quick create operation.
prepare(array $params)
Prepare a storage directory for usage.
Class representing a non-directory file on the file system.
doOperationsInternal(array $ops, array $opts)
newStatus(... $args)
Yields the result of the status wrapper callback on either:
clearCache(array $paths=null)
Invalidate any in-process file stat and property cache.
getFileList(array $params)
Get an iterator to list all stored files under a storage directory.
getRootStoragePath()
Get the root storage path of this backend.
getScopedLocksForOps(array $ops, StatusValue $status)
Get an array of scoped locks needed for a batch of file operations.
string $name
Unique backend name.
clean(array $params)
Delete a storage directory if it is empty.
getLocalCopy(array $params)
Get a local copy on disk of the file at a storage path in the backend.
getFileProps(array $params)
Get the properties of the content of the file at a storage path in the backend.
quickDelete(array $params, array $opts=[])
Performs a single quick delete operation.
static isPathTraversalFree( $path)
Check if a relative path has no directory traversals.
getJournal()
Get the file journal object for this backend.
concatenate(array $params)
Concatenate a list of storage files into a single file system file.
getTopDirectoryList(array $params)
Same as FileBackend::getDirectoryList() except only lists directories that are immediately under the ...
if(!is_readable( $file)) $ext
getLocalReference(array $params)
Returns a file system file, identical in content to the file at a storage path.
copy(array $params, array $opts=[])
Performs a single copy operation.
getFeatures()
Get the a bitfield of extra features supported by the backend medium Stable to override.
getContainerStoragePath( $container)
Get the storage path for the given container for this backend.
getFileContents(array $params)
Get the contents of a file at a storage path in the backend.
getWikiId()
Alias to getDomainId()
scopedProfileSection( $section)
secure(array $params)
Take measures to block web access to a storage directory and the container it belongs to.
static parentStoragePath( $storagePath)
Get the parent storage directory of a storage path.
string $domainId
Unique domain name.
isReadOnly()
Check if this backend is read-only.
static makeContentDisposition( $type, $filename='')
Build a Content-Disposition header value per RFC 6266.