X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Flibs%2Ffilebackend%2FFileBackend.php;h=6ab1707be6dda671501648dd118e0ed00846bc4d;hb=eb06fb858e8492453ddb66f9b813ef4724decced;hp=06a155a469ab3a9eadf2c7c9bf49133f8d0e2b10;hpb=390f206158028609fab6c94decad893033c41fd8;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/libs/filebackend/FileBackend.php b/includes/libs/filebackend/FileBackend.php index 06a155a469..6ab1707be6 100644 --- a/includes/libs/filebackend/FileBackend.php +++ b/includes/libs/filebackend/FileBackend.php @@ -27,9 +27,11 @@ * @file * @ingroup FileBackend */ +use MediaWiki\FileBackend\FSFile\TempFSFileFactory; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerInterface; use Wikimedia\ScopedCallback; +use Psr\Log\NullLogger; /** * @brief Base class for all file backend classes (including multi-write backends). @@ -105,8 +107,8 @@ abstract class FileBackend implements LoggerAwareInterface { /** @var int How many operations can be done in parallel */ protected $concurrency; - /** @var string Temporary file directory */ - protected $tmpDirectory; + /** @var TempFSFileFactory */ + protected $tmpFileFactory; /** @var LockManager */ protected $lockManager; @@ -114,7 +116,7 @@ abstract class FileBackend implements LoggerAwareInterface { protected $fileJournal; /** @var LoggerInterface */ protected $logger; - /** @var object|string Class name or object With profileIn/profileOut methods */ + /** @var callable|null */ protected $profiler; /** @var callable */ @@ -129,6 +131,29 @@ abstract class FileBackend implements LoggerAwareInterface { const ATTR_METADATA = 2; // files can be stored with metadata key/values const ATTR_UNICODE_PATHS = 4; // files can have Unicode paths (not just ASCII) + /** @var false Idiom for "no info; non-existant file" (since 1.34) */ + const STAT_ABSENT = false; + + /** @var null Idiom for "no info; I/O errors" (since 1.34) */ + const STAT_ERROR = null; + /** @var null Idiom for "no file/directory list; I/O errors" (since 1.34) */ + const LIST_ERROR = null; + /** @var null Idiom for "no temp URL; not supported or I/O errors" (since 1.34) */ + const TEMPURL_ERROR = null; + /** @var null Idiom for "existence unknown; I/O errors" (since 1.34) */ + const EXISTENCE_ERROR = null; + + /** @var false Idiom for "no timestamp; missing file or I/O errors" (since 1.34) */ + const TIMESTAMP_FAIL = false; + /** @var false Idiom for "no content; missing file or I/O errors" (since 1.34) */ + const CONTENT_FAIL = false; + /** @var false Idiom for "no metadata; missing file or I/O errors" (since 1.34) */ + const XATTRS_FAIL = false; + /** @var false Idiom for "no size; missing file or I/O errors" (since 1.34) */ + const SIZE_FAIL = false; + /** @var false Idiom for "no SHA1 hash; missing file or I/O errors" (since 1.34) */ + const SHA1_FAIL = false; + /** * Create a new backend instance from configuration. * This should only be called from within FileBackendGroup. @@ -151,19 +176,21 @@ abstract class FileBackend implements LoggerAwareInterface { * - parallelize : When to do file operations in parallel (when possible). * Allowed values are "implicit", "explicit" and "off". * - concurrency : How many file operations can be done in parallel. - * - tmpDirectory : Directory to use for temporary files. If this is not set or null, - * then the backend will try to discover a usable temporary directory. + * - tmpDirectory : Directory to use for temporary files. + * - tmpFileFactory : Optional TempFSFileFactory object. Only has an effect if + * tmpDirectory is not set. If both are unset or null, then the backend will + * try to discover a usable temporary directory. * - obResetFunc : alternative callback to clear the output buffer * - streamMimeFunc : alternative method to determine the content type from the path * - logger : Optional PSR logger object. - * - profiler : Optional class name or object With profileIn/profileOut methods. + * - profiler : Optional callback that takes a section name argument and returns + * a ScopedCallback instance that ends the profile section in its destructor. * @throws InvalidArgumentException */ public function __construct( array $config ) { $this->name = $config['name']; - $this->domainId = isset( $config['domainId'] ) - ? $config['domainId'] // e.g. "my_wiki-en_" - : $config['wikiId']; // b/c alias + $this->domainId = $config['domainId'] // e.g. "my_wiki-en_" + ?? $config['wikiId']; // b/c alias if ( !preg_match( '!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) { throw new InvalidArgumentException( "Backend name '{$this->name}' is invalid." ); } elseif ( !is_string( $this->domainId ) ) { @@ -187,9 +214,17 @@ abstract class FileBackend implements LoggerAwareInterface { $this->statusWrapper = $config['statusWrapper'] ?? null; $this->profiler = $config['profiler'] ?? null; - $this->logger = $config['logger'] ?? new \Psr\Log\NullLogger(); + if ( !is_callable( $this->profiler ) ) { + $this->profiler = null; + } + $this->logger = $config['logger'] ?? new NullLogger(); $this->statusWrapper = $config['statusWrapper'] ?? null; - $this->tmpDirectory = $config['tmpDirectory'] ?? null; + // tmpDirectory gets precedence for backward compatibility + if ( isset( $config['tmpDirectory'] ) ) { + $this->tmpFileFactory = new TempFSFileFactory( $config['tmpDirectory'] ); + } else { + $this->tmpFileFactory = $config['tmpFileFactory'] ?? new TempFSFileFactory(); + } } public function setLogger( LoggerInterface $logger ) { @@ -197,7 +232,8 @@ abstract class FileBackend implements LoggerAwareInterface { } /** - * Get the unique backend name. + * 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. * @@ -219,8 +255,10 @@ abstract class FileBackend implements LoggerAwareInterface { /** * Alias to getDomainId() + * * @return string * @since 1.20 + * @deprecated Since 1.34 Use getDomainId() */ final public function getWikiId() { return $this->getDomainId(); @@ -407,10 +445,14 @@ abstract class FileBackend implements LoggerAwareInterface { * * The StatusValue will be "OK" unless: * - a) unexpected operation errors occurred (network partitions, disk full...) - * - b) significant operation errors occurred and 'force' was not set + * - b) predicted operation errors occurred and 'force' was not set * * @param array $ops List of operations to execute in order + * @codingStandardsIgnoreStart + * @phan-param array{ignoreMissingSource?:bool,overwrite?:bool,overwriteSame?:bool,headers?:bool} $ops * @param array $opts Batch operation options + * @phan-param array{force?:bool,nonLocking?:bool,nonJournaled?:bool,parallelize?:bool,bypassReadOnly?:bool,preserveCache?:bool} $opts + * @codingStandardsIgnoreEnd * @return StatusValue */ final public function doOperations( array $ops, array $opts = [] ) { @@ -648,7 +690,9 @@ abstract class FileBackend implements LoggerAwareInterface { * considered "OK" as long as no fatal errors occurred. * * @param array $ops Set of operations to execute + * @phan-param array{ignoreMissingSource?:bool,headers?:bool} $ops * @param array $opts Batch operation options + * @phan-param array{bypassReadOnly?:bool} $opts * @return StatusValue * @since 1.20 */ @@ -831,7 +875,7 @@ abstract class FileBackend implements LoggerAwareInterface { * files whereas key/value store backends might revoke container * access to the storage user representing end-users in web requests. * - * This is not guaranteed to actually make files or listings publically hidden. + * This is not guaranteed to actually make files or listings publicly hidden. * Additional server configuration may be needed to achieve the desired effect. * * @param array $params Parameters include: @@ -864,7 +908,7 @@ abstract class FileBackend implements LoggerAwareInterface { * access to the storage user representing end-users in web requests. * This essentially can undo the result of secure() calls. * - * This is not guaranteed to actually make files or listings publically viewable. + * This is not guaranteed to actually make files or listings publicly viewable. * Additional server configuration may be needed to achieve the desired effect. * * @param array $params Parameters include: @@ -922,20 +966,29 @@ abstract class FileBackend implements LoggerAwareInterface { * Check if a file exists at a storage path in the backend. * This returns false if only a directory exists at the path. * + * Callers that only care if a file is readily accessible can use non-strict + * comparisons on the result. If "does not exist" and "existence is unknown" + * must be distinguished, then strict comparisons to true/null should be used. + * + * @see FileBackend::EXISTENCE_ERROR + * @see FileBackend::directoryExists() + * * @param array $params Parameters include: * - src : source storage path * - latest : use the latest available data - * @return bool|null Returns null on failure + * @return bool|null Whether the file exists or null (I/O error) */ abstract public function fileExists( array $params ); /** * Get the last-modified timestamp of the file at a storage path. * + * @see FileBackend::TIMESTAMP_FAIL + * * @param array $params Parameters include: * - src : source storage path * - latest : use the latest available data - * @return string|bool TS_MW timestamp or false on failure + * @return string|false TS_MW timestamp or false (missing file or I/O error) */ abstract public function getFileTimestamp( array $params ); @@ -943,22 +996,22 @@ abstract class FileBackend implements LoggerAwareInterface { * Get the contents of a file at a storage path in the backend. * This should be avoided for potentially large files. * + * @see FileBackend::CONTENT_FAIL + * * @param array $params Parameters include: * - src : source storage path * - latest : use the latest available data - * @return string|bool Returns false on failure + * @return string|false Content string or false (missing file or I/O error) */ final public function getFileContents( array $params ) { - $contents = $this->getFileContentsMulti( - [ 'srcs' => [ $params['src'] ] ] + $params ); + $contents = $this->getFileContentsMulti( [ 'srcs' => [ $params['src'] ] ] + $params ); return $contents[$params['src']]; } /** * Like getFileContents() except it takes an array of storage paths - * and returns a map of storage paths to strings (or null on failure). - * The map keys (paths) are in the same order as the provided list of paths. + * and returns an order preserved map of storage paths to their content. * * @see FileBackend::getFileContents() * @@ -966,7 +1019,7 @@ abstract class FileBackend implements LoggerAwareInterface { * - srcs : list of source storage paths * - latest : use the latest available data * - parallelize : try to do operations in parallel when possible - * @return array Map of (path name => string or false on failure) + * @return string[]|false[] Map of (path name => file content or false on failure) * @since 1.20 */ abstract public function getFileContentsMulti( array $params ); @@ -982,11 +1035,13 @@ abstract class FileBackend implements LoggerAwareInterface { * * Use FileBackend::hasFeatures() to check how well this is supported. * + * @see FileBackend::XATTRS_FAIL + * * @param array $params * $params include: * - src : source storage path * - latest : use the latest available data - * @return array|bool Returns false on failure + * @return array|false File metadata array or false (missing file or I/O error) * @since 1.23 */ abstract public function getFileXAttributes( array $params ); @@ -994,10 +1049,12 @@ abstract class FileBackend implements LoggerAwareInterface { /** * Get the size (bytes) of a file at a storage path in the backend. * + * @see FileBackend::SIZE_FAIL + * * @param array $params Parameters include: * - src : source storage path * - latest : use the latest available data - * @return int|bool Returns false on failure + * @return int|false File size in bytes or false (missing file or I/O error) */ abstract public function getFileSize( array $params ); @@ -1009,36 +1066,41 @@ abstract class FileBackend implements LoggerAwareInterface { * - size : the file size (bytes) * Additional values may be included for internal use only. * + * @see FileBackend::STAT_ABSENT + * @see FileBackend::STAT_ERROR + * * @param array $params Parameters include: * - src : source storage path * - latest : use the latest available data - * @return array|bool|null Returns null on failure + * @return array|false|null Attribute map, false (missing file), or null (I/O error) */ abstract public function getFileStat( array $params ); /** - * Get a SHA-1 hash of the file at a storage path in the backend. + * Get a SHA-1 hash of the content of the file at a storage path in the backend. + * + * @see FileBackend::SHA1_FAIL * * @param array $params Parameters include: * - src : source storage path * - latest : use the latest available data - * @return string|bool Hash string or false on failure + * @return string|false Hash string or false (missing file or I/O error) */ abstract public function getFileSha1Base36( array $params ); /** - * Get the properties of the file at a storage path in the backend. + * Get the properties of the content of the file at a storage path in the backend. * This gives the result of FSFile::getProps() on a local copy of the file. * * @param array $params Parameters include: * - src : source storage path * - latest : use the latest available data - * @return array Returns FSFile::placeholderProps() on failure + * @return array Properties map; FSFile::placeholderProps() if file missing or on I/O error */ abstract public function getFileProps( array $params ); /** - * Stream the file at a storage path in the backend. + * Stream the content of the file at a storage path in the backend. * * If the file does not exists, an HTTP 404 error will be given. * Appropriate HTTP headers (Status, Content-Type, Content-Length) @@ -1059,34 +1121,36 @@ abstract class FileBackend implements LoggerAwareInterface { abstract public function streamFile( array $params ); /** - * Returns a file system file, identical to the file at a storage path. + * Returns a file system file, identical in content 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. + * - a) A TempFSFile 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. + * Temporary files may be purged when the file object falls out of scope. + * - b) An FSFile pointing to the original file at a storage path in the backend. + * This is applicable for backends layered directly on top of file systems. * - * Write operations should *never* be done on this file as some backends - * may do internal tracking or may be instances of FileBackendMultiWrite. - * In that latter case, there are copies of the file that must stay in sync. - * Additionally, further calls to this function may return the same file. + * Never modify the returned file since it might be the original, it might be shared + * among multiple callers of this method, or the backend might internally keep FSFile + * references for deferred operations. * * @param array $params Parameters include: * - src : source storage path * - latest : use the latest available data - * @return FSFile|null Returns null on failure + * @return FSFile|null Local file copy or null (missing file or I/O error) */ final public function getLocalReference( array $params ) { - $fsFiles = $this->getLocalReferenceMulti( - [ 'srcs' => [ $params['src'] ] ] + $params ); + $fsFiles = $this->getLocalReferenceMulti( [ 'srcs' => [ $params['src'] ] ] + $params ); return $fsFiles[$params['src']]; } /** - * Like getLocalReference() except it takes an array of storage paths - * and returns a map of storage paths to FSFile objects (or null on failure). - * The map keys (paths) are in the same order as the provided list of paths. + * Like getLocalReference() except it takes an array of storage paths and + * yields an order-preserved map of storage paths to temporary local file copies. + * + * Never modify the returned files since they might be originals, they might be shared + * among multiple callers of this method, or the backend might internally keep FSFile + * references for deferred operations. * * @see FileBackend::getLocalReference() * @@ -1104,22 +1168,24 @@ abstract class FileBackend implements LoggerAwareInterface { * 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. * + * Multiple calls to this method for the same path will create new copies. + * * @param array $params Parameters include: * - src : source storage path * - latest : use the latest available data - * @return TempFSFile|null Returns null on failure + * @return TempFSFile|null Temporary local file copy or null (missing file or I/O error) */ final public function getLocalCopy( array $params ) { - $tmpFiles = $this->getLocalCopyMulti( - [ 'srcs' => [ $params['src'] ] ] + $params ); + $tmpFiles = $this->getLocalCopyMulti( [ 'srcs' => [ $params['src'] ] ] + $params ); return $tmpFiles[$params['src']]; } /** - * Like getLocalCopy() except it takes an array of storage paths and - * returns a map of storage paths to TempFSFile objects (or null on failure). - * The map keys (paths) are in the same order as the provided list of paths. + * Like getLocalCopy() except it takes an array of storage paths and yields + * an order preserved-map of storage paths to temporary local file copies. + * + * Multiple calls to this method for the same path will create new copies. * * @see FileBackend::getLocalCopy() * @@ -1142,30 +1208,46 @@ abstract class FileBackend implements LoggerAwareInterface { * Otherwise, one would need to use getLocalReference(), which involves loading * the entire file on to local disk. * + * @see FileBackend::TEMPURL_ERROR + * * @param array $params Parameters include: * - src : source storage path * - ttl : lifetime (seconds) if pre-authenticated; default is 1 day - * @return string|null + * @return string|null URL or null (not supported or I/O error) * @since 1.21 */ abstract public function getFileHttpUrl( array $params ); /** - * Check if a directory exists at a given storage path. - * Backends using key/value stores will check if the path is a - * virtual directory, meaning there are files under the given directory. + * Check if a directory exists at a given storage path + * + * For backends using key/value stores, a directory is said to exist whenever + * there exist any files with paths using the given directory path as a prefix + * followed by a forward slash. For example, if there is a file called + * "mwstore://backend/container/dir/path.svg" then directories are said to exist + * at "mwstore://backend/container" and "mwstore://backend/container/dir". These + * can be thought of as "virtual" directories. + * + * Backends that directly use a filesystem layer might enumerate empty directories. + * The clean() method should always be used when files are deleted or moved if this + * is a concern. This is a trade-off to avoid write amplication/contention on file + * changes or read amplification when calling this method. * * Storage backends with eventual consistency might return stale data. * + * @see FileBackend::EXISTENCE_ERROR + * @see FileBackend::clean() + * * @param array $params Parameters include: * - dir : storage directory - * @return bool|null Returns null on failure + * @return bool|null Whether a directory exists or null (I/O error) * @since 1.20 */ abstract public function directoryExists( array $params ); /** - * Get an iterator to list *all* directories under a storage directory. + * Get an iterator to list *all* directories under a storage directory + * * If the directory is of the form "mwstore://backend/container", * then all directories in the container will be listed. * If the directory is of form "mwstore://backend/container/dir", @@ -1176,10 +1258,13 @@ abstract class FileBackend implements LoggerAwareInterface { * * Failures during iteration can result in FileBackendError exceptions (since 1.22). * + * @see FileBackend::LIST_ERROR + * @see FileBackend::directoryExists() + * * @param array $params Parameters include: * - dir : storage directory * - topOnly : only return direct child dirs of the directory - * @return Traversable|array|null Returns null on failure + * @return Traversable|array|null Directory list enumerator or null (initial I/O error) * @since 1.20 */ abstract public function getDirectoryList( array $params ); @@ -1192,9 +1277,12 @@ abstract class FileBackend implements LoggerAwareInterface { * * Failures during iteration can result in FileBackendError exceptions (since 1.22). * + * @see FileBackend::LIST_ERROR + * @see FileBackend::directoryExists() + * * @param array $params Parameters include: * - dir : storage directory - * @return Traversable|array|null Returns null on failure + * @return Traversable|array|null Directory list enumerator or null (initial I/O error) * @since 1.20 */ final public function getTopDirectoryList( array $params ) { @@ -1202,22 +1290,24 @@ abstract class FileBackend implements LoggerAwareInterface { } /** - * Get an iterator to list *all* stored files under a storage directory. - * If the directory is of the form "mwstore://backend/container", - * then all files in the container will be listed. - * If the directory is of form "mwstore://backend/container/dir", - * then all files under that directory will be listed. - * Results will be storage paths relative to the given directory. + * Get an iterator to list *all* stored files under a storage directory + * + * If the directory is of the form "mwstore://backend/container", then all + * files in the container will be listed. If the directory is of form + * "mwstore://backend/container/dir", then all files under that directory will + * be listed. Results will be storage paths relative to the given directory. * * Storage backends with eventual consistency might return stale data. * * Failures during iteration can result in FileBackendError exceptions (since 1.22). * + * @see FileBackend::LIST_ERROR + * * @param array $params Parameters include: * - dir : storage directory * - topOnly : only return direct child files of the directory (since 1.20) * - adviseStat : set to true if stat requests will be made on the files (since 1.22) - * @return Traversable|array|null Returns null on failure + * @return Traversable|array|null File list enumerator or null (initial I/O error) */ abstract public function getFileList( array $params ); @@ -1229,10 +1319,12 @@ abstract class FileBackend implements LoggerAwareInterface { * * Failures during iteration can result in FileBackendError exceptions (since 1.22). * + * @see FileBackend::LIST_ERROR + * * @param array $params Parameters include: * - dir : storage directory * - adviseStat : set to true if stat requests will be made on the files (since 1.22) - * @return Traversable|array|null Returns null on failure + * @return Traversable|array|null File list enumerator or null on failure * @since 1.20 */ final public function getTopFileList( array $params ) { @@ -1270,7 +1362,7 @@ abstract class FileBackend implements LoggerAwareInterface { * @param array $params Parameters include: * - srcs : list of source storage paths * - latest : use the latest available data - * @return bool All requests proceeded without I/O errors (since 1.24) + * @return bool Whether all requests proceeded without I/O errors (since 1.24) * @since 1.23 */ abstract public function preloadFileStat( array $params ); @@ -1319,7 +1411,7 @@ abstract class FileBackend implements LoggerAwareInterface { * @param int|string $type LockManager::LOCK_* constant or "mixed" * @param StatusValue $status StatusValue to update on lock/unlock * @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.24) - * @return ScopedLock|null Returns null on failure + * @return ScopedLock|null RAII-style self-unlocking lock or null on failure */ final public function getScopedFileLocks( array $paths, $type, StatusValue $status, $timeout = 0 @@ -1348,7 +1440,7 @@ abstract class FileBackend implements LoggerAwareInterface { * * @param array $ops List of file operations to FileBackend::doOperations() * @param StatusValue $status StatusValue to update on lock/unlock - * @return ScopedLock|null + * @return ScopedLock|null RAII-style self-unlocking lock or null on failure * @since 1.20 */ abstract public function getScopedLocksForOps( array $ops, StatusValue $status ); @@ -1446,7 +1538,7 @@ abstract class FileBackend implements LoggerAwareInterface { * Returns null if the path is not of the format of a valid storage path. * * @param string $storagePath - * @return string|null + * @return string|null Normalized storage path or null on failure */ final public static function normalizeStoragePath( $storagePath ) { list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath ); @@ -1468,7 +1560,7 @@ abstract class FileBackend implements LoggerAwareInterface { * "mwstore://backend/container/...", or null if there is no parent. * * @param string $storagePath - * @return string|null + * @return string|null Parent storage path or null on failure */ final public static function parentStoragePath( $storagePath ) { $storagePath = dirname( $storagePath ); @@ -1513,7 +1605,7 @@ abstract class FileBackend implements LoggerAwareInterface { * * @param string $type One of (attachment, inline) * @param string $filename Suggested file name (should not contain slashes) - * @throws FileBackendError + * @throws InvalidArgumentException * @return string * @since 1.20 */ @@ -1541,7 +1633,7 @@ abstract class FileBackend implements LoggerAwareInterface { * This uses the same traversal protection as Title::secureAndSplit(). * * @param string $path Storage path relative to a container - * @return string|null + * @return string|null Normalized container path or null on failure */ final protected static function normalizeContainerPath( $path ) { // Normalize directory separators @@ -1572,11 +1664,10 @@ abstract class FileBackend implements LoggerAwareInterface { * - StatusValue::newGood() if this method is called without parameters * - StatusValue::newFatal() with all parameters to this method if passed in * - * @param string $args,... + * @param string ...$args * @return StatusValue */ - final protected function newStatus() { - $args = func_get_args(); + final protected function newStatus( ...$args ) { if ( count( $args ) ) { $sv = StatusValue::newFatal( ...$args ); } else { @@ -1599,12 +1690,7 @@ abstract class FileBackend implements LoggerAwareInterface { * @return ScopedCallback|null */ protected function scopedProfileSection( $section ) { - if ( $this->profiler ) { - call_user_func( [ $this->profiler, 'profileIn' ], $section ); - return new ScopedCallback( [ $this->profiler, 'profileOut' ], [ $section ] ); - } - - return null; + return $this->profiler ? ( $this->profiler )( $section ) : null; } protected function resetOutputBuffer() {