From c1f9b1f7b1b77776192048005dcc66dcf3df2bfb Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Sat, 27 Dec 2014 15:41:37 +0100 Subject: Update to MediaWiki 1.24.1 --- includes/filebackend/FileBackend.php | 300 +++++++++++++++++++++++------------ 1 file changed, 199 insertions(+), 101 deletions(-) (limited to 'includes/filebackend/FileBackend.php') diff --git a/includes/filebackend/FileBackend.php b/includes/filebackend/FileBackend.php index f586578b..8c0a61a1 100644 --- a/includes/filebackend/FileBackend.php +++ b/includes/filebackend/FileBackend.php @@ -47,12 +47,35 @@ * For legacy reasons, the FSFileBackend class allows manually setting the paths of * containers to ones that do not respect the "wiki ID". * - * In key/value stores, the container is the only hierarchy (the rest is emulated). + * In key/value (object) stores, containers are the only hierarchy (the rest is emulated). * 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 * backends with virtual directories may not have this limitation. Callers should * store files in such a way that no files and directories are under the same path. * + * In general, this class allows for callers to access storage through the same + * interface, without regard to the underlying storage system. However, calling code + * must follow certain patterns and be aware of certain things to ensure compatibility: + * - a) Always call prepare() on the parent directory before trying to put a file there; + * key/value stores only need the container to exist first, but filesystems need + * all the parent directories to exist first (prepare() is aware of all this) + * - b) Always call clean() on a directory when it might become empty to avoid empty + * directory buildup on filesystems; key/value stores never have empty directories, + * so doing this helps preserve consistency in both cases + * - c) Likewise, do not rely on the existence of empty directories for anything; + * calling directoryExists() on a path that prepare() was previously called on + * will return false for key/value stores if there are no files under that path + * - d) Never alter the resulting FSFile returned from getLocalReference(), as it could + * either be a copy of the source file in /tmp or the original source file itself + * - e) Use a file layout that results in never attempting to store files over directories + * or directories over files; key/value stores allow this but filesystems do not + * - f) Use ASCII file names (e.g. base32, IDs, hashes) to avoid Unicode issues in Windows + * - g) Do not assume that move operations are atomic (difficult with key/value stores) + * - h) Do not assume that file stat or read operations always have immediate consistency; + * various methods have a "latest" flag that should always be used if up-to-date + * information is required (this trades performance for correctness as needed) + * - i) Do not assume that directory listings have immediate consistency + * * Methods of subclasses should avoid throwing exceptions at all costs. * As a corollary, external dependencies should be kept to a minimum. * @@ -60,58 +83,69 @@ * @since 1.19 */ abstract class FileBackend { - protected $name; // string; unique backend name - protected $wikiId; // string; unique wiki name - protected $readOnly; // string; read-only explanation message - protected $parallelize; // string; when to do operations in parallel - protected $concurrency; // integer; how many operations can be done in parallel + /** @var string Unique backend name */ + protected $name; + + /** @var string Unique wiki name */ + protected $wikiId; + + /** @var string Read-only explanation message */ + protected $readOnly; + + /** @var string When to do operations in parallel */ + protected $parallelize; + + /** @var int How many operations can be done in parallel */ + protected $concurrency; /** @var LockManager */ protected $lockManager; + /** @var FileJournal */ protected $fileJournal; + /** Bitfield flags for supported features */ + const ATTR_HEADERS = 1; // files can be tagged with standard HTTP headers + 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) + /** * Create a new backend instance from configuration. * This should only be called from within FileBackendGroup. * - * $config includes: + * @param array $config Parameters include: * - name : The unique name of this backend. * This should consist of alphanumberic, '-', and '_' characters. * This name should not be changed after use (e.g. with journaling). * Note that the name is *not* used in actual container names. * - wikiId : Prefix to container names that is unique to this backend. - * If not provided, this defaults to the current wiki ID. * It should only consist of alphanumberic, '-', and '_' characters. * This ID is what avoids collisions if multiple logical backends * use the same storage system, so this should be set carefully. - * - lockManager : Registered name of a file lock manager to use. - * - fileJournal : File journal configuration; see FileJournal::factory(). - * Journals simply log changes to files stored in the backend. + * - lockManager : LockManager object to use for any file locking. + * If not provided, then no file locking will be enforced. + * - fileJournal : FileJournal object to use for logging changes to files. + * If not provided, then change journaling will be disabled. * - readOnly : Write operations are disallowed if this is a non-empty string. * It should be an explanation for the backend being read-only. * - 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. - * - * @param array $config - * @throws MWException + * @throws FileBackendException */ public function __construct( array $config ) { $this->name = $config['name']; + $this->wikiId = $config['wikiId']; // e.g. "my_wiki-en_" if ( !preg_match( '!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) { - throw new MWException( "Backend name `{$this->name}` is invalid." ); + throw new FileBackendException( "Backend name '{$this->name}' is invalid." ); + } elseif ( !is_string( $this->wikiId ) ) { + throw new FileBackendException( "Backend wiki ID not provided for '{$this->name}'." ); } - $this->wikiId = isset( $config['wikiId'] ) - ? $config['wikiId'] - : wfWikiID(); // e.g. "my_wiki-en_" - $this->lockManager = ( $config['lockManager'] instanceof LockManager ) + $this->lockManager = isset( $config['lockManager'] ) ? $config['lockManager'] - : LockManagerGroup::singleton( $this->wikiId )->get( $config['lockManager'] ); + : new NullLockManager( array() ); $this->fileJournal = isset( $config['fileJournal'] ) - ? ( ( $config['fileJournal'] instanceof FileJournal ) - ? $config['fileJournal'] - : FileJournal::factory( $config['fileJournal'], $this->name ) ) + ? $config['fileJournal'] : FileJournal::factory( array( 'class' => 'NullFileJournal' ), $this->name ); $this->readOnly = isset( $config['readOnly'] ) ? (string)$config['readOnly'] @@ -164,6 +198,27 @@ abstract class FileBackend { return ( $this->readOnly != '' ) ? $this->readOnly : false; } + /** + * Get the a bitfield of extra features supported by the backend medium + * + * @return int Bitfield of FileBackend::ATTR_* flags + * @since 1.23 + */ + public function getFeatures() { + return self::ATTR_UNICODE_PATHS; + } + + /** + * Check if the backend medium supports a field of extra features + * + * @param int $bitfield Bitfield of FileBackend::ATTR_* flags + * @return bool + * @since 1.23 + */ + final public function hasFeatures( $bitfield ) { + return ( $this->getFeatures() & $bitfield ) === $bitfield; + } + /** * This is the main entry point into the backend for write operations. * Callers supply an ordered list of operations to perform as a transaction. @@ -671,8 +726,7 @@ abstract class FileBackend { * otherwise safe from modification from other processes. Normally, * the file will be a new temp file, which should be adequate. * - * @param array $params Operation parameters - * $params include: + * @param array $params Operation parameters, include: * - srcs : ordered source storage paths (e.g. chunk1, chunk2, ...) * - dst : file system path to 0-byte temp file * - parallelize : try to do operations in parallel when possible @@ -691,8 +745,7 @@ abstract class FileBackend { * However, setting them is not guaranteed to actually do anything. * Additional server configuration may be needed to achieve the desired effect. * - * @param array $params - * $params include: + * @param array $params Parameters include: * - dir : storage directory * - noAccess : try to deny file access (since 1.20) * - noListing : try to deny file listing (since 1.20) @@ -721,8 +774,7 @@ abstract class FileBackend { * This is not guaranteed to actually make files or listings publically hidden. * Additional server configuration may be needed to achieve the desired effect. * - * @param array $params - * $params include: + * @param array $params Parameters include: * - dir : storage directory * - noAccess : try to deny file access * - noListing : try to deny file listing @@ -752,8 +804,7 @@ abstract class FileBackend { * This is not guaranteed to actually make files or listings publically viewable. * Additional server configuration may be needed to achieve the desired effect. * - * @param array $params - * $params include: + * @param array $params Parameters include: * - dir : storage directory * - access : try to allow file access * - listing : try to allow file listing @@ -779,8 +830,7 @@ abstract class FileBackend { * Backends using key/value stores may do nothing unless the directory * is that of an empty container, in which case it will be deleted. * - * @param array $params - * $params include: + * @param array $params Parameters include: * - dir : storage directory * - recursive : recursively delete empty subdirectories first (since 1.20) * - bypassReadOnly : allow writes in read-only mode (since 1.20) @@ -807,12 +857,13 @@ abstract class FileBackend { * @return ScopedCallback|null */ final protected function getScopedPHPBehaviorForOps() { - if ( php_sapi_name() != 'cli' ) { // http://bugs.php.net/bug.php?id=47540 + if ( PHP_SAPI != 'cli' ) { // http://bugs.php.net/bug.php?id=47540 $old = ignore_user_abort( true ); // avoid half-finished operations - return new ScopedCallback( function() use ( $old ) { + return new ScopedCallback( function () use ( $old ) { ignore_user_abort( $old ); } ); } + return null; } @@ -820,8 +871,7 @@ abstract class FileBackend { * Check if a file exists at a storage path in the backend. * This returns false if only a directory exists at the path. * - * @param array $params - * $params include: + * @param array $params Parameters include: * - src : source storage path * - latest : use the latest available data * @return bool|null Returns null on failure @@ -831,8 +881,7 @@ abstract class FileBackend { /** * Get the last-modified timestamp of the file at a storage path. * - * @param array $params - * $params include: + * @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 @@ -843,8 +892,7 @@ abstract class FileBackend { * Get the contents of a file at a storage path in the backend. * This should be avoided for potentially large files. * - * @param array $params - * $params include: + * @param array $params Parameters include: * - src : source storage path * - latest : use the latest available data * @return string|bool Returns false on failure @@ -863,24 +911,42 @@ abstract class FileBackend { * * @see FileBackend::getFileContents() * - * @param array $params - * $params include: + * @param array $params Parameters include: * - 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 array Map of (path name => string or false on failure) * @since 1.20 */ abstract public function getFileContentsMulti( array $params ); /** - * Get the size (bytes) of a file at a storage path in the backend. + * Get metadata about a file at a storage path in the backend. + * If the file does not exist, then this returns false. + * Otherwise, the result is an associative array that includes: + * - headers : map of HTTP headers used for GET/HEAD requests (name => value) + * - metadata : map of file metadata (name => value) + * Metadata keys and headers names will be returned in all lower-case. + * Additional values may be included for internal use only. + * + * Use FileBackend::hasFeatures() to check how well this is supported. * * @param array $params * $params include: * - src : source storage path * - latest : use the latest available data - * @return integer|bool Returns false on failure + * @return array|bool Returns false on failure + * @since 1.23 + */ + abstract public function getFileXAttributes( array $params ); + + /** + * Get the size (bytes) of a file at a storage path in the backend. + * + * @param array $params Parameters include: + * - src : source storage path + * - latest : use the latest available data + * @return int|bool Returns false on failure */ abstract public function getFileSize( array $params ); @@ -892,19 +958,17 @@ abstract class FileBackend { * - size : the file size (bytes) * Additional values may be included for internal use only. * - * @param array $params - * $params include: + * @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|bool|null Returns null on failure */ abstract public function getFileStat( array $params ); /** * Get a SHA-1 hash of the file at a storage path in the backend. * - * @param array $params - * $params include: + * @param array $params Parameters include: * - src : source storage path * - latest : use the latest available data * @return string|bool Hash string or false on failure @@ -915,11 +979,10 @@ abstract class FileBackend { * Get the properties 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 - * $params include: + * @param array $params Parameters include: * - src : source storage path * - latest : use the latest available data - * @return Array Returns FSFile::placeholderProps() on failure + * @return array Returns FSFile::placeholderProps() on failure */ abstract public function getFileProps( array $params ); @@ -930,8 +993,7 @@ abstract class FileBackend { * will be sent if streaming began, while none will be sent otherwise. * Implementations should flush the output buffer before sending data. * - * @param array $params - * $params include: + * @param array $params Parameters include: * - src : source storage path * - headers : list of additional HTTP headers to send on success * - latest : use the latest available data @@ -952,8 +1014,7 @@ abstract class FileBackend { * In that later case, there are copies of the file that must stay in sync. * Additionally, further calls to this function may return the same file. * - * @param array $params - * $params include: + * @param array $params Parameters include: * - src : source storage path * - latest : use the latest available data * @return FSFile|null Returns null on failure @@ -972,12 +1033,11 @@ abstract class FileBackend { * * @see FileBackend::getLocalReference() * - * @param array $params - * $params include: + * @param array $params Parameters include: * - 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 => FSFile or null on failure) + * @return array Map of (path name => FSFile or null on failure) * @since 1.20 */ abstract public function getLocalReferenceMulti( array $params ); @@ -987,8 +1047,7 @@ abstract class FileBackend { * 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. * - * @param array $params - * $params include: + * @param array $params Parameters include: * - src : source storage path * - latest : use the latest available data * @return TempFSFile|null Returns null on failure @@ -1007,12 +1066,11 @@ abstract class FileBackend { * * @see FileBackend::getLocalCopy() * - * @param array $params - * $params include: + * @param array $params Parameters include: * - 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 => TempFSFile or null on failure) + * @return array Map of (path name => TempFSFile or null on failure) * @since 1.20 */ abstract public function getLocalCopyMulti( array $params ); @@ -1027,8 +1085,7 @@ abstract class FileBackend { * Otherwise, one would need to use getLocalReference(), which involves loading * the entire file on to local disk. * - * @param array $params - * $params include: + * @param array $params Parameters include: * - src : source storage path * - ttl : lifetime (seconds) if pre-authenticated; default is 1 day * @return string|null @@ -1043,8 +1100,7 @@ abstract class FileBackend { * * Storage backends with eventual consistency might return stale data. * - * @param array $params - * $params include: + * @param array $params Parameters include: * - dir : storage directory * @return bool|null Returns null on failure * @since 1.20 @@ -1063,11 +1119,10 @@ abstract class FileBackend { * * Failures during iteration can result in FileBackendError exceptions (since 1.22). * - * @param array $params - * $params include: + * @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 Returns null on failure * @since 1.20 */ abstract public function getDirectoryList( array $params ); @@ -1080,10 +1135,9 @@ abstract class FileBackend { * * Failures during iteration can result in FileBackendError exceptions (since 1.22). * - * @param array $params - * $params include: + * @param array $params Parameters include: * - dir : storage directory - * @return Traversable|Array|null Returns null on failure + * @return Traversable|array|null Returns null on failure * @since 1.20 */ final public function getTopDirectoryList( array $params ) { @@ -1102,12 +1156,11 @@ abstract class FileBackend { * * Failures during iteration can result in FileBackendError exceptions (since 1.22). * - * @param array $params - * $params include: + * @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 Returns null on failure */ abstract public function getFileList( array $params ); @@ -1119,11 +1172,10 @@ abstract class FileBackend { * * Failures during iteration can result in FileBackendError exceptions (since 1.22). * - * @param array $params - * $params include: + * @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 Returns null on failure * @since 1.20 */ final public function getTopFileList( array $params ) { @@ -1131,22 +1183,38 @@ abstract class FileBackend { } /** - * Preload persistent file stat and property cache into in-process cache. + * Preload persistent file stat cache and property cache into in-process cache. * This should be used when stat calls will be made on a known list of a many files. * + * @see FileBackend::getFileStat() + * * @param array $paths Storage paths - * @return void */ - public function preloadCache( array $paths ) {} + abstract public function preloadCache( array $paths ); /** * Invalidate any in-process file stat and property cache. * If $paths is given, then only the cache for those files will be cleared. * + * @see FileBackend::getFileStat() + * * @param array $paths Storage paths (optional) - * @return void */ - public function clearCache( array $paths = null ) {} + abstract public function clearCache( array $paths = null ); + + /** + * Preload file stat information (concurrently if possible) into in-process cache. + * This should be used when stat calls will be made on a known list of a many files. + * + * @see FileBackend::getFileStat() + * + * @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) + * @since 1.23 + */ + abstract public function preloadFileStat( array $params ); /** * Lock the files at the given storage paths in the backend. @@ -1155,23 +1223,26 @@ abstract class FileBackend { * Callers should consider using getScopedFileLocks() instead. * * @param array $paths Storage paths - * @param integer $type LockManager::LOCK_* constant + * @param int $type LockManager::LOCK_* constant + * @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.24) * @return Status */ - final public function lockFiles( array $paths, $type ) { + final public function lockFiles( array $paths, $type, $timeout = 0 ) { $paths = array_map( 'FileBackend::normalizeStoragePath', $paths ); - return $this->lockManager->lock( $paths, $type ); + + return $this->lockManager->lock( $paths, $type, $timeout ); } /** * Unlock the files at the given storage paths in the backend. * * @param array $paths Storage paths - * @param integer $type LockManager::LOCK_* constant + * @param int $type LockManager::LOCK_* constant * @return Status */ final public function unlockFiles( array $paths, $type ) { $paths = array_map( 'FileBackend::normalizeStoragePath', $paths ); + return $this->lockManager->unlock( $paths, $type ); } @@ -1186,11 +1257,12 @@ abstract class FileBackend { * @see ScopedLock::factory() * * @param array $paths List of storage paths or map of lock types to path lists - * @param integer|string $type LockManager::LOCK_* constant or "mixed" + * @param int|string $type LockManager::LOCK_* constant or "mixed" * @param Status $status Status 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 */ - final public function getScopedFileLocks( array $paths, $type, Status $status ) { + final public function getScopedFileLocks( array $paths, $type, Status $status, $timeout = 0 ) { if ( $type === 'mixed' ) { foreach ( $paths as &$typePaths ) { $typePaths = array_map( 'FileBackend::normalizeStoragePath', $typePaths ); @@ -1198,7 +1270,8 @@ abstract class FileBackend { } else { $paths = array_map( 'FileBackend::normalizeStoragePath', $paths ); } - return ScopedLock::factory( $this->lockManager, $paths, $type, $status ); + + return ScopedLock::factory( $this->lockManager, $paths, $type, $status, $timeout ); } /** @@ -1214,7 +1287,7 @@ abstract class FileBackend { * * @param array $ops List of file operations to FileBackend::doOperations() * @param Status $status Status to update on lock/unlock - * @return Array List of ScopedFileLocks or null values + * @return array List of ScopedFileLocks or null values * @since 1.20 */ abstract public function getScopedLocksForOps( array $ops, Status $status ); @@ -1267,7 +1340,7 @@ abstract class FileBackend { * This does not do any path normalization or traversal checks. * * @param string $storagePath - * @return Array (backend, container, rel object) or (null, null, null) + * @return array (backend, container, rel object) or (null, null, null) */ final public static function splitStoragePath( $storagePath ) { if ( self::isStoragePath( $storagePath ) ) { @@ -1281,6 +1354,7 @@ abstract class FileBackend { } } } + return array( null, null, null ); } @@ -1301,6 +1375,7 @@ abstract class FileBackend { : "mwstore://{$backend}/{$container}"; } } + return null; } @@ -1315,6 +1390,7 @@ abstract class FileBackend { final public static function parentStoragePath( $storagePath ) { $storagePath = dirname( $storagePath ); list( , , $rel ) = self::splitStoragePath( $storagePath ); + return ( $rel === null ) ? null : $storagePath; } @@ -1322,11 +1398,20 @@ abstract class FileBackend { * Get the final extension from a storage or FS path * * @param string $path + * @param string $case One of (rawcase, uppercase, lowercase) (since 1.24) * @return string */ - final public static function extensionFromPath( $path ) { + final public static function extensionFromPath( $path, $case = 'lowercase' ) { $i = strrpos( $path, '.' ); - return strtolower( $i ? substr( $path, $i + 1 ) : '' ); + $ext = $i ? substr( $path, $i + 1 ) : ''; + + if ( $case === 'lowercase' ) { + $ext = strtolower( $ext ); + } elseif ( $case === 'uppercase' ) { + $ext = strtoupper( $ext ); + } + + return $ext; } /** @@ -1345,7 +1430,7 @@ abstract class FileBackend { * * @param string $type One of (attachment, inline) * @param string $filename Suggested file name (should not contain slashes) - * @throws MWException + * @throws FileBackendError * @return string * @since 1.20 */ @@ -1354,7 +1439,7 @@ abstract class FileBackend { $type = strtolower( $type ); if ( !in_array( $type, array( 'inline', 'attachment' ) ) ) { - throw new MWException( "Invalid Content-Disposition type '$type'." ); + throw new FileBackendError( "Invalid Content-Disposition type '$type'." ); } $parts[] = $type; @@ -1395,12 +1480,25 @@ abstract class FileBackend { return null; } } + return $path; } } /** + * Generic file backend exception for checked and unexpected (e.g. config) exceptions + * + * @ingroup FileBackend + * @since 1.23 + */ +class FileBackendException extends MWException { +} + +/** + * File backend exception for checked exceptions (e.g. I/O errors) + * * @ingroup FileBackend * @since 1.22 */ -class FileBackendError extends MWException {} +class FileBackendError extends FileBackendException { +} -- cgit v1.2.2