MediaWiki  1.23.2
SwiftFileBackend.php
Go to the documentation of this file.
1 <?php
37  protected $http;
38 
40  protected $authTTL;
41 
43  protected $swiftAuthUrl;
44 
46  protected $swiftUser;
47 
49  protected $swiftKey;
50 
52  protected $swiftTempUrlKey;
53 
55  protected $rgwS3AccessKey;
56 
58  protected $rgwS3SecretKey;
59 
61  protected $srvCache;
62 
64  protected $containerStatCache;
65 
67  protected $authCreds;
68 
70  protected $authSessionTimestamp = 0;
71 
73  protected $authErrorTimestamp = null;
74 
76  protected $isRGW = false;
77 
106  public function __construct( array $config ) {
107  parent::__construct( $config );
108  // Required settings
109  $this->swiftAuthUrl = $config['swiftAuthUrl'];
110  $this->swiftUser = $config['swiftUser'];
111  $this->swiftKey = $config['swiftKey'];
112  // Optional settings
113  $this->authTTL = isset( $config['swiftAuthTTL'] )
114  ? $config['swiftAuthTTL']
115  : 5 * 60; // some sane number
116  $this->swiftTempUrlKey = isset( $config['swiftTempUrlKey'] )
117  ? $config['swiftTempUrlKey']
118  : '';
119  $this->shardViaHashLevels = isset( $config['shardViaHashLevels'] )
120  ? $config['shardViaHashLevels']
121  : '';
122  $this->rgwS3AccessKey = isset( $config['rgwS3AccessKey'] )
123  ? $config['rgwS3AccessKey']
124  : '';
125  $this->rgwS3SecretKey = isset( $config['rgwS3SecretKey'] )
126  ? $config['rgwS3SecretKey']
127  : '';
128  // HTTP helper client
129  $this->http = new MultiHttpClient( array() );
130  // Cache container information to mask latency
131  $this->memCache = wfGetMainCache();
132  // Process cache for container info
133  $this->containerStatCache = new ProcessCacheLRU( 300 );
134  // Cache auth token information to avoid RTTs
135  if ( !empty( $config['cacheAuthInfo'] ) ) {
136  if ( PHP_SAPI === 'cli' ) {
137  $this->srvCache = wfGetMainCache(); // preferrably memcached
138  } else {
139  try { // look for APC, XCache, WinCache, ect...
140  $this->srvCache = ObjectCache::newAccelerator( array() );
141  } catch ( Exception $e ) {
142  }
143  }
144  }
145  $this->srvCache = $this->srvCache ?: new EmptyBagOStuff();
146  }
147 
148  public function getFeatures() {
150  }
151 
152  protected function resolveContainerPath( $container, $relStoragePath ) {
153  if ( !mb_check_encoding( $relStoragePath, 'UTF-8' ) ) { // mb_string required by CF
154  return null; // not UTF-8, makes it hard to use CF and the swift HTTP API
155  } elseif ( strlen( urlencode( $relStoragePath ) ) > 1024 ) {
156  return null; // too long for Swift
157  }
158 
159  return $relStoragePath;
160  }
161 
162  public function isPathUsableInternal( $storagePath ) {
163  list( $container, $rel ) = $this->resolveStoragePathReal( $storagePath );
164  if ( $rel === null ) {
165  return false; // invalid
166  }
167 
168  return is_array( $this->getContainerStat( $container ) );
169  }
170 
178  protected function sanitizeHdrs( array $params ) {
179  $headers = array();
180 
181  // Normalize casing, and strip out illegal headers
182  if ( isset( $params['headers'] ) ) {
183  foreach ( $params['headers'] as $name => $value ) {
184  $name = strtolower( $name );
185  if ( preg_match( '/^content-(type|length)$/', $name ) ) {
186  continue; // blacklisted
187  } elseif ( preg_match( '/^(x-)?content-/', $name ) ) {
188  $headers[$name] = $value; // allowed
189  } elseif ( preg_match( '/^content-(disposition)/', $name ) ) {
190  $headers[$name] = $value; // allowed
191  }
192  }
193  }
194  // By default, Swift has annoyingly low maximum header value limits
195  if ( isset( $headers['content-disposition'] ) ) {
196  $disposition = '';
197  foreach ( explode( ';', $headers['content-disposition'] ) as $part ) {
198  $part = trim( $part );
199  $new = ( $disposition === '' ) ? $part : "{$disposition};{$part}";
200  if ( strlen( $new ) <= 255 ) {
201  $disposition = $new;
202  } else {
203  break; // too long; sigh
204  }
205  }
206  $headers['content-disposition'] = $disposition;
207  }
208 
209  return $headers;
210  }
211 
212  protected function doCreateInternal( array $params ) {
213  $status = Status::newGood();
214 
215  list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
216  if ( $dstRel === null ) {
217  $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
218 
219  return $status;
220  }
221 
222  $sha1Hash = wfBaseConvert( sha1( $params['content'] ), 16, 36, 31 );
223  $contentType = $this->getContentType( $params['dst'], $params['content'], null );
224 
225  $reqs = array( array(
226  'method' => 'PUT',
227  'url' => array( $dstCont, $dstRel ),
228  'headers' => array(
229  'content-length' => strlen( $params['content'] ),
230  'etag' => md5( $params['content'] ),
231  'content-type' => $contentType,
232  'x-object-meta-sha1base36' => $sha1Hash
233  ) + $this->sanitizeHdrs( $params ),
234  'body' => $params['content']
235  ) );
236 
237  $be = $this;
238  $method = __METHOD__;
239  $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) {
240  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
241  if ( $rcode === 201 ) {
242  // good
243  } elseif ( $rcode === 412 ) {
244  $status->fatal( 'backend-fail-contenttype', $params['dst'] );
245  } else {
246  $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
247  }
248  };
249 
250  $opHandle = new SwiftFileOpHandle( $this, $handler, $reqs );
251  if ( !empty( $params['async'] ) ) { // deferred
252  $status->value = $opHandle;
253  } else { // actually write the object in Swift
254  $status->merge( current( $this->doExecuteOpHandlesInternal( array( $opHandle ) ) ) );
255  }
256 
257  return $status;
258  }
259 
260  protected function doStoreInternal( array $params ) {
261  $status = Status::newGood();
262 
263  list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
264  if ( $dstRel === null ) {
265  $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
266 
267  return $status;
268  }
269 
271  $sha1Hash = sha1_file( $params['src'] );
273  if ( $sha1Hash === false ) { // source doesn't exist?
274  $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
275 
276  return $status;
277  }
278  $sha1Hash = wfBaseConvert( $sha1Hash, 16, 36, 31 );
279  $contentType = $this->getContentType( $params['dst'], null, $params['src'] );
280 
281  $handle = fopen( $params['src'], 'rb' );
282  if ( $handle === false ) { // source doesn't exist?
283  $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
284 
285  return $status;
286  }
287 
288  $reqs = array( array(
289  'method' => 'PUT',
290  'url' => array( $dstCont, $dstRel ),
291  'headers' => array(
292  'content-length' => filesize( $params['src'] ),
293  'etag' => md5_file( $params['src'] ),
294  'content-type' => $contentType,
295  'x-object-meta-sha1base36' => $sha1Hash
296  ) + $this->sanitizeHdrs( $params ),
297  'body' => $handle // resource
298  ) );
299 
300  $be = $this;
301  $method = __METHOD__;
302  $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) {
303  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
304  if ( $rcode === 201 ) {
305  // good
306  } elseif ( $rcode === 412 ) {
307  $status->fatal( 'backend-fail-contenttype', $params['dst'] );
308  } else {
309  $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
310  }
311  };
312 
313  $opHandle = new SwiftFileOpHandle( $this, $handler, $reqs );
314  if ( !empty( $params['async'] ) ) { // deferred
315  $status->value = $opHandle;
316  } else { // actually write the object in Swift
317  $status->merge( current( $this->doExecuteOpHandlesInternal( array( $opHandle ) ) ) );
318  }
319 
320  return $status;
321  }
322 
323  protected function doCopyInternal( array $params ) {
324  $status = Status::newGood();
325 
326  list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
327  if ( $srcRel === null ) {
328  $status->fatal( 'backend-fail-invalidpath', $params['src'] );
329 
330  return $status;
331  }
332 
333  list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
334  if ( $dstRel === null ) {
335  $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
336 
337  return $status;
338  }
339 
340  $reqs = array( array(
341  'method' => 'PUT',
342  'url' => array( $dstCont, $dstRel ),
343  'headers' => array(
344  'x-copy-from' => '/' . rawurlencode( $srcCont ) .
345  '/' . str_replace( "%2F", "/", rawurlencode( $srcRel ) )
346  ) + $this->sanitizeHdrs( $params ), // extra headers merged into object
347  ) );
348 
349  $be = $this;
350  $method = __METHOD__;
351  $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) {
352  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
353  if ( $rcode === 201 ) {
354  // good
355  } elseif ( $rcode === 404 ) {
356  $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
357  } else {
358  $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
359  }
360  };
361 
362  $opHandle = new SwiftFileOpHandle( $this, $handler, $reqs );
363  if ( !empty( $params['async'] ) ) { // deferred
364  $status->value = $opHandle;
365  } else { // actually write the object in Swift
366  $status->merge( current( $this->doExecuteOpHandlesInternal( array( $opHandle ) ) ) );
367  }
368 
369  return $status;
370  }
371 
372  protected function doMoveInternal( array $params ) {
373  $status = Status::newGood();
374 
375  list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
376  if ( $srcRel === null ) {
377  $status->fatal( 'backend-fail-invalidpath', $params['src'] );
378 
379  return $status;
380  }
381 
382  list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
383  if ( $dstRel === null ) {
384  $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
385 
386  return $status;
387  }
388 
389  $reqs = array(
390  array(
391  'method' => 'PUT',
392  'url' => array( $dstCont, $dstRel ),
393  'headers' => array(
394  'x-copy-from' => '/' . rawurlencode( $srcCont ) .
395  '/' . str_replace( "%2F", "/", rawurlencode( $srcRel ) )
396  ) + $this->sanitizeHdrs( $params ) // extra headers merged into object
397  )
398  );
399  if ( "{$srcCont}/{$srcRel}" !== "{$dstCont}/{$dstRel}" ) {
400  $reqs[] = array(
401  'method' => 'DELETE',
402  'url' => array( $srcCont, $srcRel ),
403  'headers' => array()
404  );
405  }
406 
407  $be = $this;
408  $method = __METHOD__;
409  $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) {
410  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
411  if ( $request['method'] === 'PUT' && $rcode === 201 ) {
412  // good
413  } elseif ( $request['method'] === 'DELETE' && $rcode === 204 ) {
414  // good
415  } elseif ( $rcode === 404 ) {
416  $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
417  } else {
418  $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
419  }
420  };
421 
422  $opHandle = new SwiftFileOpHandle( $this, $handler, $reqs );
423  if ( !empty( $params['async'] ) ) { // deferred
424  $status->value = $opHandle;
425  } else { // actually move the object in Swift
426  $status->merge( current( $this->doExecuteOpHandlesInternal( array( $opHandle ) ) ) );
427  }
428 
429  return $status;
430  }
431 
432  protected function doDeleteInternal( array $params ) {
433  $status = Status::newGood();
434 
435  list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
436  if ( $srcRel === null ) {
437  $status->fatal( 'backend-fail-invalidpath', $params['src'] );
438 
439  return $status;
440  }
441 
442  $reqs = array( array(
443  'method' => 'DELETE',
444  'url' => array( $srcCont, $srcRel ),
445  'headers' => array()
446  ) );
447 
448  $be = $this;
449  $method = __METHOD__;
450  $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) {
451  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
452  if ( $rcode === 204 ) {
453  // good
454  } elseif ( $rcode === 404 ) {
455  if ( empty( $params['ignoreMissingSource'] ) ) {
456  $status->fatal( 'backend-fail-delete', $params['src'] );
457  }
458  } else {
459  $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
460  }
461  };
462 
463  $opHandle = new SwiftFileOpHandle( $this, $handler, $reqs );
464  if ( !empty( $params['async'] ) ) { // deferred
465  $status->value = $opHandle;
466  } else { // actually delete the object in Swift
467  $status->merge( current( $this->doExecuteOpHandlesInternal( array( $opHandle ) ) ) );
468  }
469 
470  return $status;
471  }
472 
473  protected function doDescribeInternal( array $params ) {
474  $status = Status::newGood();
475 
476  list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
477  if ( $srcRel === null ) {
478  $status->fatal( 'backend-fail-invalidpath', $params['src'] );
479 
480  return $status;
481  }
482 
483  // Fetch the old object headers/metadata...this should be in stat cache by now
484  $stat = $this->getFileStat( array( 'src' => $params['src'], 'latest' => 1 ) );
485  if ( $stat && !isset( $stat['xattr'] ) ) { // older cache entry
486  $stat = $this->doGetFileStat( array( 'src' => $params['src'], 'latest' => 1 ) );
487  }
488  if ( !$stat ) {
489  $status->fatal( 'backend-fail-describe', $params['src'] );
490 
491  return $status;
492  }
493 
494  // POST clears prior headers, so we need to merge the changes in to the old ones
495  $metaHdrs = array();
496  foreach ( $stat['xattr']['metadata'] as $name => $value ) {
497  $metaHdrs["x-object-meta-$name"] = $value;
498  }
499  $customHdrs = $this->sanitizeHdrs( $params ) + $stat['xattr']['headers'];
500 
501  $reqs = array( array(
502  'method' => 'POST',
503  'url' => array( $srcCont, $srcRel ),
504  'headers' => $metaHdrs + $customHdrs
505  ) );
506 
507  $be = $this;
508  $method = __METHOD__;
509  $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) {
510  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
511  if ( $rcode === 202 ) {
512  // good
513  } elseif ( $rcode === 404 ) {
514  $status->fatal( 'backend-fail-describe', $params['src'] );
515  } else {
516  $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
517  }
518  };
519 
520  $opHandle = new SwiftFileOpHandle( $this, $handler, $reqs );
521  if ( !empty( $params['async'] ) ) { // deferred
522  $status->value = $opHandle;
523  } else { // actually change the object in Swift
524  $status->merge( current( $this->doExecuteOpHandlesInternal( array( $opHandle ) ) ) );
525  }
526 
527  return $status;
528  }
529 
530  protected function doPrepareInternal( $fullCont, $dir, array $params ) {
531  $status = Status::newGood();
532 
533  // (a) Check if container already exists
534  $stat = $this->getContainerStat( $fullCont );
535  if ( is_array( $stat ) ) {
536  return $status; // already there
537  } elseif ( $stat === null ) {
538  $status->fatal( 'backend-fail-internal', $this->name );
539 
540  return $status;
541  }
542 
543  // (b) Create container as needed with proper ACLs
544  if ( $stat === false ) {
545  $params['op'] = 'prepare';
546  $status->merge( $this->createContainer( $fullCont, $params ) );
547  }
548 
549  return $status;
550  }
551 
552  protected function doSecureInternal( $fullCont, $dir, array $params ) {
553  $status = Status::newGood();
554  if ( empty( $params['noAccess'] ) ) {
555  return $status; // nothing to do
556  }
557 
558  $stat = $this->getContainerStat( $fullCont );
559  if ( is_array( $stat ) ) {
560  // Make container private to end-users...
561  $status->merge( $this->setContainerAccess(
562  $fullCont,
563  array( $this->swiftUser ), // read
564  array( $this->swiftUser ) // write
565  ) );
566  } elseif ( $stat === false ) {
567  $status->fatal( 'backend-fail-usable', $params['dir'] );
568  } else {
569  $status->fatal( 'backend-fail-internal', $this->name );
570  }
571 
572  return $status;
573  }
574 
575  protected function doPublishInternal( $fullCont, $dir, array $params ) {
576  $status = Status::newGood();
577 
578  $stat = $this->getContainerStat( $fullCont );
579  if ( is_array( $stat ) ) {
580  // Make container public to end-users...
581  $status->merge( $this->setContainerAccess(
582  $fullCont,
583  array( $this->swiftUser, '.r:*' ), // read
584  array( $this->swiftUser ) // write
585  ) );
586  } elseif ( $stat === false ) {
587  $status->fatal( 'backend-fail-usable', $params['dir'] );
588  } else {
589  $status->fatal( 'backend-fail-internal', $this->name );
590  }
591 
592  return $status;
593  }
594 
595  protected function doCleanInternal( $fullCont, $dir, array $params ) {
596  $status = Status::newGood();
597 
598  // Only containers themselves can be removed, all else is virtual
599  if ( $dir != '' ) {
600  return $status; // nothing to do
601  }
602 
603  // (a) Check the container
604  $stat = $this->getContainerStat( $fullCont, true );
605  if ( $stat === false ) {
606  return $status; // ok, nothing to do
607  } elseif ( !is_array( $stat ) ) {
608  $status->fatal( 'backend-fail-internal', $this->name );
609 
610  return $status;
611  }
612 
613  // (b) Delete the container if empty
614  if ( $stat['count'] == 0 ) {
615  $params['op'] = 'clean';
616  $status->merge( $this->deleteContainer( $fullCont, $params ) );
617  }
618 
619  return $status;
620  }
621 
622  protected function doGetFileStat( array $params ) {
623  $params = array( 'srcs' => array( $params['src'] ), 'concurrency' => 1 ) + $params;
624  unset( $params['src'] );
625  $stats = $this->doGetFileStatMulti( $params );
626 
627  return reset( $stats );
628  }
629 
640  protected function convertSwiftDate( $ts, $format = TS_MW ) {
641  try {
642  $timestamp = new MWTimestamp( $ts );
643 
644  return $timestamp->getTimestamp( $format );
645  } catch ( MWException $e ) {
646  throw new FileBackendError( $e->getMessage() );
647  }
648  }
649 
657  protected function addMissingMetadata( array $objHdrs, $path ) {
658  if ( isset( $objHdrs['x-object-meta-sha1base36'] ) ) {
659  return $objHdrs; // nothing to do
660  }
661 
662  $section = new ProfileSection( __METHOD__ . '-' . $this->name );
663  trigger_error( "$path was not stored with SHA-1 metadata.", E_USER_WARNING );
664 
665  $auth = $this->getAuthentication();
666  if ( !$auth ) {
667  $objHdrs['x-object-meta-sha1base36'] = false;
668 
669  return $objHdrs; // failed
670  }
671 
672  $status = Status::newGood();
673  $scopeLockS = $this->getScopedFileLocks( array( $path ), LockManager::LOCK_UW, $status );
674  if ( $status->isOK() ) {
675  $tmpFile = $this->getLocalCopy( array( 'src' => $path, 'latest' => 1 ) );
676  if ( $tmpFile ) {
677  $hash = $tmpFile->getSha1Base36();
678  if ( $hash !== false ) {
679  $objHdrs['x-object-meta-sha1base36'] = $hash;
680  list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $path );
681  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( array(
682  'method' => 'POST',
683  'url' => $this->storageUrl( $auth, $srcCont, $srcRel ),
684  'headers' => $this->authTokenHeaders( $auth ) + $objHdrs
685  ) );
686  if ( $rcode >= 200 && $rcode <= 299 ) {
687  return $objHdrs; // success
688  }
689  }
690  }
691  }
692  trigger_error( "Unable to set SHA-1 metadata for $path", E_USER_WARNING );
693  $objHdrs['x-object-meta-sha1base36'] = false;
694 
695  return $objHdrs; // failed
696  }
697 
698  protected function doGetFileContentsMulti( array $params ) {
699  $contents = array();
700 
701  $auth = $this->getAuthentication();
702 
703  $ep = array_diff_key( $params, array( 'srcs' => 1 ) ); // for error logging
704  // Blindly create tmp files and stream to them, catching any exception if the file does
705  // not exist. Doing stats here is useless and will loop infinitely in addMissingMetadata().
706  $reqs = array(); // (path => op)
707 
708  foreach ( $params['srcs'] as $path ) { // each path in this concurrent batch
709  list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $path );
710  if ( $srcRel === null || !$auth ) {
711  $contents[$path] = false;
712  continue;
713  }
714  // Create a new temporary memory file...
715  $handle = fopen( 'php://temp', 'wb' );
716  if ( $handle ) {
717  $reqs[$path] = array(
718  'method' => 'GET',
719  'url' => $this->storageUrl( $auth, $srcCont, $srcRel ),
720  'headers' => $this->authTokenHeaders( $auth )
721  + $this->headersFromParams( $params ),
722  'stream' => $handle,
723  );
724  }
725  $contents[$path] = false;
726  }
727 
728  $opts = array( 'maxConnsPerHost' => $params['concurrency'] );
729  $reqs = $this->http->runMulti( $reqs, $opts );
730  foreach ( $reqs as $path => $op ) {
731  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $op['response'];
732  if ( $rcode >= 200 && $rcode <= 299 ) {
733  rewind( $op['stream'] ); // start from the beginning
734  $contents[$path] = stream_get_contents( $op['stream'] );
735  } elseif ( $rcode === 404 ) {
736  $contents[$path] = false;
737  } else {
738  $this->onError( null, __METHOD__,
739  array( 'src' => $path ) + $ep, $rerr, $rcode, $rdesc );
740  }
741  fclose( $op['stream'] ); // close open handle
742  }
743 
744  return $contents;
745  }
746 
747  protected function doDirectoryExists( $fullCont, $dir, array $params ) {
748  $prefix = ( $dir == '' ) ? null : "{$dir}/";
749  $status = $this->objectListing( $fullCont, 'names', 1, null, $prefix );
750  if ( $status->isOk() ) {
751  return ( count( $status->value ) ) > 0;
752  }
753 
754  return null; // error
755  }
756 
764  public function getDirectoryListInternal( $fullCont, $dir, array $params ) {
765  return new SwiftFileBackendDirList( $this, $fullCont, $dir, $params );
766  }
767 
775  public function getFileListInternal( $fullCont, $dir, array $params ) {
776  return new SwiftFileBackendFileList( $this, $fullCont, $dir, $params );
777  }
778 
790  public function getDirListPageInternal( $fullCont, $dir, &$after, $limit, array $params ) {
791  $dirs = array();
792  if ( $after === INF ) {
793  return $dirs; // nothing more
794  }
795 
796  $section = new ProfileSection( __METHOD__ . '-' . $this->name );
797 
798  $prefix = ( $dir == '' ) ? null : "{$dir}/";
799  // Non-recursive: only list dirs right under $dir
800  if ( !empty( $params['topOnly'] ) ) {
801  $status = $this->objectListing( $fullCont, 'names', $limit, $after, $prefix, '/' );
802  if ( !$status->isOk() ) {
803  return $dirs; // error
804  }
805  $objects = $status->value;
806  foreach ( $objects as $object ) { // files and directories
807  if ( substr( $object, -1 ) === '/' ) {
808  $dirs[] = $object; // directories end in '/'
809  }
810  }
811  } else {
812  // Recursive: list all dirs under $dir and its subdirs
813  $getParentDir = function ( $path ) {
814  return ( strpos( $path, '/' ) !== false ) ? dirname( $path ) : false;
815  };
816 
817  // Get directory from last item of prior page
818  $lastDir = $getParentDir( $after ); // must be first page
819  $status = $this->objectListing( $fullCont, 'names', $limit, $after, $prefix );
820 
821  if ( !$status->isOk() ) {
822  return $dirs; // error
823  }
824 
825  $objects = $status->value;
826 
827  foreach ( $objects as $object ) { // files
828  $objectDir = $getParentDir( $object ); // directory of object
829 
830  if ( $objectDir !== false && $objectDir !== $dir ) {
831  // Swift stores paths in UTF-8, using binary sorting.
832  // See function "create_container_table" in common/db.py.
833  // If a directory is not "greater" than the last one,
834  // then it was already listed by the calling iterator.
835  if ( strcmp( $objectDir, $lastDir ) > 0 ) {
836  $pDir = $objectDir;
837  do { // add dir and all its parent dirs
838  $dirs[] = "{$pDir}/";
839  $pDir = $getParentDir( $pDir );
840  } while ( $pDir !== false // sanity
841  && strcmp( $pDir, $lastDir ) > 0 // not done already
842  && strlen( $pDir ) > strlen( $dir ) // within $dir
843  );
844  }
845  $lastDir = $objectDir;
846  }
847  }
848  }
849  // Page on the unfiltered directory listing (what is returned may be filtered)
850  if ( count( $objects ) < $limit ) {
851  $after = INF; // avoid a second RTT
852  } else {
853  $after = end( $objects ); // update last item
854  }
855 
856  return $dirs;
857  }
858 
870  public function getFileListPageInternal( $fullCont, $dir, &$after, $limit, array $params ) {
871  $files = array(); // list of (path, stat array or null) entries
872  if ( $after === INF ) {
873  return $files; // nothing more
874  }
875 
876  $section = new ProfileSection( __METHOD__ . '-' . $this->name );
877 
878  $prefix = ( $dir == '' ) ? null : "{$dir}/";
879  // $objects will contain a list of unfiltered names or CF_Object items
880  // Non-recursive: only list files right under $dir
881  if ( !empty( $params['topOnly'] ) ) {
882  if ( !empty( $params['adviseStat'] ) ) {
883  $status = $this->objectListing( $fullCont, 'info', $limit, $after, $prefix, '/' );
884  } else {
885  $status = $this->objectListing( $fullCont, 'names', $limit, $after, $prefix, '/' );
886  }
887  } else {
888  // Recursive: list all files under $dir and its subdirs
889  if ( !empty( $params['adviseStat'] ) ) {
890  $status = $this->objectListing( $fullCont, 'info', $limit, $after, $prefix );
891  } else {
892  $status = $this->objectListing( $fullCont, 'names', $limit, $after, $prefix );
893  }
894  }
895 
896  // Reformat this list into a list of (name, stat array or null) entries
897  if ( !$status->isOk() ) {
898  return $files; // error
899  }
900 
901  $objects = $status->value;
902  $files = $this->buildFileObjectListing( $params, $dir, $objects );
903 
904  // Page on the unfiltered object listing (what is returned may be filtered)
905  if ( count( $objects ) < $limit ) {
906  $after = INF; // avoid a second RTT
907  } else {
908  $after = end( $objects ); // update last item
909  $after = is_object( $after ) ? $after->name : $after;
910  }
911 
912  return $files;
913  }
914 
924  private function buildFileObjectListing( array $params, $dir, array $objects ) {
925  $names = array();
926  foreach ( $objects as $object ) {
927  if ( is_object( $object ) ) {
928  if ( isset( $object->subdir ) || !isset( $object->name ) ) {
929  continue; // virtual directory entry; ignore
930  }
931  $stat = array(
932  // Convert various random Swift dates to TS_MW
933  'mtime' => $this->convertSwiftDate( $object->last_modified, TS_MW ),
934  'size' => (int)$object->bytes,
935  // Note: manifiest ETags are not an MD5 of the file
936  'md5' => ctype_xdigit( $object->hash ) ? $object->hash : null,
937  'latest' => false // eventually consistent
938  );
939  $names[] = array( $object->name, $stat );
940  } elseif ( substr( $object, -1 ) !== '/' ) {
941  // Omit directories, which end in '/' in listings
942  $names[] = array( $object, null );
943  }
944  }
945 
946  return $names;
947  }
948 
955  public function loadListingStatInternal( $path, array $val ) {
956  $this->cheapCache->set( $path, 'stat', $val );
957  }
958 
959  protected function doGetFileXAttributes( array $params ) {
960  $stat = $this->getFileStat( $params );
961  if ( $stat ) {
962  if ( !isset( $stat['xattr'] ) ) {
963  // Stat entries filled by file listings don't include metadata/headers
964  $this->clearCache( array( $params['src'] ) );
965  $stat = $this->getFileStat( $params );
966  }
967 
968  return $stat['xattr'];
969  } else {
970  return false;
971  }
972  }
973 
974  protected function doGetFileSha1base36( array $params ) {
975  $stat = $this->getFileStat( $params );
976  if ( $stat ) {
977  if ( !isset( $stat['sha1'] ) ) {
978  // Stat entries filled by file listings don't include SHA1
979  $this->clearCache( array( $params['src'] ) );
980  $stat = $this->getFileStat( $params );
981  }
982 
983  return $stat['sha1'];
984  } else {
985  return false;
986  }
987  }
988 
989  protected function doStreamFile( array $params ) {
990  $status = Status::newGood();
991 
992  list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
993  if ( $srcRel === null ) {
994  $status->fatal( 'backend-fail-invalidpath', $params['src'] );
995  }
996 
997  $auth = $this->getAuthentication();
998  if ( !$auth || !is_array( $this->getContainerStat( $srcCont ) ) ) {
999  $status->fatal( 'backend-fail-stream', $params['src'] );
1000 
1001  return $status;
1002  }
1003 
1004  $handle = fopen( 'php://output', 'wb' );
1005 
1006  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( array(
1007  'method' => 'GET',
1008  'url' => $this->storageUrl( $auth, $srcCont, $srcRel ),
1009  'headers' => $this->authTokenHeaders( $auth )
1010  + $this->headersFromParams( $params ),
1011  'stream' => $handle,
1012  ) );
1013 
1014  if ( $rcode >= 200 && $rcode <= 299 ) {
1015  // good
1016  } elseif ( $rcode === 404 ) {
1017  $status->fatal( 'backend-fail-stream', $params['src'] );
1018  } else {
1019  $this->onError( $status, __METHOD__, $params, $rerr, $rcode, $rdesc );
1020  }
1021 
1022  return $status;
1023  }
1024 
1025  protected function doGetLocalCopyMulti( array $params ) {
1026  $tmpFiles = array();
1027 
1028  $auth = $this->getAuthentication();
1029 
1030  $ep = array_diff_key( $params, array( 'srcs' => 1 ) ); // for error logging
1031  // Blindly create tmp files and stream to them, catching any exception if the file does
1032  // not exist. Doing a stat here is useless causes infinite loops in addMissingMetadata().
1033  $reqs = array(); // (path => op)
1034 
1035  foreach ( $params['srcs'] as $path ) { // each path in this concurrent batch
1036  list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $path );
1037  if ( $srcRel === null || !$auth ) {
1038  $tmpFiles[$path] = null;
1039  continue;
1040  }
1041  // Get source file extension
1043  // Create a new temporary file...
1044  $tmpFile = TempFSFile::factory( 'localcopy_', $ext );
1045  if ( $tmpFile ) {
1046  $handle = fopen( $tmpFile->getPath(), 'wb' );
1047  if ( $handle ) {
1048  $reqs[$path] = array(
1049  'method' => 'GET',
1050  'url' => $this->storageUrl( $auth, $srcCont, $srcRel ),
1051  'headers' => $this->authTokenHeaders( $auth )
1052  + $this->headersFromParams( $params ),
1053  'stream' => $handle,
1054  );
1055  } else {
1056  $tmpFile = null;
1057  }
1058  }
1059  $tmpFiles[$path] = $tmpFile;
1060  }
1061 
1062  $opts = array( 'maxConnsPerHost' => $params['concurrency'] );
1063  $reqs = $this->http->runMulti( $reqs, $opts );
1064  foreach ( $reqs as $path => $op ) {
1065  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $op['response'];
1066  fclose( $op['stream'] ); // close open handle
1067  if ( $rcode >= 200 && $rcode <= 299
1068  // double check that the disk is not full/broken
1069  && $tmpFiles[$path]->getSize() == $rhdrs['content-length']
1070  ) {
1071  // good
1072  } elseif ( $rcode === 404 ) {
1073  $tmpFiles[$path] = false;
1074  } else {
1075  $tmpFiles[$path] = null;
1076  $this->onError( null, __METHOD__,
1077  array( 'src' => $path ) + $ep, $rerr, $rcode, $rdesc );
1078  }
1079  }
1080 
1081  return $tmpFiles;
1082  }
1083 
1084  public function getFileHttpUrl( array $params ) {
1085  if ( $this->swiftTempUrlKey != '' ||
1086  ( $this->rgwS3AccessKey != '' && $this->rgwS3SecretKey != '' )
1087  ) {
1088  list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
1089  if ( $srcRel === null ) {
1090  return null; // invalid path
1091  }
1092 
1093  $auth = $this->getAuthentication();
1094  if ( !$auth ) {
1095  return null;
1096  }
1097 
1098  $ttl = isset( $params['ttl'] ) ? $params['ttl'] : 86400;
1099  $expires = time() + $ttl;
1100 
1101  if ( $this->swiftTempUrlKey != '' ) {
1102  $url = $this->storageUrl( $auth, $srcCont, $srcRel );
1103  // Swift wants the signature based on the unencoded object name
1104  $contPath = parse_url( $this->storageUrl( $auth, $srcCont ), PHP_URL_PATH );
1105  $signature = hash_hmac( 'sha1',
1106  "GET\n{$expires}\n{$contPath}/{$srcRel}",
1107  $this->swiftTempUrlKey
1108  );
1109 
1110  return "{$url}?temp_url_sig={$signature}&temp_url_expires={$expires}";
1111  } else { // give S3 API URL for rgw
1112  // Path for signature starts with the bucket
1113  $spath = '/' . rawurlencode( $srcCont ) . '/' .
1114  str_replace( '%2F', '/', rawurlencode( $srcRel ) );
1115  // Calculate the hash
1116  $signature = base64_encode( hash_hmac(
1117  'sha1',
1118  "GET\n\n\n{$expires}\n{$spath}",
1119  $this->rgwS3SecretKey,
1120  true // raw
1121  ) );
1122  // See http://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html.
1123  // Note: adding a newline for empty CanonicalizedAmzHeaders does not work.
1125  str_replace( '/swift/v1', '', // S3 API is the rgw default
1126  $this->storageUrl( $auth ) . $spath ),
1127  array(
1128  'Signature' => $signature,
1129  'Expires' => $expires,
1130  'AWSAccessKeyId' => $this->rgwS3AccessKey )
1131  );
1132  }
1133  }
1134 
1135  return null;
1136  }
1137 
1138  protected function directoriesAreVirtual() {
1139  return true;
1140  }
1141 
1150  protected function headersFromParams( array $params ) {
1151  $hdrs = array();
1152  if ( !empty( $params['latest'] ) ) {
1153  $hdrs['x-newest'] = 'true';
1154  }
1155 
1156  return $hdrs;
1157  }
1158 
1159  protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
1160  $statuses = array();
1161 
1162  $auth = $this->getAuthentication();
1163  if ( !$auth ) {
1164  foreach ( $fileOpHandles as $index => $fileOpHandle ) {
1165  $statuses[$index] = Status::newFatal( 'backend-fail-connect', $this->name );
1166  }
1167 
1168  return $statuses;
1169  }
1170 
1171  // Split the HTTP requests into stages that can be done concurrently
1172  $httpReqsByStage = array(); // map of (stage => index => HTTP request)
1173  foreach ( $fileOpHandles as $index => $fileOpHandle ) {
1174  $reqs = $fileOpHandle->httpOp;
1175  // Convert the 'url' parameter to an actual URL using $auth
1176  foreach ( $reqs as $stage => &$req ) {
1177  list( $container, $relPath ) = $req['url'];
1178  $req['url'] = $this->storageUrl( $auth, $container, $relPath );
1179  $req['headers'] = isset( $req['headers'] ) ? $req['headers'] : array();
1180  $req['headers'] = $this->authTokenHeaders( $auth ) + $req['headers'];
1181  $httpReqsByStage[$stage][$index] = $req;
1182  }
1183  $statuses[$index] = Status::newGood();
1184  }
1185 
1186  // Run all requests for the first stage, then the next, and so on
1187  $reqCount = count( $httpReqsByStage );
1188  for ( $stage = 0; $stage < $reqCount; ++$stage ) {
1189  $httpReqs = $this->http->runMulti( $httpReqsByStage[$stage] );
1190  foreach ( $httpReqs as $index => $httpReq ) {
1191  // Run the callback for each request of this operation
1192  $callback = $fileOpHandles[$index]->callback;
1193  call_user_func_array( $callback, array( $httpReq, $statuses[$index] ) );
1194  // On failure, abort all remaining requests for this operation
1195  // (e.g. abort the DELETE request if the COPY request fails for a move)
1196  if ( !$statuses[$index]->isOK() ) {
1197  $stages = count( $fileOpHandles[$index]->httpOp );
1198  for ( $s = ( $stage + 1 ); $s < $stages; ++$s ) {
1199  unset( $httpReqsByStage[$s][$index] );
1200  }
1201  }
1202  }
1203  }
1204 
1205  return $statuses;
1206  }
1207 
1230  protected function setContainerAccess( $container, array $readGrps, array $writeGrps ) {
1231  $status = Status::newGood();
1232  $auth = $this->getAuthentication();
1233 
1234  if ( !$auth ) {
1235  $status->fatal( 'backend-fail-connect', $this->name );
1236 
1237  return $status;
1238  }
1239 
1240  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( array(
1241  'method' => 'POST',
1242  'url' => $this->storageUrl( $auth, $container ),
1243  'headers' => $this->authTokenHeaders( $auth ) + array(
1244  'x-container-read' => implode( ',', $readGrps ),
1245  'x-container-write' => implode( ',', $writeGrps )
1246  )
1247  ) );
1248 
1249  if ( $rcode != 204 && $rcode !== 202 ) {
1250  $status->fatal( 'backend-fail-internal', $this->name );
1251  }
1252 
1253  return $status;
1254  }
1255 
1264  protected function getContainerStat( $container, $bypassCache = false ) {
1265  $section = new ProfileSection( __METHOD__ . '-' . $this->name );
1266 
1267  if ( $bypassCache ) { // purge cache
1268  $this->containerStatCache->clear( $container );
1269  } elseif ( !$this->containerStatCache->has( $container, 'stat' ) ) {
1270  $this->primeContainerCache( array( $container ) ); // check persistent cache
1271  }
1272  if ( !$this->containerStatCache->has( $container, 'stat' ) ) {
1273  $auth = $this->getAuthentication();
1274  if ( !$auth ) {
1275  return null;
1276  }
1277 
1278  wfProfileIn( __METHOD__ . "-{$this->name}-miss" );
1279  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( array(
1280  'method' => 'HEAD',
1281  'url' => $this->storageUrl( $auth, $container ),
1282  'headers' => $this->authTokenHeaders( $auth )
1283  ) );
1284  wfProfileOut( __METHOD__ . "-{$this->name}-miss" );
1285 
1286  if ( $rcode === 204 ) {
1287  $stat = array(
1288  'count' => $rhdrs['x-container-object-count'],
1289  'bytes' => $rhdrs['x-container-bytes-used']
1290  );
1291  if ( $bypassCache ) {
1292  return $stat;
1293  } else {
1294  $this->containerStatCache->set( $container, 'stat', $stat ); // cache it
1295  $this->setContainerCache( $container, $stat ); // update persistent cache
1296  }
1297  } elseif ( $rcode === 404 ) {
1298  return false;
1299  } else {
1300  $this->onError( null, __METHOD__,
1301  array( 'cont' => $container ), $rerr, $rcode, $rdesc );
1302 
1303  return null;
1304  }
1305  }
1306 
1307  return $this->containerStatCache->get( $container, 'stat' );
1308  }
1309 
1317  protected function createContainer( $container, array $params ) {
1318  $status = Status::newGood();
1319 
1320  $auth = $this->getAuthentication();
1321  if ( !$auth ) {
1322  $status->fatal( 'backend-fail-connect', $this->name );
1323 
1324  return $status;
1325  }
1326 
1327  // @see SwiftFileBackend::setContainerAccess()
1328  if ( empty( $params['noAccess'] ) ) {
1329  $readGrps = array( '.r:*', $this->swiftUser ); // public
1330  } else {
1331  $readGrps = array( $this->swiftUser ); // private
1332  }
1333  $writeGrps = array( $this->swiftUser ); // sanity
1334 
1335  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( array(
1336  'method' => 'PUT',
1337  'url' => $this->storageUrl( $auth, $container ),
1338  'headers' => $this->authTokenHeaders( $auth ) + array(
1339  'x-container-read' => implode( ',', $readGrps ),
1340  'x-container-write' => implode( ',', $writeGrps )
1341  )
1342  ) );
1343 
1344  if ( $rcode === 201 ) { // new
1345  // good
1346  } elseif ( $rcode === 202 ) { // already there
1347  // this shouldn't really happen, but is OK
1348  } else {
1349  $this->onError( $status, __METHOD__, $params, $rerr, $rcode, $rdesc );
1350  }
1351 
1352  return $status;
1353  }
1354 
1362  protected function deleteContainer( $container, array $params ) {
1363  $status = Status::newGood();
1364 
1365  $auth = $this->getAuthentication();
1366  if ( !$auth ) {
1367  $status->fatal( 'backend-fail-connect', $this->name );
1368 
1369  return $status;
1370  }
1371 
1372  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( array(
1373  'method' => 'DELETE',
1374  'url' => $this->storageUrl( $auth, $container ),
1375  'headers' => $this->authTokenHeaders( $auth )
1376  ) );
1377 
1378  if ( $rcode >= 200 && $rcode <= 299 ) { // deleted
1379  $this->containerStatCache->clear( $container ); // purge
1380  } elseif ( $rcode === 404 ) { // not there
1381  // this shouldn't really happen, but is OK
1382  } elseif ( $rcode === 409 ) { // not empty
1383  $this->onError( $status, __METHOD__, $params, $rerr, $rcode, $rdesc ); // race?
1384  } else {
1385  $this->onError( $status, __METHOD__, $params, $rerr, $rcode, $rdesc );
1386  }
1387 
1388  return $status;
1389  }
1390 
1403  private function objectListing(
1404  $fullCont, $type, $limit, $after = null, $prefix = null, $delim = null
1405  ) {
1406  $status = Status::newGood();
1407 
1408  $auth = $this->getAuthentication();
1409  if ( !$auth ) {
1410  $status->fatal( 'backend-fail-connect', $this->name );
1411 
1412  return $status;
1413  }
1414 
1415  $query = array( 'limit' => $limit );
1416  if ( $type === 'info' ) {
1417  $query['format'] = 'json';
1418  }
1419  if ( $after !== null ) {
1420  $query['marker'] = $after;
1421  }
1422  if ( $prefix !== null ) {
1423  $query['prefix'] = $prefix;
1424  }
1425  if ( $delim !== null ) {
1426  $query['delimiter'] = $delim;
1427  }
1428 
1429  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( array(
1430  'method' => 'GET',
1431  'url' => $this->storageUrl( $auth, $fullCont ),
1432  'query' => $query,
1433  'headers' => $this->authTokenHeaders( $auth )
1434  ) );
1435 
1436  $params = array( 'cont' => $fullCont, 'prefix' => $prefix, 'delim' => $delim );
1437  if ( $rcode === 200 ) { // good
1438  if ( $type === 'info' ) {
1439  $status->value = FormatJson::decode( trim( $rbody ) );
1440  } else {
1441  $status->value = explode( "\n", trim( $rbody ) );
1442  }
1443  } elseif ( $rcode === 204 ) {
1444  $status->value = array(); // empty container
1445  } elseif ( $rcode === 404 ) {
1446  $status->value = array(); // no container
1447  } else {
1448  $this->onError( $status, __METHOD__, $params, $rerr, $rcode, $rdesc );
1449  }
1450 
1451  return $status;
1452  }
1453 
1454  protected function doPrimeContainerCache( array $containerInfo ) {
1455  foreach ( $containerInfo as $container => $info ) {
1456  $this->containerStatCache->set( $container, 'stat', $info );
1457  }
1458  }
1459 
1460  protected function doGetFileStatMulti( array $params ) {
1461  $stats = array();
1462 
1463  $auth = $this->getAuthentication();
1464 
1465  $reqs = array();
1466  foreach ( $params['srcs'] as $path ) {
1467  list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $path );
1468  if ( $srcRel === null ) {
1469  $stats[$path] = false;
1470  continue; // invalid storage path
1471  } elseif ( !$auth ) {
1472  $stats[$path] = null;
1473  continue;
1474  }
1475 
1476  // (a) Check the container
1477  $cstat = $this->getContainerStat( $srcCont );
1478  if ( $cstat === false ) {
1479  $stats[$path] = false;
1480  continue; // ok, nothing to do
1481  } elseif ( !is_array( $cstat ) ) {
1482  $stats[$path] = null;
1483  continue;
1484  }
1485 
1486  $reqs[$path] = array(
1487  'method' => 'HEAD',
1488  'url' => $this->storageUrl( $auth, $srcCont, $srcRel ),
1489  'headers' => $this->authTokenHeaders( $auth ) + $this->headersFromParams( $params )
1490  );
1491  }
1492 
1493  $opts = array( 'maxConnsPerHost' => $params['concurrency'] );
1494  $reqs = $this->http->runMulti( $reqs, $opts );
1495 
1496  foreach ( $params['srcs'] as $path ) {
1497  if ( array_key_exists( $path, $stats ) ) {
1498  continue; // some sort of failure above
1499  }
1500  // (b) Check the file
1501  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $reqs[$path]['response'];
1502  if ( $rcode === 200 || $rcode === 204 ) {
1503  // Update the object if it is missing some headers
1504  $rhdrs = $this->addMissingMetadata( $rhdrs, $path );
1505  // Fetch all of the custom metadata headers
1506  $metadata = array();
1507  foreach ( $rhdrs as $name => $value ) {
1508  if ( strpos( $name, 'x-object-meta-' ) === 0 ) {
1509  $metadata[substr( $name, strlen( 'x-object-meta-' ) )] = $value;
1510  }
1511  }
1512  // Fetch all of the custom raw HTTP headers
1513  $headers = $this->sanitizeHdrs( array( 'headers' => $rhdrs ) );
1514  $stat = array(
1515  // Convert various random Swift dates to TS_MW
1516  'mtime' => $this->convertSwiftDate( $rhdrs['last-modified'], TS_MW ),
1517  // Empty objects actually return no content-length header in Ceph
1518  'size' => isset( $rhdrs['content-length'] ) ? (int)$rhdrs['content-length'] : 0,
1519  'sha1' => $rhdrs[ 'x-object-meta-sha1base36'],
1520  // Note: manifiest ETags are not an MD5 of the file
1521  'md5' => ctype_xdigit( $rhdrs['etag'] ) ? $rhdrs['etag'] : null,
1522  'xattr' => array( 'metadata' => $metadata, 'headers' => $headers )
1523  );
1524  if ( $this->isRGW ) {
1525  $stat['latest'] = true; // strong consistency
1526  }
1527  } elseif ( $rcode === 404 ) {
1528  $stat = false;
1529  } else {
1530  $stat = null;
1531  $this->onError( null, __METHOD__, $params, $rerr, $rcode, $rdesc );
1532  }
1533  $stats[$path] = $stat;
1534  }
1535 
1536  return $stats;
1537  }
1538 
1542  protected function getAuthentication() {
1543  if ( $this->authErrorTimestamp !== null ) {
1544  if ( ( time() - $this->authErrorTimestamp ) < 60 ) {
1545  return null; // failed last attempt; don't bother
1546  } else { // actually retry this time
1547  $this->authErrorTimestamp = null;
1548  }
1549  }
1550  // Session keys expire after a while, so we renew them periodically
1551  $reAuth = ( ( time() - $this->authSessionTimestamp ) > $this->authTTL );
1552  // Authenticate with proxy and get a session key...
1553  if ( !$this->authCreds || $reAuth ) {
1554  $this->authSessionTimestamp = 0;
1555  $cacheKey = $this->getCredsCacheKey( $this->swiftUser );
1556  $creds = $this->srvCache->get( $cacheKey ); // credentials
1557  // Try to use the credential cache
1558  if ( isset( $creds['auth_token'] ) && isset( $creds['storage_url'] ) ) {
1559  $this->authCreds = $creds;
1560  // Skew the timestamp for worst case to avoid using stale credentials
1561  $this->authSessionTimestamp = time() - ceil( $this->authTTL / 2 );
1562  } else { // cache miss
1563  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( array(
1564  'method' => 'GET',
1565  'url' => "{$this->swiftAuthUrl}/v1.0",
1566  'headers' => array(
1567  'x-auth-user' => $this->swiftUser,
1568  'x-auth-key' => $this->swiftKey
1569  )
1570  ) );
1571 
1572  if ( $rcode >= 200 && $rcode <= 299 ) { // OK
1573  $this->authCreds = array(
1574  'auth_token' => $rhdrs['x-auth-token'],
1575  'storage_url' => $rhdrs['x-storage-url']
1576  );
1577  $this->srvCache->set( $cacheKey, $this->authCreds, ceil( $this->authTTL / 2 ) );
1578  $this->authSessionTimestamp = time();
1579  } elseif ( $rcode === 401 ) {
1580  $this->onError( null, __METHOD__, array(), "Authentication failed.", $rcode );
1581  $this->authErrorTimestamp = time();
1582 
1583  return null;
1584  } else {
1585  $this->onError( null, __METHOD__, array(), "HTTP return code: $rcode", $rcode );
1586  $this->authErrorTimestamp = time();
1587 
1588  return null;
1589  }
1590  }
1591  // Ceph RGW does not use <account> in URLs (OpenStack Swift uses "/v1/<account>")
1592  if ( substr( $this->authCreds['storage_url'], -3 ) === '/v1' ) {
1593  $this->isRGW = true; // take advantage of strong consistency
1594  }
1595  }
1596 
1597  return $this->authCreds;
1598  }
1599 
1606  protected function storageUrl( array $creds, $container = null, $object = null ) {
1607  $parts = array( $creds['storage_url'] );
1608  if ( strlen( $container ) ) {
1609  $parts[] = rawurlencode( $container );
1610  }
1611  if ( strlen( $object ) ) {
1612  $parts[] = str_replace( "%2F", "/", rawurlencode( $object ) );
1613  }
1614 
1615  return implode( '/', $parts );
1616  }
1617 
1622  protected function authTokenHeaders( array $creds ) {
1623  return array( 'x-auth-token' => $creds['auth_token'] );
1624  }
1625 
1632  private function getCredsCacheKey( $username ) {
1633  return 'swiftcredentials:' . md5( $username . ':' . $this->swiftAuthUrl );
1634  }
1635 
1647  public function onError( $status, $func, array $params, $err = '', $code = 0, $desc = '' ) {
1648  if ( $status instanceof Status ) {
1649  $status->fatal( 'backend-fail-internal', $this->name );
1650  }
1651  if ( $code == 401 ) { // possibly a stale token
1652  $this->srvCache->delete( $this->getCredsCacheKey( $this->swiftUser ) );
1653  }
1654  wfDebugLog( 'SwiftBackend',
1655  "HTTP $code ($desc) in '{$func}' (given '" . FormatJson::encode( $params ) . "')" .
1656  ( $err ? ": $err" : "" )
1657  );
1658  }
1660 
1666  public $httpOp;
1668  public $callback;
1669 
1675  public function __construct( SwiftFileBackend $backend, Closure $callback, array $httpOp ) {
1676  $this->backend = $backend;
1677  $this->callback = $callback;
1678  $this->httpOp = $httpOp;
1679  }
1681 
1689 abstract class SwiftFileBackendList implements Iterator {
1691  protected $bufferIter = array();
1692 
1694  protected $bufferAfter = null;
1695 
1697  protected $pos = 0;
1700  protected $params = array();
1701 
1703  protected $backend;
1704 
1706  protected $container;
1707 
1709  protected $dir;
1710 
1712  protected $suffixStart;
1713 
1714  const PAGE_SIZE = 9000; // file listing buffer size
1715 
1722  public function __construct( SwiftFileBackend $backend, $fullCont, $dir, array $params ) {
1723  $this->backend = $backend;
1724  $this->container = $fullCont;
1725  $this->dir = $dir;
1726  if ( substr( $this->dir, -1 ) === '/' ) {
1727  $this->dir = substr( $this->dir, 0, -1 ); // remove trailing slash
1728  }
1729  if ( $this->dir == '' ) { // whole container
1730  $this->suffixStart = 0;
1731  } else { // dir within container
1732  $this->suffixStart = strlen( $this->dir ) + 1; // size of "path/to/dir/"
1733  }
1734  $this->params = $params;
1735  }
1736 
1741  public function key() {
1742  return $this->pos;
1743  }
1744 
1748  public function next() {
1749  // Advance to the next file in the page
1750  next( $this->bufferIter );
1751  ++$this->pos;
1752  // Check if there are no files left in this page and
1753  // advance to the next page if this page was not empty.
1754  if ( !$this->valid() && count( $this->bufferIter ) ) {
1755  $this->bufferIter = $this->pageFromList(
1756  $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE, $this->params
1757  ); // updates $this->bufferAfter
1758  }
1759  }
1760 
1764  public function rewind() {
1765  $this->pos = 0;
1766  $this->bufferAfter = null;
1767  $this->bufferIter = $this->pageFromList(
1768  $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE, $this->params
1769  ); // updates $this->bufferAfter
1770  }
1771 
1776  public function valid() {
1777  if ( $this->bufferIter === null ) {
1778  return false; // some failure?
1779  } else {
1780  return ( current( $this->bufferIter ) !== false ); // no paths can have this value
1781  }
1782  }
1783 
1794  abstract protected function pageFromList( $container, $dir, &$after, $limit, array $params );
1795 }
1796 
1805  public function current() {
1806  return substr( current( $this->bufferIter ), $this->suffixStart, -1 );
1807  }
1808 
1809  protected function pageFromList( $container, $dir, &$after, $limit, array $params ) {
1810  return $this->backend->getDirListPageInternal( $container, $dir, $after, $limit, $params );
1811  }
1812 }
1813 
1822  public function current() {
1823  list( $path, $stat ) = current( $this->bufferIter );
1824  $relPath = substr( $path, $this->suffixStart );
1825  if ( is_array( $stat ) ) {
1826  $storageDir = rtrim( $this->params['dir'], '/' );
1827  $this->backend->loadListingStatInternal( "$storageDir/$relPath", $stat );
1828  }
1829 
1830  return $relPath;
1831  }
1832 
1833  protected function pageFromList( $container, $dir, &$after, $limit, array $params ) {
1834  return $this->backend->getFileListPageInternal( $container, $dir, $after, $limit, $params );
1835  }
1836 }
SwiftFileBackend\$containerStatCache
ProcessCacheLRU $containerStatCache
Container stat cache *.
Definition: SwiftFileBackend.php:54
SwiftFileBackendList\rewind
rewind()
Definition: SwiftFileBackend.php:1740
SwiftFileOpHandle
Definition: SwiftFileBackend.php:1650
MWTimestamp
Library for creating and parsing MW-style timestamps.
Definition: MWTimestamp.php:31
SwiftFileBackend\doPrepareInternal
doPrepareInternal( $fullCont, $dir, array $params)
Definition: SwiftFileBackend.php:516
SwiftFileBackend\$isRGW
bool $isRGW
Whether the server is an Ceph RGW *.
Definition: SwiftFileBackend.php:62
MultiHttpClient
Class to handle concurrent HTTP requests.
Definition: MultiHttpClient.php:42
SwiftFileBackend\isPathUsableInternal
isPathUsableInternal( $storagePath)
Check if a file can be created or changed at a given storage path.
Definition: SwiftFileBackend.php:148
SwiftFileBackend\$authErrorTimestamp
int $authErrorTimestamp
UNIX timestamp *.
Definition: SwiftFileBackend.php:60
php
skin txt MediaWiki includes four core it has been set as the default in MediaWiki since the replacing Monobook it had been been the default skin since before being replaced by Vector largely rewritten in while keeping its appearance Several legacy skins were removed in the as the burden of supporting them became too heavy to bear Those in etc for skin dependent CSS etc for skin dependent JavaScript These can also be customised on a per user by etc This feature has led to a wide variety of user styles becoming that gallery is a good place to ending in php
Definition: skin.txt:62
SwiftFileBackend\getAuthentication
getAuthentication()
Definition: SwiftFileBackend.php:1528
$files
$files
Definition: importImages.php:67
SwiftFileBackend\getContainerStat
getContainerStat( $container, $bypassCache=false)
Get a Swift container stat array, possibly from process cache.
Definition: SwiftFileBackend.php:1250
SwiftFileBackendDirList\pageFromList
pageFromList( $container, $dir, &$after, $limit, array $params)
Get the given list portion (page)
Definition: SwiftFileBackend.php:1785
EmptyBagOStuff
A BagOStuff object with no objects in it.
Definition: EmptyBagOStuff.php:29
SwiftFileBackend\$swiftKey
string $swiftKey
Secret key for user *.
Definition: SwiftFileBackend.php:44
FileBackend\ATTR_HEADERS
const ATTR_HEADERS
Flags for supported features.
Definition: FileBackend.php:101
LockManager\LOCK_UW
const LOCK_UW
Definition: LockManager.php:59
$timestamp
if( $limit) $timestamp
Definition: importImages.php:104
SwiftFileBackend\doGetFileXAttributes
doGetFileXAttributes(array $params)
Definition: SwiftFileBackend.php:945
FileBackendError
File backend exception for checked exceptions (e.g.
Definition: FileBackend.php:1492
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all')
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1040
wfProfileIn
wfProfileIn( $functionname)
Begin profiling of a function.
Definition: Profiler.php:33
SwiftFileBackend\buildFileObjectListing
buildFileObjectListing(array $params, $dir, array $objects)
Build a list of file objects, filtering out any directories and extracting any stat info if provided ...
Definition: SwiftFileBackend.php:910
wfSuppressWarnings
wfSuppressWarnings( $end=false)
Reference-counted warning suppression.
Definition: GlobalFunctions.php:2387
SwiftFileBackend\$swiftUser
string $swiftUser
Swift user (account:user) to authenticate as *.
Definition: SwiftFileBackend.php:42
SwiftFileBackend\loadListingStatInternal
loadListingStatInternal( $path, array $val)
Do not call this function outside of SwiftFileBackendFileList.
Definition: SwiftFileBackend.php:941
Status\newGood
static newGood( $value=null)
Factory function for good results.
Definition: Status.php:77
SwiftFileBackend\resolveContainerPath
resolveContainerPath( $container, $relStoragePath)
Resolve a relative storage path, checking if it's allowed by the backend.
Definition: SwiftFileBackend.php:138
$params
$params
Definition: styleTest.css.php:40
$limit
if( $sleep) $limit
Definition: importImages.php:99
BagOStuff
interface is intended to be more or less compatible with the PHP memcached client.
Definition: BagOStuff.php:43
$s
$s
Definition: mergeMessageFileList.php:156
SwiftFileBackend\doGetFileContentsMulti
doGetFileContentsMulti(array $params)
Definition: SwiftFileBackend.php:684
SwiftFileBackend\createContainer
createContainer( $container, array $params)
Create a Swift container.
Definition: SwiftFileBackend.php:1303
SwiftFileBackend\getFileListPageInternal
getFileListPageInternal( $fullCont, $dir, &$after, $limit, array $params)
Do not call this function outside of SwiftFileBackendFileList.
Definition: SwiftFileBackend.php:856
SwiftFileBackendList\PAGE_SIZE
const PAGE_SIZE
Definition: SwiftFileBackend.php:1690
SwiftFileBackend\doGetFileSha1base36
doGetFileSha1base36(array $params)
Definition: SwiftFileBackend.php:960
FileBackendStore\clearCache
clearCache(array $paths=null)
Invalidate any in-process file stat and property cache.
Definition: FileBackendStore.php:1254
SwiftFileBackend\getFeatures
getFeatures()
Get the a bitfield of extra features supported by the backend medium.
Definition: SwiftFileBackend.php:134
SwiftFileBackend\doDeleteInternal
doDeleteInternal(array $params)
Definition: SwiftFileBackend.php:418
SwiftFileBackend\headersFromParams
headersFromParams(array $params)
Get headers to send to Swift when reading a file based on a FileBackend params array,...
Definition: SwiftFileBackend.php:1136
SwiftFileBackend\deleteContainer
deleteContainer( $container, array $params)
Delete a Swift container.
Definition: SwiftFileBackend.php:1348
SwiftFileBackendList\$suffixStart
int $suffixStart
Definition: SwiftFileBackend.php:1688
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:40
wfAppendQuery
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
Definition: GlobalFunctions.php:459
FileBackend\getScopedFileLocks
getScopedFileLocks(array $paths, $type, Status $status)
Lock the files at the given storage paths in the backend.
Definition: FileBackend.php:1262
SwiftFileBackend\getCredsCacheKey
getCredsCacheKey( $username)
Get the cache key for a container.
Definition: SwiftFileBackend.php:1618
FormatJson\decode
static decode( $value, $assoc=false)
Decodes a JSON string.
Definition: FormatJson.php:126
FormatJson\encode
static encode( $value, $pretty=false, $escaping=0)
Returns the JSON representation of a value.
Definition: FormatJson.php:104
ProfileSection
Class for handling function-scope profiling.
Definition: Profiler.php:60
SwiftFileBackend\doDirectoryExists
doDirectoryExists( $fullCont, $dir, array $params)
Definition: SwiftFileBackend.php:733
wfGetMainCache
wfGetMainCache()
Get the main cache object.
Definition: GlobalFunctions.php:3957
MWException
MediaWiki exception.
Definition: MWException.php:26
SwiftFileBackendList\$params
array $params
Definition: SwiftFileBackend.php:1680
SwiftFileBackend
Class for an OpenStack Swift (or Ceph RGW) based file backend.
Definition: SwiftFileBackend.php:35
SwiftFileBackendList
SwiftFileBackend helper class to page through listings.
Definition: SwiftFileBackend.php:1673
SwiftFileBackend\addMissingMetadata
addMissingMetadata(array $objHdrs, $path)
Fill in any missing object metadata and save it to Swift.
Definition: SwiftFileBackend.php:643
wfRestoreWarnings
wfRestoreWarnings()
Restore error level to previous value.
Definition: GlobalFunctions.php:2417
SwiftFileBackendList\valid
valid()
Definition: SwiftFileBackend.php:1752
SwiftFileBackend\getDirectoryListInternal
getDirectoryListInternal( $fullCont, $dir, array $params)
Definition: SwiftFileBackend.php:750
FileBackendStoreOpHandle\$backend
FileBackendStore $backend
Definition: FileBackendStore.php:1814
SwiftFileBackend\getFileHttpUrl
getFileHttpUrl(array $params)
Definition: SwiftFileBackend.php:1070
SwiftFileBackend\doMoveInternal
doMoveInternal(array $params)
Definition: SwiftFileBackend.php:358
wfProfileOut
wfProfileOut( $functionname='missing')
Stop profiling of a function.
Definition: Profiler.php:46
SwiftFileBackendList\$dir
string $dir
Storage directory *.
Definition: SwiftFileBackend.php:1686
SwiftFileBackend\$authCreds
array $authCreds
Definition: SwiftFileBackend.php:56
array
the array() calling protocol came about after MediaWiki 1.4rc1.
List of Api Query prop modules.
SwiftFileBackendList\pageFromList
pageFromList( $container, $dir, &$after, $limit, array $params)
Get the given list portion (page)
$dirs
$dirs
Definition: mergeMessageFileList.php:163
SwiftFileBackend\doGetLocalCopyMulti
doGetLocalCopyMulti(array $params)
Definition: SwiftFileBackend.php:1011
SwiftFileBackend\onError
onError( $status, $func, array $params, $err='', $code=0, $desc='')
Log an unexpected exception for this backend.
Definition: SwiftFileBackend.php:1633
SwiftFileBackendDirList\current
current()
Definition: SwiftFileBackend.php:1781
SwiftFileBackend\doDescribeInternal
doDescribeInternal(array $params)
Definition: SwiftFileBackend.php:459
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:188
SwiftFileBackend\sanitizeHdrs
sanitizeHdrs(array $params)
Sanitize and filter the custom headers from a $params array.
Definition: SwiftFileBackend.php:164
SwiftFileBackend\storageUrl
storageUrl(array $creds, $container=null, $object=null)
Definition: SwiftFileBackend.php:1592
FileBackendStore\getContentType
getContentType( $storagePath, $content, $fsPath)
Get the content type to use in HEAD/GET requests for a file.
Definition: FileBackendStore.php:1799
SwiftFileBackendFileList\current
current()
Definition: SwiftFileBackend.php:1798
$section
$section
Definition: Utf8Test.php:88
SwiftFileBackendDirList
Iterator for listing directories.
Definition: SwiftFileBackend.php:1776
TS_MW
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
Definition: GlobalFunctions.php:2431
SwiftFileBackendList\$container
string $container
Container name *.
Definition: SwiftFileBackend.php:1684
SwiftFileBackend\doSecureInternal
doSecureInternal( $fullCont, $dir, array $params)
Definition: SwiftFileBackend.php:538
FileBackend\ATTR_METADATA
const ATTR_METADATA
Definition: FileBackend.php:102
TempFSFile\factory
static factory( $prefix, $extension='')
Make a new temporary file on the file system.
Definition: TempFSFile.php:44
$value
$value
Definition: styleTest.css.php:45
FileBackendStore\getFileStat
getFileStat(array $params)
Get quick information about a file at a storage path in the backend.
Definition: FileBackendStore.php:620
SwiftFileBackend\__construct
__construct(array $config)
Definition: SwiftFileBackend.php:92
SwiftFileBackend\directoriesAreVirtual
directoriesAreVirtual()
Is this a key/value store where directories are just virtual? Virtual directories exists in so much a...
Definition: SwiftFileBackend.php:1124
FileBackendStore\resolveStoragePathReal
resolveStoragePathReal( $storagePath)
Like resolveStoragePath() except null values are returned if the container is sharded and the shard c...
Definition: FileBackendStore.php:1418
SwiftFileBackend\authTokenHeaders
authTokenHeaders(array $creds)
Definition: SwiftFileBackend.php:1608
SwiftFileBackendList\$bufferIter
array $bufferIter
List of path or (path,stat array) entries *.
Definition: SwiftFileBackend.php:1674
SwiftFileBackendList\$backend
SwiftFileBackend $backend
Definition: SwiftFileBackend.php:1682
SwiftFileBackend\doCreateInternal
doCreateInternal(array $params)
Definition: SwiftFileBackend.php:198
SwiftFileBackend\$authSessionTimestamp
int $authSessionTimestamp
UNIX timestamp *.
Definition: SwiftFileBackend.php:58
FileBackendStore
Base class for all backends using particular storage medium.
Definition: FileBackendStore.php:38
SwiftFileBackend\doExecuteOpHandlesInternal
doExecuteOpHandlesInternal(array $fileOpHandles)
Definition: SwiftFileBackend.php:1145
SwiftFileBackend\$swiftAuthUrl
string $swiftAuthUrl
Authentication base URL (without version) *.
Definition: SwiftFileBackend.php:40
FileBackendStoreOpHandle
FileBackendStore helper class for performing asynchronous file operations.
Definition: FileBackendStore.php:1812
ObjectCache\newAccelerator
static newAccelerator( $params)
Factory function referenced from DefaultSettings.php for CACHE_ACCEL.
Definition: ObjectCache.php:125
SwiftFileBackend\doStreamFile
doStreamFile(array $params)
Definition: SwiftFileBackend.php:975
SwiftFileBackendList\$pos
int $pos
Definition: SwiftFileBackend.php:1678
$hash
return false to override stock group addition can be modified try getUserPermissionsErrors userCan checks are continued by internal code can override on output return false to not delete it return false to override the default password checks & $hash
Definition: hooks.txt:2697
SwiftFileBackendList\next
next()
Definition: SwiftFileBackend.php:1724
SwiftFileBackendList\$bufferAfter
string $bufferAfter
List items after this path *.
Definition: SwiftFileBackend.php:1676
SwiftFileBackend\$rgwS3SecretKey
string $rgwS3SecretKey
S3 authentication key (RADOS Gateway) *.
Definition: SwiftFileBackend.php:50
FileBackendStore\primeContainerCache
primeContainerCache(array $items)
Do a batch lookup from cache for container stats for all containers used in a list of container names...
Definition: FileBackendStore.php:1600
SwiftFileBackend\$authTTL
int $authTTL
TTL in seconds *.
Definition: SwiftFileBackend.php:38
$dir
if(count( $args)==0) $dir
Definition: importImages.php:49
$ext
$ext
Definition: NoLocalSettings.php:34
FileBackend\extensionFromPath
static extensionFromPath( $path)
Get the final extension from a storage or FS path.
Definition: FileBackend.php:1400
SwiftFileBackend\doCopyInternal
doCopyInternal(array $params)
Definition: SwiftFileBackend.php:309
FileBackend\$name
string $name
Unique backend name *.
Definition: FileBackend.php:86
wfBaseConvert
wfBaseConvert( $input, $sourceBase, $destBase, $pad=1, $lowercase=true, $engine='auto')
Convert an arbitrarily-long digit string from one numeric base to another, optionally zero-padding to...
Definition: GlobalFunctions.php:3368
SwiftFileBackend\$rgwS3AccessKey
string $rgwS3AccessKey
S3 access key (RADOS Gateway) *.
Definition: SwiftFileBackend.php:48
FileBackend\getLocalCopy
getLocalCopy(array $params)
Get a local copy on disk of the file at a storage path in the backend.
Definition: FileBackend.php:1054
SwiftFileBackend\objectListing
objectListing( $fullCont, $type, $limit, $after=null, $prefix=null, $delim=null)
Get a list of objects under a container.
Definition: SwiftFileBackend.php:1389
SwiftFileOpHandle\$httpOp
array $httpOp
List of Requests for MultiHttpClient *.
Definition: SwiftFileBackend.php:1651
$path
$path
Definition: NoLocalSettings.php:35
SwiftFileBackend\convertSwiftDate
convertSwiftDate( $ts, $format=TS_MW)
Convert dates like "Tue, 03 Jan 2012 22:01:04 GMT"/"2013-05-11T07:37:27.678360Z".
Definition: SwiftFileBackend.php:626
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
SwiftFileBackend\doPublishInternal
doPublishInternal( $fullCont, $dir, array $params)
Definition: SwiftFileBackend.php:561
SwiftFileOpHandle\$callback
Closure $callback
Definition: SwiftFileBackend.php:1652
SwiftFileBackendFileList\pageFromList
pageFromList( $container, $dir, &$after, $limit, array $params)
Get the given list portion (page)
Definition: SwiftFileBackend.php:1809
SwiftFileBackend\doGetFileStat
doGetFileStat(array $params)
Definition: SwiftFileBackend.php:608
SwiftFileBackend\$swiftTempUrlKey
string $swiftTempUrlKey
Shared secret value for making temp URLs *.
Definition: SwiftFileBackend.php:46
SwiftFileBackend\doStoreInternal
doStoreInternal(array $params)
Definition: SwiftFileBackend.php:246
SwiftFileBackend\doGetFileStatMulti
doGetFileStatMulti(array $params)
Get file stat information (concurrently if possible) for several files.
Definition: SwiftFileBackend.php:1446
name
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at name
Definition: design.txt:12
SwiftFileBackend\setContainerAccess
setContainerAccess( $container, array $readGrps, array $writeGrps)
Set read/write permissions for a Swift container.
Definition: SwiftFileBackend.php:1216
SwiftFileBackend\doCleanInternal
doCleanInternal( $fullCont, $dir, array $params)
Definition: SwiftFileBackend.php:581
ProcessCacheLRU
Handles per process caching of items.
Definition: ProcessCacheLRU.php:28
SwiftFileBackend\$srvCache
BagOStuff $srvCache
Definition: SwiftFileBackend.php:52
SwiftFileOpHandle\__construct
__construct(SwiftFileBackend $backend, Closure $callback, array $httpOp)
Definition: SwiftFileBackend.php:1659
$e
if( $useReadline) $e
Definition: eval.php:66
$query
return true to allow those checks to and false if checking is done use this to change the tables headers temp or archived zone change it to an object instance and return false override the list derivative used the name of the old file when set the default code will be skipped add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1105
SwiftFileBackendFileList
Iterator for listing regular files.
Definition: SwiftFileBackend.php:1793
SwiftFileBackend\getFileListInternal
getFileListInternal( $fullCont, $dir, array $params)
Definition: SwiftFileBackend.php:761
SwiftFileBackend\doPrimeContainerCache
doPrimeContainerCache(array $containerInfo)
Fill the backend-specific process cache given an array of resolved container names and their correspo...
Definition: SwiftFileBackend.php:1440
FileBackendStore\setContainerCache
setContainerCache( $container, array $val)
Set the cached info for a container.
Definition: FileBackendStore.php:1577
SwiftFileBackend\getDirListPageInternal
getDirListPageInternal( $fullCont, $dir, &$after, $limit, array $params)
Do not call this function outside of SwiftFileBackendFileList.
Definition: SwiftFileBackend.php:776
Status\newFatal
static newFatal( $message)
Factory function for fatal errors.
Definition: Status.php:63
SwiftFileBackendList\__construct
__construct(SwiftFileBackend $backend, $fullCont, $dir, array $params)
Definition: SwiftFileBackend.php:1698
SwiftFileBackend\$http
MultiHttpClient $http
Definition: SwiftFileBackend.php:36
$type
$type
Definition: testCompression.php:46
SwiftFileBackendList\key
key()
Definition: SwiftFileBackend.php:1717