* Otherwise, the result is an associative array that includes:
* mtime : the last-modified timestamp (TS_MW)
* size : the file size (bytes)
+ * Additional values may be included for internal use only.
*
* $params include:
* src : source storage path
* This class defines the methods as abstract that subclasses must implement.
* Callers outside of FileBackend and its helper classes, such as FileOp,
* should only call functions that are present in FileBackendBase.
- *
+ *
* The FileBackendBase operations are implemented using primitive functions
* such as storeInternal(), copyInternal(), deleteInternal() and the like.
* This class is also responsible for path resolution and sanitization.
- *
+ *
* @ingroup FileBackend
* @since 1.19
*/
return $this->maxFileSize;
}
+ /**
+ * Check if a file can be created at a given storage path.
+ * FS backends should check if the parent directory exists and the file is writable.
+ * Backends using key/value stores should check if the container exists.
+ *
+ * @param $storagePath string
+ * @return bool
+ */
+ abstract public function isPathUsableInternal( $storagePath );
+
/**
* Create a file in the backend with the given contents.
* Do not call this function from places outside FileBackend and FileOp.
$status->fatal( 'backend-fail-invalidpath', $params['dir'] );
return $status; // invalid storage path
}
+ // Attempt to lock this directory...
+ $filesLockEx = array( $params['dir'] );
+ $scopedLockE = $this->getScopedFileLocks( $filesLockEx, LockManager::LOCK_EX, $status );
+ if ( !$status->isOK() ) {
+ return $status; // abort
+ }
if ( $shard !== null ) { // confined to a single container/shard
$status->merge( $this->doCleanInternal( $fullCont, $dir, $params ) );
} else { // directory is on several shards
*/
final public function getFileStat( array $params ) {
$path = $params['src'];
+ $latest = !empty( $params['latest'] );
if ( isset( $this->cache[$path]['stat'] ) ) {
- return $this->cache[$path]['stat'];
+ // If we want the latest data, check that this cached
+ // value was in fact fetched with the latest available data.
+ if ( !$latest || $this->cache[$path]['stat']['latest'] ) {
+ return $this->cache[$path]['stat'];
+ }
}
$stat = $this->doGetFileStat( $params );
if ( is_array( $stat ) ) { // don't cache negatives
$this->trimCache(); // limit memory
$this->cache[$path]['stat'] = $stat;
+ $this->cache[$path]['stat']['latest'] = $latest;
}
return $stat;
}
}
// Optimization: if doing an EX lock anyway, don't also set an SH one
$filesLockSh = array_diff( $filesLockSh, $filesLockEx );
+ // Get a shared lock on the parent directory of each path changed
+ $filesLockSh = array_merge( $filesLockSh, array_map( 'dirname', $filesLockEx ) );
// Try to lock those files for the scope of this function...
$scopeLockS = $this->getScopedFileLocks( $filesLockSh, LockManager::LOCK_UW, $status );
$scopeLockE = $this->getScopedFileLocks( $filesLockEx, LockManager::LOCK_EX, $status );
}
/**
- * Allow stale data for file reads and existence checks.
- *
- * Note that we don't want to mix stale and non-stale reads
- * because stat calls are cached: if we read X without 'latest'
- * and then read it with 'latest', the data may still be stale.
+ * Allow stale data for file reads and existence checks
*
* @return void
*/
* Callers are responsible for handling file locking.
*
* $opts is an array of options, including:
- * 'force' : Errors that would normally cause a rollback do not.
- * The remaining operations are still attempted if any fail.
- * 'allowStale' : Don't require the latest available data.
- * This can increase performance for non-critical writes.
- * This has no effect unless the 'force' flag is set.
+ * 'force' : Errors that would normally cause a rollback do not.
+ * The remaining operations are still attempted if any fail.
+ * 'allowStale' : Don't require the latest available data.
+ * This can increase performance for non-critical writes.
+ * This has no effect unless the 'force' flag is set.
*
* @param $performOps Array List of FileOp operations
* @param $opts Array Batch operation options
/**
* Check if this operation failed precheck() or attempt()
*
- * @return type
+ * @return bool
*/
final public function failed() {
return $this->failed;
if ( !is_file( $this->params['src'] ) ) {
$status->fatal( 'backend-fail-notexists', $this->params['src'] );
return $status;
- }
// Check if the source file is too big
- if ( filesize( $this->params['src'] ) > $this->backend->maxFileSizeInternal() ) {
- $status->fatal( 'backend-fail-store', $this->params['dst'] );
+ } elseif ( filesize( $this->params['src'] ) > $this->backend->maxFileSizeInternal() ) {
+ $status->fatal( 'backend-fail-store', $this->params['src'], $this->params['dst'] );
+ return $status;
+ // Check if a file can be placed at the destination
+ } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
+ $status->fatal( 'backend-fail-store', $this->params['src'], $this->params['dst'] );
return $status;
}
// Check if destination file exists
$status->merge( $this->precheckDestExistence( $predicates ) );
- if ( !$status->isOK() ) {
- return $status;
+ if ( $status->isOK() ) {
+ // Update file existence predicates
+ $predicates['exists'][$this->params['dst']] = true;
+ $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
}
- // Update file existence predicates
- $predicates['exists'][$this->params['dst']] = true;
- $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
return $status; // safe to call attempt()
}
if ( strlen( $this->params['content'] ) > $this->backend->maxFileSizeInternal() ) {
$status->fatal( 'backend-fail-create', $this->params['dst'] );
return $status;
+ // Check if a file can be placed at the destination
+ } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
+ $status->fatal( 'backend-fail-create', $this->params['dst'] );
+ return $status;
}
// Check if destination file exists
$status->merge( $this->precheckDestExistence( $predicates ) );
- if ( !$status->isOK() ) {
- return $status;
+ if ( $status->isOK() ) {
+ // Update file existence predicates
+ $predicates['exists'][$this->params['dst']] = true;
+ $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
}
- // Update file existence predicates
- $predicates['exists'][$this->params['dst']] = true;
- $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
return $status; // safe to call attempt()
}
if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
$status->fatal( 'backend-fail-notexists', $this->params['src'] );
return $status;
+ // Check if a file can be placed at the destination
+ } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
+ $status->fatal( 'backend-fail-copy', $this->params['src'], $this->params['dst'] );
+ return $status;
}
// Check if destination file exists
$status->merge( $this->precheckDestExistence( $predicates ) );
- if ( !$status->isOK() ) {
- return $status;
+ if ( $status->isOK() ) {
+ // Update file existence predicates
+ $predicates['exists'][$this->params['dst']] = true;
+ $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
}
- // Update file existence predicates
- $predicates['exists'][$this->params['dst']] = true;
- $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
return $status; // safe to call attempt()
}
if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
$status->fatal( 'backend-fail-notexists', $this->params['src'] );
return $status;
+ // Check if a file can be placed at the destination
+ } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
+ $status->fatal( 'backend-fail-move', $this->params['src'], $this->params['dst'] );
+ return $status;
}
// Check if destination file exists
$status->merge( $this->precheckDestExistence( $predicates ) );
- if ( !$status->isOK() ) {
- return $status;
+ if ( $status->isOK() ) {
+ // Update file existence predicates
+ $predicates['exists'][$this->params['src']] = false;
+ $predicates['sha1'][$this->params['src']] = false;
+ $predicates['exists'][$this->params['dst']] = true;
+ $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
}
- // Update file existence predicates
- $predicates['exists'][$this->params['src']] = false;
- $predicates['sha1'][$this->params['src']] = false;
- $predicates['exists'][$this->params['dst']] = true;
- $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
return $status; // safe to call attempt()
}
// Just delete source as the destination needs no changes
$params = array( 'src' => $this->params['src'] );
$status->merge( $this->backend->deleteInternal( $params ) );
- if ( !$status->isOK() ) {
- return $status;
- }
}
}
return $status;
if ( $this->needsDelete ) {
// Delete the source file
$status->merge( $this->backend->deleteInternal( $this->params ) );
- if ( !$status->isOK() ) {
- return $status;
- }
}
return $status;
}