MediaWiki  1.29.1
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 
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  : 15 * 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( [] );
130  // Cache container information to mask latency
131  if ( isset( $config['wanCache'] ) && $config['wanCache'] instanceof WANObjectCache ) {
132  $this->memCache = $config['wanCache'];
133  }
134  // Process cache for container info
135  $this->containerStatCache = new ProcessCacheLRU( 300 );
136  // Cache auth token information to avoid RTTs
137  if ( !empty( $config['cacheAuthInfo'] ) && isset( $config['srvCache'] ) ) {
138  $this->srvCache = $config['srvCache'];
139  } else {
140  $this->srvCache = new EmptyBagOStuff();
141  }
142  }
143 
144  public function getFeatures() {
147  }
148 
149  protected function resolveContainerPath( $container, $relStoragePath ) {
150  if ( !mb_check_encoding( $relStoragePath, 'UTF-8' ) ) {
151  return null; // not UTF-8, makes it hard to use CF and the swift HTTP API
152  } elseif ( strlen( urlencode( $relStoragePath ) ) > 1024 ) {
153  return null; // too long for Swift
154  }
155 
156  return $relStoragePath;
157  }
158 
159  public function isPathUsableInternal( $storagePath ) {
160  list( $container, $rel ) = $this->resolveStoragePathReal( $storagePath );
161  if ( $rel === null ) {
162  return false; // invalid
163  }
164 
165  return is_array( $this->getContainerStat( $container ) );
166  }
167 
175  protected function sanitizeHdrs( array $params ) {
176  return isset( $params['headers'] )
177  ? $this->getCustomHeaders( $params['headers'] )
178  : [];
179  }
180 
185  protected function getCustomHeaders( array $rawHeaders ) {
186  $headers = [];
187 
188  // Normalize casing, and strip out illegal headers
189  foreach ( $rawHeaders as $name => $value ) {
190  $name = strtolower( $name );
191  if ( preg_match( '/^content-(type|length)$/', $name ) ) {
192  continue; // blacklisted
193  } elseif ( preg_match( '/^(x-)?content-/', $name ) ) {
194  $headers[$name] = $value; // allowed
195  } elseif ( preg_match( '/^content-(disposition)/', $name ) ) {
196  $headers[$name] = $value; // allowed
197  }
198  }
199  // By default, Swift has annoyingly low maximum header value limits
200  if ( isset( $headers['content-disposition'] ) ) {
201  $disposition = '';
202  // @note: assume FileBackend::makeContentDisposition() already used
203  foreach ( explode( ';', $headers['content-disposition'] ) as $part ) {
204  $part = trim( $part );
205  $new = ( $disposition === '' ) ? $part : "{$disposition};{$part}";
206  if ( strlen( $new ) <= 255 ) {
207  $disposition = $new;
208  } else {
209  break; // too long; sigh
210  }
211  }
212  $headers['content-disposition'] = $disposition;
213  }
214 
215  return $headers;
216  }
217 
222  protected function getMetadataHeaders( array $rawHeaders ) {
223  $headers = [];
224  foreach ( $rawHeaders as $name => $value ) {
225  $name = strtolower( $name );
226  if ( strpos( $name, 'x-object-meta-' ) === 0 ) {
227  $headers[$name] = $value;
228  }
229  }
230 
231  return $headers;
232  }
233 
238  protected function getMetadata( array $rawHeaders ) {
239  $metadata = [];
240  foreach ( $this->getMetadataHeaders( $rawHeaders ) as $name => $value ) {
241  $metadata[substr( $name, strlen( 'x-object-meta-' ) )] = $value;
242  }
243 
244  return $metadata;
245  }
246 
247  protected function doCreateInternal( array $params ) {
248  $status = $this->newStatus();
249 
250  list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
251  if ( $dstRel === null ) {
252  $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
253 
254  return $status;
255  }
256 
257  $sha1Hash = Wikimedia\base_convert( sha1( $params['content'] ), 16, 36, 31 );
258  $contentType = isset( $params['headers']['content-type'] )
259  ? $params['headers']['content-type']
260  : $this->getContentType( $params['dst'], $params['content'], null );
261 
262  $reqs = [ [
263  'method' => 'PUT',
264  'url' => [ $dstCont, $dstRel ],
265  'headers' => [
266  'content-length' => strlen( $params['content'] ),
267  'etag' => md5( $params['content'] ),
268  'content-type' => $contentType,
269  'x-object-meta-sha1base36' => $sha1Hash
270  ] + $this->sanitizeHdrs( $params ),
271  'body' => $params['content']
272  ] ];
273 
274  $method = __METHOD__;
275  $handler = function ( array $request, StatusValue $status ) use ( $method, $params ) {
276  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
277  if ( $rcode === 201 ) {
278  // good
279  } elseif ( $rcode === 412 ) {
280  $status->fatal( 'backend-fail-contenttype', $params['dst'] );
281  } else {
282  $this->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
283  }
284  };
285 
286  $opHandle = new SwiftFileOpHandle( $this, $handler, $reqs );
287  if ( !empty( $params['async'] ) ) { // deferred
288  $status->value = $opHandle;
289  } else { // actually write the object in Swift
290  $status->merge( current( $this->executeOpHandlesInternal( [ $opHandle ] ) ) );
291  }
292 
293  return $status;
294  }
295 
296  protected function doStoreInternal( array $params ) {
297  $status = $this->newStatus();
298 
299  list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
300  if ( $dstRel === null ) {
301  $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
302 
303  return $status;
304  }
305 
306  MediaWiki\suppressWarnings();
307  $sha1Hash = sha1_file( $params['src'] );
308  MediaWiki\restoreWarnings();
309  if ( $sha1Hash === false ) { // source doesn't exist?
310  $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
311 
312  return $status;
313  }
314  $sha1Hash = Wikimedia\base_convert( $sha1Hash, 16, 36, 31 );
315  $contentType = isset( $params['headers']['content-type'] )
316  ? $params['headers']['content-type']
317  : $this->getContentType( $params['dst'], null, $params['src'] );
318 
319  $handle = fopen( $params['src'], 'rb' );
320  if ( $handle === false ) { // source doesn't exist?
321  $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
322 
323  return $status;
324  }
325 
326  $reqs = [ [
327  'method' => 'PUT',
328  'url' => [ $dstCont, $dstRel ],
329  'headers' => [
330  'content-length' => filesize( $params['src'] ),
331  'etag' => md5_file( $params['src'] ),
332  'content-type' => $contentType,
333  'x-object-meta-sha1base36' => $sha1Hash
334  ] + $this->sanitizeHdrs( $params ),
335  'body' => $handle // resource
336  ] ];
337 
338  $method = __METHOD__;
339  $handler = function ( array $request, StatusValue $status ) use ( $method, $params ) {
340  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
341  if ( $rcode === 201 ) {
342  // good
343  } elseif ( $rcode === 412 ) {
344  $status->fatal( 'backend-fail-contenttype', $params['dst'] );
345  } else {
346  $this->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
347  }
348  };
349 
350  $opHandle = new SwiftFileOpHandle( $this, $handler, $reqs );
351  $opHandle->resourcesToClose[] = $handle;
352 
353  if ( !empty( $params['async'] ) ) { // deferred
354  $status->value = $opHandle;
355  } else { // actually write the object in Swift
356  $status->merge( current( $this->executeOpHandlesInternal( [ $opHandle ] ) ) );
357  }
358 
359  return $status;
360  }
361 
362  protected function doCopyInternal( array $params ) {
363  $status = $this->newStatus();
364 
365  list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
366  if ( $srcRel === null ) {
367  $status->fatal( 'backend-fail-invalidpath', $params['src'] );
368 
369  return $status;
370  }
371 
372  list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
373  if ( $dstRel === null ) {
374  $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
375 
376  return $status;
377  }
378 
379  $reqs = [ [
380  'method' => 'PUT',
381  'url' => [ $dstCont, $dstRel ],
382  'headers' => [
383  'x-copy-from' => '/' . rawurlencode( $srcCont ) .
384  '/' . str_replace( "%2F", "/", rawurlencode( $srcRel ) )
385  ] + $this->sanitizeHdrs( $params ), // extra headers merged into object
386  ] ];
387 
388  $method = __METHOD__;
389  $handler = function ( array $request, StatusValue $status ) use ( $method, $params ) {
390  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
391  if ( $rcode === 201 ) {
392  // good
393  } elseif ( $rcode === 404 ) {
394  $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
395  } else {
396  $this->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
397  }
398  };
399 
400  $opHandle = new SwiftFileOpHandle( $this, $handler, $reqs );
401  if ( !empty( $params['async'] ) ) { // deferred
402  $status->value = $opHandle;
403  } else { // actually write the object in Swift
404  $status->merge( current( $this->executeOpHandlesInternal( [ $opHandle ] ) ) );
405  }
406 
407  return $status;
408  }
409 
410  protected function doMoveInternal( array $params ) {
411  $status = $this->newStatus();
412 
413  list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
414  if ( $srcRel === null ) {
415  $status->fatal( 'backend-fail-invalidpath', $params['src'] );
416 
417  return $status;
418  }
419 
420  list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
421  if ( $dstRel === null ) {
422  $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
423 
424  return $status;
425  }
426 
427  $reqs = [
428  [
429  'method' => 'PUT',
430  'url' => [ $dstCont, $dstRel ],
431  'headers' => [
432  'x-copy-from' => '/' . rawurlencode( $srcCont ) .
433  '/' . str_replace( "%2F", "/", rawurlencode( $srcRel ) )
434  ] + $this->sanitizeHdrs( $params ) // extra headers merged into object
435  ]
436  ];
437  if ( "{$srcCont}/{$srcRel}" !== "{$dstCont}/{$dstRel}" ) {
438  $reqs[] = [
439  'method' => 'DELETE',
440  'url' => [ $srcCont, $srcRel ],
441  'headers' => []
442  ];
443  }
444 
445  $method = __METHOD__;
446  $handler = function ( array $request, StatusValue $status ) use ( $method, $params ) {
447  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
448  if ( $request['method'] === 'PUT' && $rcode === 201 ) {
449  // good
450  } elseif ( $request['method'] === 'DELETE' && $rcode === 204 ) {
451  // good
452  } elseif ( $rcode === 404 ) {
453  $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
454  } else {
455  $this->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
456  }
457  };
458 
459  $opHandle = new SwiftFileOpHandle( $this, $handler, $reqs );
460  if ( !empty( $params['async'] ) ) { // deferred
461  $status->value = $opHandle;
462  } else { // actually move the object in Swift
463  $status->merge( current( $this->executeOpHandlesInternal( [ $opHandle ] ) ) );
464  }
465 
466  return $status;
467  }
468 
469  protected function doDeleteInternal( array $params ) {
470  $status = $this->newStatus();
471 
472  list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
473  if ( $srcRel === null ) {
474  $status->fatal( 'backend-fail-invalidpath', $params['src'] );
475 
476  return $status;
477  }
478 
479  $reqs = [ [
480  'method' => 'DELETE',
481  'url' => [ $srcCont, $srcRel ],
482  'headers' => []
483  ] ];
484 
485  $method = __METHOD__;
486  $handler = function ( array $request, StatusValue $status ) use ( $method, $params ) {
487  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
488  if ( $rcode === 204 ) {
489  // good
490  } elseif ( $rcode === 404 ) {
491  if ( empty( $params['ignoreMissingSource'] ) ) {
492  $status->fatal( 'backend-fail-delete', $params['src'] );
493  }
494  } else {
495  $this->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
496  }
497  };
498 
499  $opHandle = new SwiftFileOpHandle( $this, $handler, $reqs );
500  if ( !empty( $params['async'] ) ) { // deferred
501  $status->value = $opHandle;
502  } else { // actually delete the object in Swift
503  $status->merge( current( $this->executeOpHandlesInternal( [ $opHandle ] ) ) );
504  }
505 
506  return $status;
507  }
508 
509  protected function doDescribeInternal( array $params ) {
510  $status = $this->newStatus();
511 
512  list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
513  if ( $srcRel === null ) {
514  $status->fatal( 'backend-fail-invalidpath', $params['src'] );
515 
516  return $status;
517  }
518 
519  // Fetch the old object headers/metadata...this should be in stat cache by now
520  $stat = $this->getFileStat( [ 'src' => $params['src'], 'latest' => 1 ] );
521  if ( $stat && !isset( $stat['xattr'] ) ) { // older cache entry
522  $stat = $this->doGetFileStat( [ 'src' => $params['src'], 'latest' => 1 ] );
523  }
524  if ( !$stat ) {
525  $status->fatal( 'backend-fail-describe', $params['src'] );
526 
527  return $status;
528  }
529 
530  // POST clears prior headers, so we need to merge the changes in to the old ones
531  $metaHdrs = [];
532  foreach ( $stat['xattr']['metadata'] as $name => $value ) {
533  $metaHdrs["x-object-meta-$name"] = $value;
534  }
535  $customHdrs = $this->sanitizeHdrs( $params ) + $stat['xattr']['headers'];
536 
537  $reqs = [ [
538  'method' => 'POST',
539  'url' => [ $srcCont, $srcRel ],
540  'headers' => $metaHdrs + $customHdrs
541  ] ];
542 
543  $method = __METHOD__;
544  $handler = function ( array $request, StatusValue $status ) use ( $method, $params ) {
545  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
546  if ( $rcode === 202 ) {
547  // good
548  } elseif ( $rcode === 404 ) {
549  $status->fatal( 'backend-fail-describe', $params['src'] );
550  } else {
551  $this->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
552  }
553  };
554 
555  $opHandle = new SwiftFileOpHandle( $this, $handler, $reqs );
556  if ( !empty( $params['async'] ) ) { // deferred
557  $status->value = $opHandle;
558  } else { // actually change the object in Swift
559  $status->merge( current( $this->executeOpHandlesInternal( [ $opHandle ] ) ) );
560  }
561 
562  return $status;
563  }
564 
565  protected function doPrepareInternal( $fullCont, $dir, array $params ) {
566  $status = $this->newStatus();
567 
568  // (a) Check if container already exists
569  $stat = $this->getContainerStat( $fullCont );
570  if ( is_array( $stat ) ) {
571  return $status; // already there
572  } elseif ( $stat === null ) {
573  $status->fatal( 'backend-fail-internal', $this->name );
574  $this->logger->error( __METHOD__ . ': cannot get container stat' );
575 
576  return $status;
577  }
578 
579  // (b) Create container as needed with proper ACLs
580  if ( $stat === false ) {
581  $params['op'] = 'prepare';
582  $status->merge( $this->createContainer( $fullCont, $params ) );
583  }
584 
585  return $status;
586  }
587 
588  protected function doSecureInternal( $fullCont, $dir, array $params ) {
589  $status = $this->newStatus();
590  if ( empty( $params['noAccess'] ) ) {
591  return $status; // nothing to do
592  }
593 
594  $stat = $this->getContainerStat( $fullCont );
595  if ( is_array( $stat ) ) {
596  // Make container private to end-users...
597  $status->merge( $this->setContainerAccess(
598  $fullCont,
599  [ $this->swiftUser ], // read
600  [ $this->swiftUser ] // write
601  ) );
602  } elseif ( $stat === false ) {
603  $status->fatal( 'backend-fail-usable', $params['dir'] );
604  } else {
605  $status->fatal( 'backend-fail-internal', $this->name );
606  $this->logger->error( __METHOD__ . ': cannot get container stat' );
607  }
608 
609  return $status;
610  }
611 
612  protected function doPublishInternal( $fullCont, $dir, array $params ) {
613  $status = $this->newStatus();
614 
615  $stat = $this->getContainerStat( $fullCont );
616  if ( is_array( $stat ) ) {
617  // Make container public to end-users...
618  $status->merge( $this->setContainerAccess(
619  $fullCont,
620  [ $this->swiftUser, '.r:*' ], // read
621  [ $this->swiftUser ] // write
622  ) );
623  } elseif ( $stat === false ) {
624  $status->fatal( 'backend-fail-usable', $params['dir'] );
625  } else {
626  $status->fatal( 'backend-fail-internal', $this->name );
627  $this->logger->error( __METHOD__ . ': cannot get container stat' );
628  }
629 
630  return $status;
631  }
632 
633  protected function doCleanInternal( $fullCont, $dir, array $params ) {
634  $status = $this->newStatus();
635 
636  // Only containers themselves can be removed, all else is virtual
637  if ( $dir != '' ) {
638  return $status; // nothing to do
639  }
640 
641  // (a) Check the container
642  $stat = $this->getContainerStat( $fullCont, true );
643  if ( $stat === false ) {
644  return $status; // ok, nothing to do
645  } elseif ( !is_array( $stat ) ) {
646  $status->fatal( 'backend-fail-internal', $this->name );
647  $this->logger->error( __METHOD__ . ': cannot get container stat' );
648 
649  return $status;
650  }
651 
652  // (b) Delete the container if empty
653  if ( $stat['count'] == 0 ) {
654  $params['op'] = 'clean';
655  $status->merge( $this->deleteContainer( $fullCont, $params ) );
656  }
657 
658  return $status;
659  }
660 
661  protected function doGetFileStat( array $params ) {
662  $params = [ 'srcs' => [ $params['src'] ], 'concurrency' => 1 ] + $params;
663  unset( $params['src'] );
664  $stats = $this->doGetFileStatMulti( $params );
665 
666  return reset( $stats );
667  }
668 
679  protected function convertSwiftDate( $ts, $format = TS_MW ) {
680  try {
681  $timestamp = new MWTimestamp( $ts );
682 
683  return $timestamp->getTimestamp( $format );
684  } catch ( Exception $e ) {
685  throw new FileBackendError( $e->getMessage() );
686  }
687  }
688 
696  protected function addMissingMetadata( array $objHdrs, $path ) {
697  if ( isset( $objHdrs['x-object-meta-sha1base36'] ) ) {
698  return $objHdrs; // nothing to do
699  }
700 
702  $ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
703  $this->logger->error( __METHOD__ . ": $path was not stored with SHA-1 metadata." );
704 
705  $objHdrs['x-object-meta-sha1base36'] = false;
706 
707  $auth = $this->getAuthentication();
708  if ( !$auth ) {
709  return $objHdrs; // failed
710  }
711 
712  // Find prior custom HTTP headers
713  $postHeaders = $this->getCustomHeaders( $objHdrs );
714  // Find prior metadata headers
715  $postHeaders += $this->getMetadataHeaders( $objHdrs );
716 
717  $status = $this->newStatus();
719  $scopeLockS = $this->getScopedFileLocks( [ $path ], LockManager::LOCK_UW, $status );
720  if ( $status->isOK() ) {
721  $tmpFile = $this->getLocalCopy( [ 'src' => $path, 'latest' => 1 ] );
722  if ( $tmpFile ) {
723  $hash = $tmpFile->getSha1Base36();
724  if ( $hash !== false ) {
725  $objHdrs['x-object-meta-sha1base36'] = $hash;
726  // Merge new SHA1 header into the old ones
727  $postHeaders['x-object-meta-sha1base36'] = $hash;
728  list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $path );
729  list( $rcode ) = $this->http->run( [
730  'method' => 'POST',
731  'url' => $this->storageUrl( $auth, $srcCont, $srcRel ),
732  'headers' => $this->authTokenHeaders( $auth ) + $postHeaders
733  ] );
734  if ( $rcode >= 200 && $rcode <= 299 ) {
735  $this->deleteFileCache( $path );
736 
737  return $objHdrs; // success
738  }
739  }
740  }
741  }
742 
743  $this->logger->error( __METHOD__ . ": unable to set SHA-1 metadata for $path" );
744 
745  return $objHdrs; // failed
746  }
747 
748  protected function doGetFileContentsMulti( array $params ) {
749  $contents = [];
750 
751  $auth = $this->getAuthentication();
752 
753  $ep = array_diff_key( $params, [ 'srcs' => 1 ] ); // for error logging
754  // Blindly create tmp files and stream to them, catching any exception if the file does
755  // not exist. Doing stats here is useless and will loop infinitely in addMissingMetadata().
756  $reqs = []; // (path => op)
757 
758  foreach ( $params['srcs'] as $path ) { // each path in this concurrent batch
759  list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $path );
760  if ( $srcRel === null || !$auth ) {
761  $contents[$path] = false;
762  continue;
763  }
764  // Create a new temporary memory file...
765  $handle = fopen( 'php://temp', 'wb' );
766  if ( $handle ) {
767  $reqs[$path] = [
768  'method' => 'GET',
769  'url' => $this->storageUrl( $auth, $srcCont, $srcRel ),
770  'headers' => $this->authTokenHeaders( $auth )
771  + $this->headersFromParams( $params ),
772  'stream' => $handle,
773  ];
774  }
775  $contents[$path] = false;
776  }
777 
778  $opts = [ 'maxConnsPerHost' => $params['concurrency'] ];
779  $reqs = $this->http->runMulti( $reqs, $opts );
780  foreach ( $reqs as $path => $op ) {
781  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $op['response'];
782  if ( $rcode >= 200 && $rcode <= 299 ) {
783  rewind( $op['stream'] ); // start from the beginning
784  $contents[$path] = stream_get_contents( $op['stream'] );
785  } elseif ( $rcode === 404 ) {
786  $contents[$path] = false;
787  } else {
788  $this->onError( null, __METHOD__,
789  [ 'src' => $path ] + $ep, $rerr, $rcode, $rdesc );
790  }
791  fclose( $op['stream'] ); // close open handle
792  }
793 
794  return $contents;
795  }
796 
797  protected function doDirectoryExists( $fullCont, $dir, array $params ) {
798  $prefix = ( $dir == '' ) ? null : "{$dir}/";
799  $status = $this->objectListing( $fullCont, 'names', 1, null, $prefix );
800  if ( $status->isOK() ) {
801  return ( count( $status->value ) ) > 0;
802  }
803 
804  return null; // error
805  }
806 
814  public function getDirectoryListInternal( $fullCont, $dir, array $params ) {
815  return new SwiftFileBackendDirList( $this, $fullCont, $dir, $params );
816  }
817 
825  public function getFileListInternal( $fullCont, $dir, array $params ) {
826  return new SwiftFileBackendFileList( $this, $fullCont, $dir, $params );
827  }
828 
840  public function getDirListPageInternal( $fullCont, $dir, &$after, $limit, array $params ) {
841  $dirs = [];
842  if ( $after === INF ) {
843  return $dirs; // nothing more
844  }
845 
846  $ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
847 
848  $prefix = ( $dir == '' ) ? null : "{$dir}/";
849  // Non-recursive: only list dirs right under $dir
850  if ( !empty( $params['topOnly'] ) ) {
851  $status = $this->objectListing( $fullCont, 'names', $limit, $after, $prefix, '/' );
852  if ( !$status->isOK() ) {
853  throw new FileBackendError( "Iterator page I/O error." );
854  }
855  $objects = $status->value;
856  foreach ( $objects as $object ) { // files and directories
857  if ( substr( $object, -1 ) === '/' ) {
858  $dirs[] = $object; // directories end in '/'
859  }
860  }
861  } else {
862  // Recursive: list all dirs under $dir and its subdirs
863  $getParentDir = function ( $path ) {
864  return ( strpos( $path, '/' ) !== false ) ? dirname( $path ) : false;
865  };
866 
867  // Get directory from last item of prior page
868  $lastDir = $getParentDir( $after ); // must be first page
869  $status = $this->objectListing( $fullCont, 'names', $limit, $after, $prefix );
870 
871  if ( !$status->isOK() ) {
872  throw new FileBackendError( "Iterator page I/O error." );
873  }
874 
875  $objects = $status->value;
876 
877  foreach ( $objects as $object ) { // files
878  $objectDir = $getParentDir( $object ); // directory of object
879 
880  if ( $objectDir !== false && $objectDir !== $dir ) {
881  // Swift stores paths in UTF-8, using binary sorting.
882  // See function "create_container_table" in common/db.py.
883  // If a directory is not "greater" than the last one,
884  // then it was already listed by the calling iterator.
885  if ( strcmp( $objectDir, $lastDir ) > 0 ) {
886  $pDir = $objectDir;
887  do { // add dir and all its parent dirs
888  $dirs[] = "{$pDir}/";
889  $pDir = $getParentDir( $pDir );
890  } while ( $pDir !== false // sanity
891  && strcmp( $pDir, $lastDir ) > 0 // not done already
892  && strlen( $pDir ) > strlen( $dir ) // within $dir
893  );
894  }
895  $lastDir = $objectDir;
896  }
897  }
898  }
899  // Page on the unfiltered directory listing (what is returned may be filtered)
900  if ( count( $objects ) < $limit ) {
901  $after = INF; // avoid a second RTT
902  } else {
903  $after = end( $objects ); // update last item
904  }
905 
906  return $dirs;
907  }
908 
920  public function getFileListPageInternal( $fullCont, $dir, &$after, $limit, array $params ) {
921  $files = []; // list of (path, stat array or null) entries
922  if ( $after === INF ) {
923  return $files; // nothing more
924  }
925 
926  $ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
927 
928  $prefix = ( $dir == '' ) ? null : "{$dir}/";
929  // $objects will contain a list of unfiltered names or CF_Object items
930  // Non-recursive: only list files right under $dir
931  if ( !empty( $params['topOnly'] ) ) {
932  if ( !empty( $params['adviseStat'] ) ) {
933  $status = $this->objectListing( $fullCont, 'info', $limit, $after, $prefix, '/' );
934  } else {
935  $status = $this->objectListing( $fullCont, 'names', $limit, $after, $prefix, '/' );
936  }
937  } else {
938  // Recursive: list all files under $dir and its subdirs
939  if ( !empty( $params['adviseStat'] ) ) {
940  $status = $this->objectListing( $fullCont, 'info', $limit, $after, $prefix );
941  } else {
942  $status = $this->objectListing( $fullCont, 'names', $limit, $after, $prefix );
943  }
944  }
945 
946  // Reformat this list into a list of (name, stat array or null) entries
947  if ( !$status->isOK() ) {
948  throw new FileBackendError( "Iterator page I/O error." );
949  }
950 
951  $objects = $status->value;
952  $files = $this->buildFileObjectListing( $params, $dir, $objects );
953 
954  // Page on the unfiltered object listing (what is returned may be filtered)
955  if ( count( $objects ) < $limit ) {
956  $after = INF; // avoid a second RTT
957  } else {
958  $after = end( $objects ); // update last item
959  $after = is_object( $after ) ? $after->name : $after;
960  }
961 
962  return $files;
963  }
964 
974  private function buildFileObjectListing( array $params, $dir, array $objects ) {
975  $names = [];
976  foreach ( $objects as $object ) {
977  if ( is_object( $object ) ) {
978  if ( isset( $object->subdir ) || !isset( $object->name ) ) {
979  continue; // virtual directory entry; ignore
980  }
981  $stat = [
982  // Convert various random Swift dates to TS_MW
983  'mtime' => $this->convertSwiftDate( $object->last_modified, TS_MW ),
984  'size' => (int)$object->bytes,
985  'sha1' => null,
986  // Note: manifiest ETags are not an MD5 of the file
987  'md5' => ctype_xdigit( $object->hash ) ? $object->hash : null,
988  'latest' => false // eventually consistent
989  ];
990  $names[] = [ $object->name, $stat ];
991  } elseif ( substr( $object, -1 ) !== '/' ) {
992  // Omit directories, which end in '/' in listings
993  $names[] = [ $object, null ];
994  }
995  }
996 
997  return $names;
998  }
999 
1006  public function loadListingStatInternal( $path, array $val ) {
1007  $this->cheapCache->set( $path, 'stat', $val );
1008  }
1009 
1010  protected function doGetFileXAttributes( array $params ) {
1011  $stat = $this->getFileStat( $params );
1012  if ( $stat ) {
1013  if ( !isset( $stat['xattr'] ) ) {
1014  // Stat entries filled by file listings don't include metadata/headers
1015  $this->clearCache( [ $params['src'] ] );
1016  $stat = $this->getFileStat( $params );
1017  }
1018 
1019  return $stat['xattr'];
1020  } else {
1021  return false;
1022  }
1023  }
1024 
1025  protected function doGetFileSha1base36( array $params ) {
1026  $stat = $this->getFileStat( $params );
1027  if ( $stat ) {
1028  if ( !isset( $stat['sha1'] ) ) {
1029  // Stat entries filled by file listings don't include SHA1
1030  $this->clearCache( [ $params['src'] ] );
1031  $stat = $this->getFileStat( $params );
1032  }
1033 
1034  return $stat['sha1'];
1035  } else {
1036  return false;
1037  }
1038  }
1039 
1040  protected function doStreamFile( array $params ) {
1041  $status = $this->newStatus();
1042 
1043  $flags = !empty( $params['headless'] ) ? StreamFile::STREAM_HEADLESS : 0;
1044 
1045  list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
1046  if ( $srcRel === null ) {
1048  $status->fatal( 'backend-fail-invalidpath', $params['src'] );
1049 
1050  return $status;
1051  }
1052 
1053  $auth = $this->getAuthentication();
1054  if ( !$auth || !is_array( $this->getContainerStat( $srcCont ) ) ) {
1056  $status->fatal( 'backend-fail-stream', $params['src'] );
1057 
1058  return $status;
1059  }
1060 
1061  // If "headers" is set, we only want to send them if the file is there.
1062  // Do not bother checking if the file exists if headers are not set though.
1063  if ( $params['headers'] && !$this->fileExists( $params ) ) {
1065  $status->fatal( 'backend-fail-stream', $params['src'] );
1066 
1067  return $status;
1068  }
1069 
1070  // Send the requested additional headers
1071  foreach ( $params['headers'] as $header ) {
1072  header( $header ); // aways send
1073  }
1074 
1075  if ( empty( $params['allowOB'] ) ) {
1076  // Cancel output buffering and gzipping if set
1077  call_user_func( $this->obResetFunc );
1078  }
1079 
1080  $handle = fopen( 'php://output', 'wb' );
1081  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( [
1082  'method' => 'GET',
1083  'url' => $this->storageUrl( $auth, $srcCont, $srcRel ),
1084  'headers' => $this->authTokenHeaders( $auth )
1085  + $this->headersFromParams( $params ) + $params['options'],
1086  'stream' => $handle,
1087  'flags' => [ 'relayResponseHeaders' => empty( $params['headless'] ) ]
1088  ] );
1089 
1090  if ( $rcode >= 200 && $rcode <= 299 ) {
1091  // good
1092  } elseif ( $rcode === 404 ) {
1093  $status->fatal( 'backend-fail-stream', $params['src'] );
1094  // Per T43113, nasty things can happen if bad cache entries get
1095  // stuck in cache. It's also possible that this error can come up
1096  // with simple race conditions. Clear out the stat cache to be safe.
1097  $this->clearCache( [ $params['src'] ] );
1098  $this->deleteFileCache( $params['src'] );
1099  } else {
1100  $this->onError( $status, __METHOD__, $params, $rerr, $rcode, $rdesc );
1101  }
1102 
1103  return $status;
1104  }
1105 
1106  protected function doGetLocalCopyMulti( array $params ) {
1108  $tmpFiles = [];
1109 
1110  $auth = $this->getAuthentication();
1111 
1112  $ep = array_diff_key( $params, [ 'srcs' => 1 ] ); // for error logging
1113  // Blindly create tmp files and stream to them, catching any exception if the file does
1114  // not exist. Doing a stat here is useless causes infinite loops in addMissingMetadata().
1115  $reqs = []; // (path => op)
1116 
1117  foreach ( $params['srcs'] as $path ) { // each path in this concurrent batch
1118  list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $path );
1119  if ( $srcRel === null || !$auth ) {
1120  $tmpFiles[$path] = null;
1121  continue;
1122  }
1123  // Get source file extension
1125  // Create a new temporary file...
1126  $tmpFile = TempFSFile::factory( 'localcopy_', $ext, $this->tmpDirectory );
1127  if ( $tmpFile ) {
1128  $handle = fopen( $tmpFile->getPath(), 'wb' );
1129  if ( $handle ) {
1130  $reqs[$path] = [
1131  'method' => 'GET',
1132  'url' => $this->storageUrl( $auth, $srcCont, $srcRel ),
1133  'headers' => $this->authTokenHeaders( $auth )
1134  + $this->headersFromParams( $params ),
1135  'stream' => $handle,
1136  ];
1137  } else {
1138  $tmpFile = null;
1139  }
1140  }
1141  $tmpFiles[$path] = $tmpFile;
1142  }
1143 
1144  $isLatest = ( $this->isRGW || !empty( $params['latest'] ) );
1145  $opts = [ 'maxConnsPerHost' => $params['concurrency'] ];
1146  $reqs = $this->http->runMulti( $reqs, $opts );
1147  foreach ( $reqs as $path => $op ) {
1148  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $op['response'];
1149  fclose( $op['stream'] ); // close open handle
1150  if ( $rcode >= 200 && $rcode <= 299 ) {
1151  $size = $tmpFiles[$path] ? $tmpFiles[$path]->getSize() : 0;
1152  // Double check that the disk is not full/broken
1153  if ( $size != $rhdrs['content-length'] ) {
1154  $tmpFiles[$path] = null;
1155  $rerr = "Got {$size}/{$rhdrs['content-length']} bytes";
1156  $this->onError( null, __METHOD__,
1157  [ 'src' => $path ] + $ep, $rerr, $rcode, $rdesc );
1158  }
1159  // Set the file stat process cache in passing
1160  $stat = $this->getStatFromHeaders( $rhdrs );
1161  $stat['latest'] = $isLatest;
1162  $this->cheapCache->set( $path, 'stat', $stat );
1163  } elseif ( $rcode === 404 ) {
1164  $tmpFiles[$path] = false;
1165  } else {
1166  $tmpFiles[$path] = null;
1167  $this->onError( null, __METHOD__,
1168  [ 'src' => $path ] + $ep, $rerr, $rcode, $rdesc );
1169  }
1170  }
1171 
1172  return $tmpFiles;
1173  }
1174 
1175  public function getFileHttpUrl( array $params ) {
1176  if ( $this->swiftTempUrlKey != '' ||
1177  ( $this->rgwS3AccessKey != '' && $this->rgwS3SecretKey != '' )
1178  ) {
1179  list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
1180  if ( $srcRel === null ) {
1181  return null; // invalid path
1182  }
1183 
1184  $auth = $this->getAuthentication();
1185  if ( !$auth ) {
1186  return null;
1187  }
1188 
1189  $ttl = isset( $params['ttl'] ) ? $params['ttl'] : 86400;
1190  $expires = time() + $ttl;
1191 
1192  if ( $this->swiftTempUrlKey != '' ) {
1193  $url = $this->storageUrl( $auth, $srcCont, $srcRel );
1194  // Swift wants the signature based on the unencoded object name
1195  $contPath = parse_url( $this->storageUrl( $auth, $srcCont ), PHP_URL_PATH );
1196  $signature = hash_hmac( 'sha1',
1197  "GET\n{$expires}\n{$contPath}/{$srcRel}",
1198  $this->swiftTempUrlKey
1199  );
1200 
1201  return "{$url}?temp_url_sig={$signature}&temp_url_expires={$expires}";
1202  } else { // give S3 API URL for rgw
1203  // Path for signature starts with the bucket
1204  $spath = '/' . rawurlencode( $srcCont ) . '/' .
1205  str_replace( '%2F', '/', rawurlencode( $srcRel ) );
1206  // Calculate the hash
1207  $signature = base64_encode( hash_hmac(
1208  'sha1',
1209  "GET\n\n\n{$expires}\n{$spath}",
1210  $this->rgwS3SecretKey,
1211  true // raw
1212  ) );
1213  // See https://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html.
1214  // Note: adding a newline for empty CanonicalizedAmzHeaders does not work.
1215  // Note: S3 API is the rgw default; remove the /swift/ URL bit.
1216  return str_replace( '/swift/v1', '', $this->storageUrl( $auth ) . $spath ) .
1217  '?' .
1218  http_build_query( [
1219  'Signature' => $signature,
1220  'Expires' => $expires,
1221  'AWSAccessKeyId' => $this->rgwS3AccessKey
1222  ] );
1223  }
1224  }
1225 
1226  return null;
1227  }
1228 
1229  protected function directoriesAreVirtual() {
1230  return true;
1231  }
1232 
1241  protected function headersFromParams( array $params ) {
1242  $hdrs = [];
1243  if ( !empty( $params['latest'] ) ) {
1244  $hdrs['x-newest'] = 'true';
1245  }
1246 
1247  return $hdrs;
1248  }
1249 
1255  protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
1257  $statuses = [];
1258 
1259  $auth = $this->getAuthentication();
1260  if ( !$auth ) {
1261  foreach ( $fileOpHandles as $index => $fileOpHandle ) {
1262  $statuses[$index] = $this->newStatus( 'backend-fail-connect', $this->name );
1263  }
1264 
1265  return $statuses;
1266  }
1267 
1268  // Split the HTTP requests into stages that can be done concurrently
1269  $httpReqsByStage = []; // map of (stage => index => HTTP request)
1270  foreach ( $fileOpHandles as $index => $fileOpHandle ) {
1272  $reqs = $fileOpHandle->httpOp;
1273  // Convert the 'url' parameter to an actual URL using $auth
1274  foreach ( $reqs as $stage => &$req ) {
1275  list( $container, $relPath ) = $req['url'];
1276  $req['url'] = $this->storageUrl( $auth, $container, $relPath );
1277  $req['headers'] = isset( $req['headers'] ) ? $req['headers'] : [];
1278  $req['headers'] = $this->authTokenHeaders( $auth ) + $req['headers'];
1279  $httpReqsByStage[$stage][$index] = $req;
1280  }
1281  $statuses[$index] = $this->newStatus();
1282  }
1283 
1284  // Run all requests for the first stage, then the next, and so on
1285  $reqCount = count( $httpReqsByStage );
1286  for ( $stage = 0; $stage < $reqCount; ++$stage ) {
1287  $httpReqs = $this->http->runMulti( $httpReqsByStage[$stage] );
1288  foreach ( $httpReqs as $index => $httpReq ) {
1289  // Run the callback for each request of this operation
1290  $callback = $fileOpHandles[$index]->callback;
1291  call_user_func_array( $callback, [ $httpReq, $statuses[$index] ] );
1292  // On failure, abort all remaining requests for this operation
1293  // (e.g. abort the DELETE request if the COPY request fails for a move)
1294  if ( !$statuses[$index]->isOK() ) {
1295  $stages = count( $fileOpHandles[$index]->httpOp );
1296  for ( $s = ( $stage + 1 ); $s < $stages; ++$s ) {
1297  unset( $httpReqsByStage[$s][$index] );
1298  }
1299  }
1300  }
1301  }
1302 
1303  return $statuses;
1304  }
1305 
1328  protected function setContainerAccess( $container, array $readGrps, array $writeGrps ) {
1329  $status = $this->newStatus();
1330  $auth = $this->getAuthentication();
1331 
1332  if ( !$auth ) {
1333  $status->fatal( 'backend-fail-connect', $this->name );
1334 
1335  return $status;
1336  }
1337 
1338  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( [
1339  'method' => 'POST',
1340  'url' => $this->storageUrl( $auth, $container ),
1341  'headers' => $this->authTokenHeaders( $auth ) + [
1342  'x-container-read' => implode( ',', $readGrps ),
1343  'x-container-write' => implode( ',', $writeGrps )
1344  ]
1345  ] );
1346 
1347  if ( $rcode != 204 && $rcode !== 202 ) {
1348  $status->fatal( 'backend-fail-internal', $this->name );
1349  $this->logger->error( __METHOD__ . ': unexpected rcode value (' . $rcode . ')' );
1350  }
1351 
1352  return $status;
1353  }
1354 
1363  protected function getContainerStat( $container, $bypassCache = false ) {
1364  $ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
1365 
1366  if ( $bypassCache ) { // purge cache
1367  $this->containerStatCache->clear( $container );
1368  } elseif ( !$this->containerStatCache->has( $container, 'stat' ) ) {
1369  $this->primeContainerCache( [ $container ] ); // check persistent cache
1370  }
1371  if ( !$this->containerStatCache->has( $container, 'stat' ) ) {
1372  $auth = $this->getAuthentication();
1373  if ( !$auth ) {
1374  return null;
1375  }
1376 
1377  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( [
1378  'method' => 'HEAD',
1379  'url' => $this->storageUrl( $auth, $container ),
1380  'headers' => $this->authTokenHeaders( $auth )
1381  ] );
1382 
1383  if ( $rcode === 204 ) {
1384  $stat = [
1385  'count' => $rhdrs['x-container-object-count'],
1386  'bytes' => $rhdrs['x-container-bytes-used']
1387  ];
1388  if ( $bypassCache ) {
1389  return $stat;
1390  } else {
1391  $this->containerStatCache->set( $container, 'stat', $stat ); // cache it
1392  $this->setContainerCache( $container, $stat ); // update persistent cache
1393  }
1394  } elseif ( $rcode === 404 ) {
1395  return false;
1396  } else {
1397  $this->onError( null, __METHOD__,
1398  [ 'cont' => $container ], $rerr, $rcode, $rdesc );
1399 
1400  return null;
1401  }
1402  }
1403 
1404  return $this->containerStatCache->get( $container, 'stat' );
1405  }
1406 
1414  protected function createContainer( $container, array $params ) {
1415  $status = $this->newStatus();
1416 
1417  $auth = $this->getAuthentication();
1418  if ( !$auth ) {
1419  $status->fatal( 'backend-fail-connect', $this->name );
1420 
1421  return $status;
1422  }
1423 
1424  // @see SwiftFileBackend::setContainerAccess()
1425  if ( empty( $params['noAccess'] ) ) {
1426  $readGrps = [ '.r:*', $this->swiftUser ]; // public
1427  } else {
1428  $readGrps = [ $this->swiftUser ]; // private
1429  }
1430  $writeGrps = [ $this->swiftUser ]; // sanity
1431 
1432  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( [
1433  'method' => 'PUT',
1434  'url' => $this->storageUrl( $auth, $container ),
1435  'headers' => $this->authTokenHeaders( $auth ) + [
1436  'x-container-read' => implode( ',', $readGrps ),
1437  'x-container-write' => implode( ',', $writeGrps )
1438  ]
1439  ] );
1440 
1441  if ( $rcode === 201 ) { // new
1442  // good
1443  } elseif ( $rcode === 202 ) { // already there
1444  // this shouldn't really happen, but is OK
1445  } else {
1446  $this->onError( $status, __METHOD__, $params, $rerr, $rcode, $rdesc );
1447  }
1448 
1449  return $status;
1450  }
1451 
1459  protected function deleteContainer( $container, array $params ) {
1460  $status = $this->newStatus();
1461 
1462  $auth = $this->getAuthentication();
1463  if ( !$auth ) {
1464  $status->fatal( 'backend-fail-connect', $this->name );
1465 
1466  return $status;
1467  }
1468 
1469  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( [
1470  'method' => 'DELETE',
1471  'url' => $this->storageUrl( $auth, $container ),
1472  'headers' => $this->authTokenHeaders( $auth )
1473  ] );
1474 
1475  if ( $rcode >= 200 && $rcode <= 299 ) { // deleted
1476  $this->containerStatCache->clear( $container ); // purge
1477  } elseif ( $rcode === 404 ) { // not there
1478  // this shouldn't really happen, but is OK
1479  } elseif ( $rcode === 409 ) { // not empty
1480  $this->onError( $status, __METHOD__, $params, $rerr, $rcode, $rdesc ); // race?
1481  } else {
1482  $this->onError( $status, __METHOD__, $params, $rerr, $rcode, $rdesc );
1483  }
1484 
1485  return $status;
1486  }
1487 
1500  private function objectListing(
1501  $fullCont, $type, $limit, $after = null, $prefix = null, $delim = null
1502  ) {
1503  $status = $this->newStatus();
1504 
1505  $auth = $this->getAuthentication();
1506  if ( !$auth ) {
1507  $status->fatal( 'backend-fail-connect', $this->name );
1508 
1509  return $status;
1510  }
1511 
1512  $query = [ 'limit' => $limit ];
1513  if ( $type === 'info' ) {
1514  $query['format'] = 'json';
1515  }
1516  if ( $after !== null ) {
1517  $query['marker'] = $after;
1518  }
1519  if ( $prefix !== null ) {
1520  $query['prefix'] = $prefix;
1521  }
1522  if ( $delim !== null ) {
1523  $query['delimiter'] = $delim;
1524  }
1525 
1526  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( [
1527  'method' => 'GET',
1528  'url' => $this->storageUrl( $auth, $fullCont ),
1529  'query' => $query,
1530  'headers' => $this->authTokenHeaders( $auth )
1531  ] );
1532 
1533  $params = [ 'cont' => $fullCont, 'prefix' => $prefix, 'delim' => $delim ];
1534  if ( $rcode === 200 ) { // good
1535  if ( $type === 'info' ) {
1536  $status->value = FormatJson::decode( trim( $rbody ) );
1537  } else {
1538  $status->value = explode( "\n", trim( $rbody ) );
1539  }
1540  } elseif ( $rcode === 204 ) {
1541  $status->value = []; // empty container
1542  } elseif ( $rcode === 404 ) {
1543  $status->value = []; // no container
1544  } else {
1545  $this->onError( $status, __METHOD__, $params, $rerr, $rcode, $rdesc );
1546  }
1547 
1548  return $status;
1549  }
1550 
1551  protected function doPrimeContainerCache( array $containerInfo ) {
1552  foreach ( $containerInfo as $container => $info ) {
1553  $this->containerStatCache->set( $container, 'stat', $info );
1554  }
1555  }
1556 
1557  protected function doGetFileStatMulti( array $params ) {
1558  $stats = [];
1559 
1560  $auth = $this->getAuthentication();
1561 
1562  $reqs = [];
1563  foreach ( $params['srcs'] as $path ) {
1564  list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $path );
1565  if ( $srcRel === null ) {
1566  $stats[$path] = false;
1567  continue; // invalid storage path
1568  } elseif ( !$auth ) {
1569  $stats[$path] = null;
1570  continue;
1571  }
1572 
1573  // (a) Check the container
1574  $cstat = $this->getContainerStat( $srcCont );
1575  if ( $cstat === false ) {
1576  $stats[$path] = false;
1577  continue; // ok, nothing to do
1578  } elseif ( !is_array( $cstat ) ) {
1579  $stats[$path] = null;
1580  continue;
1581  }
1582 
1583  $reqs[$path] = [
1584  'method' => 'HEAD',
1585  'url' => $this->storageUrl( $auth, $srcCont, $srcRel ),
1586  'headers' => $this->authTokenHeaders( $auth ) + $this->headersFromParams( $params )
1587  ];
1588  }
1589 
1590  $opts = [ 'maxConnsPerHost' => $params['concurrency'] ];
1591  $reqs = $this->http->runMulti( $reqs, $opts );
1592 
1593  foreach ( $params['srcs'] as $path ) {
1594  if ( array_key_exists( $path, $stats ) ) {
1595  continue; // some sort of failure above
1596  }
1597  // (b) Check the file
1598  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $reqs[$path]['response'];
1599  if ( $rcode === 200 || $rcode === 204 ) {
1600  // Update the object if it is missing some headers
1601  $rhdrs = $this->addMissingMetadata( $rhdrs, $path );
1602  // Load the stat array from the headers
1603  $stat = $this->getStatFromHeaders( $rhdrs );
1604  if ( $this->isRGW ) {
1605  $stat['latest'] = true; // strong consistency
1606  }
1607  } elseif ( $rcode === 404 ) {
1608  $stat = false;
1609  } else {
1610  $stat = null;
1611  $this->onError( null, __METHOD__, $params, $rerr, $rcode, $rdesc );
1612  }
1613  $stats[$path] = $stat;
1614  }
1615 
1616  return $stats;
1617  }
1618 
1623  protected function getStatFromHeaders( array $rhdrs ) {
1624  // Fetch all of the custom metadata headers
1625  $metadata = $this->getMetadata( $rhdrs );
1626  // Fetch all of the custom raw HTTP headers
1627  $headers = $this->sanitizeHdrs( [ 'headers' => $rhdrs ] );
1628 
1629  return [
1630  // Convert various random Swift dates to TS_MW
1631  'mtime' => $this->convertSwiftDate( $rhdrs['last-modified'], TS_MW ),
1632  // Empty objects actually return no content-length header in Ceph
1633  'size' => isset( $rhdrs['content-length'] ) ? (int)$rhdrs['content-length'] : 0,
1634  'sha1' => isset( $metadata['sha1base36'] ) ? $metadata['sha1base36'] : null,
1635  // Note: manifiest ETags are not an MD5 of the file
1636  'md5' => ctype_xdigit( $rhdrs['etag'] ) ? $rhdrs['etag'] : null,
1637  'xattr' => [ 'metadata' => $metadata, 'headers' => $headers ]
1638  ];
1639  }
1640 
1644  protected function getAuthentication() {
1645  if ( $this->authErrorTimestamp !== null ) {
1646  if ( ( time() - $this->authErrorTimestamp ) < 60 ) {
1647  return null; // failed last attempt; don't bother
1648  } else { // actually retry this time
1649  $this->authErrorTimestamp = null;
1650  }
1651  }
1652  // Session keys expire after a while, so we renew them periodically
1653  $reAuth = ( ( time() - $this->authSessionTimestamp ) > $this->authTTL );
1654  // Authenticate with proxy and get a session key...
1655  if ( !$this->authCreds || $reAuth ) {
1656  $this->authSessionTimestamp = 0;
1657  $cacheKey = $this->getCredsCacheKey( $this->swiftUser );
1658  $creds = $this->srvCache->get( $cacheKey ); // credentials
1659  // Try to use the credential cache
1660  if ( isset( $creds['auth_token'] ) && isset( $creds['storage_url'] ) ) {
1661  $this->authCreds = $creds;
1662  // Skew the timestamp for worst case to avoid using stale credentials
1663  $this->authSessionTimestamp = time() - ceil( $this->authTTL / 2 );
1664  } else { // cache miss
1665  list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( [
1666  'method' => 'GET',
1667  'url' => "{$this->swiftAuthUrl}/v1.0",
1668  'headers' => [
1669  'x-auth-user' => $this->swiftUser,
1670  'x-auth-key' => $this->swiftKey
1671  ]
1672  ] );
1673 
1674  if ( $rcode >= 200 && $rcode <= 299 ) { // OK
1675  $this->authCreds = [
1676  'auth_token' => $rhdrs['x-auth-token'],
1677  'storage_url' => $rhdrs['x-storage-url']
1678  ];
1679  $this->srvCache->set( $cacheKey, $this->authCreds, ceil( $this->authTTL / 2 ) );
1680  $this->authSessionTimestamp = time();
1681  } elseif ( $rcode === 401 ) {
1682  $this->onError( null, __METHOD__, [], "Authentication failed.", $rcode );
1683  $this->authErrorTimestamp = time();
1684 
1685  return null;
1686  } else {
1687  $this->onError( null, __METHOD__, [], "HTTP return code: $rcode", $rcode );
1688  $this->authErrorTimestamp = time();
1689 
1690  return null;
1691  }
1692  }
1693  // Ceph RGW does not use <account> in URLs (OpenStack Swift uses "/v1/<account>")
1694  if ( substr( $this->authCreds['storage_url'], -3 ) === '/v1' ) {
1695  $this->isRGW = true; // take advantage of strong consistency in Ceph
1696  }
1697  }
1698 
1699  return $this->authCreds;
1700  }
1701 
1708  protected function storageUrl( array $creds, $container = null, $object = null ) {
1709  $parts = [ $creds['storage_url'] ];
1710  if ( strlen( $container ) ) {
1711  $parts[] = rawurlencode( $container );
1712  }
1713  if ( strlen( $object ) ) {
1714  $parts[] = str_replace( "%2F", "/", rawurlencode( $object ) );
1715  }
1716 
1717  return implode( '/', $parts );
1718  }
1719 
1724  protected function authTokenHeaders( array $creds ) {
1725  return [ 'x-auth-token' => $creds['auth_token'] ];
1726  }
1727 
1734  private function getCredsCacheKey( $username ) {
1735  return 'swiftcredentials:' . md5( $username . ':' . $this->swiftAuthUrl );
1736  }
1737 
1749  public function onError( $status, $func, array $params, $err = '', $code = 0, $desc = '' ) {
1750  if ( $status instanceof StatusValue ) {
1751  $status->fatal( 'backend-fail-internal', $this->name );
1752  }
1753  if ( $code == 401 ) { // possibly a stale token
1754  $this->srvCache->delete( $this->getCredsCacheKey( $this->swiftUser ) );
1755  }
1756  $this->logger->error(
1757  "HTTP $code ($desc) in '{$func}' (given '" . FormatJson::encode( $params ) . "')" .
1758  ( $err ? ": $err" : "" )
1759  );
1760  }
1761 }
1762 
1768  public $httpOp;
1770  public $callback;
1771 
1778  $this->backend = $backend;
1779  $this->callback = $callback;
1780  $this->httpOp = $httpOp;
1781  }
1782 }
1783 
1791 abstract class SwiftFileBackendList implements Iterator {
1793  protected $bufferIter = [];
1794 
1796  protected $bufferAfter = null;
1797 
1799  protected $pos = 0;
1800 
1802  protected $params = [];
1803 
1805  protected $backend;
1806 
1808  protected $container;
1809 
1811  protected $dir;
1812 
1814  protected $suffixStart;
1815 
1816  const PAGE_SIZE = 9000; // file listing buffer size
1817 
1824  public function __construct( SwiftFileBackend $backend, $fullCont, $dir, array $params ) {
1825  $this->backend = $backend;
1826  $this->container = $fullCont;
1827  $this->dir = $dir;
1828  if ( substr( $this->dir, -1 ) === '/' ) {
1829  $this->dir = substr( $this->dir, 0, -1 ); // remove trailing slash
1830  }
1831  if ( $this->dir == '' ) { // whole container
1832  $this->suffixStart = 0;
1833  } else { // dir within container
1834  $this->suffixStart = strlen( $this->dir ) + 1; // size of "path/to/dir/"
1835  }
1836  $this->params = $params;
1837  }
1838 
1843  public function key() {
1844  return $this->pos;
1845  }
1846 
1850  public function next() {
1851  // Advance to the next file in the page
1852  next( $this->bufferIter );
1853  ++$this->pos;
1854  // Check if there are no files left in this page and
1855  // advance to the next page if this page was not empty.
1856  if ( !$this->valid() && count( $this->bufferIter ) ) {
1857  $this->bufferIter = $this->pageFromList(
1858  $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE, $this->params
1859  ); // updates $this->bufferAfter
1860  }
1861  }
1862 
1866  public function rewind() {
1867  $this->pos = 0;
1868  $this->bufferAfter = null;
1869  $this->bufferIter = $this->pageFromList(
1870  $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE, $this->params
1871  ); // updates $this->bufferAfter
1872  }
1873 
1878  public function valid() {
1879  if ( $this->bufferIter === null ) {
1880  return false; // some failure?
1881  } else {
1882  return ( current( $this->bufferIter ) !== false ); // no paths can have this value
1883  }
1884  }
1885 
1896  abstract protected function pageFromList( $container, $dir, &$after, $limit, array $params );
1897 }
1898 
1907  public function current() {
1908  return substr( current( $this->bufferIter ), $this->suffixStart, -1 );
1909  }
1910 
1911  protected function pageFromList( $container, $dir, &$after, $limit, array $params ) {
1912  return $this->backend->getDirListPageInternal( $container, $dir, $after, $limit, $params );
1913  }
1914 }
1915 
1924  public function current() {
1925  list( $path, $stat ) = current( $this->bufferIter );
1926  $relPath = substr( $path, $this->suffixStart );
1927  if ( is_array( $stat ) ) {
1928  $storageDir = rtrim( $this->params['dir'], '/' );
1929  $this->backend->loadListingStatInternal( "$storageDir/$relPath", $stat );
1930  }
1931 
1932  return $relPath;
1933  }
1934 
1935  protected function pageFromList( $container, $dir, &$after, $limit, array $params ) {
1936  return $this->backend->getFileListPageInternal( $container, $dir, $after, $limit, $params );
1937  }
1938 }
SwiftFileBackend\$containerStatCache
ProcessCacheLRU $containerStatCache
Container stat cache.
Definition: SwiftFileBackend.php:64
SwiftFileBackendList\rewind
rewind()
Definition: SwiftFileBackend.php:1866
SwiftFileOpHandle
Definition: SwiftFileBackend.php:1766
MWTimestamp
Library for creating and parsing MW-style timestamps.
Definition: MWTimestamp.php:32
SwiftFileBackend\doPrepareInternal
doPrepareInternal( $fullCont, $dir, array $params)
Definition: SwiftFileBackend.php:565
SwiftFileBackend\$isRGW
bool $isRGW
Whether the server is an Ceph RGW.
Definition: SwiftFileBackend.php:76
MultiHttpClient
Class to handle concurrent HTTP requests.
Definition: MultiHttpClient.php:45
StatusValue
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: StatusValue.php:42
SwiftFileBackend\isPathUsableInternal
isPathUsableInternal( $storagePath)
Check if a file can be created or changed at a given storage path.
Definition: SwiftFileBackend.php:159
$request
error also a ContextSource you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2612
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:189
SwiftFileBackend\$authErrorTimestamp
int $authErrorTimestamp
UNIX timestamp.
Definition: SwiftFileBackend.php:73
SwiftFileBackend\getAuthentication
getAuthentication()
Definition: SwiftFileBackend.php:1644
SwiftFileBackend\getContainerStat
getContainerStat( $container, $bypassCache=false)
Get a Swift container stat array, possibly from process cache.
Definition: SwiftFileBackend.php:1363
SwiftFileBackendDirList\pageFromList
pageFromList( $container, $dir, &$after, $limit, array $params)
Get the given list portion (page)
Definition: SwiftFileBackend.php:1911
EmptyBagOStuff
A BagOStuff object with no objects in it.
Definition: EmptyBagOStuff.php:29
SwiftFileBackend\$swiftKey
string $swiftKey
Secret key for user.
Definition: SwiftFileBackend.php:49
SwiftFileBackend\getStatFromHeaders
getStatFromHeaders(array $rhdrs)
Definition: SwiftFileBackend.php:1623
FileBackend\ATTR_HEADERS
const ATTR_HEADERS
Bitfield flags for supported features.
Definition: FileBackend.php:129
captcha-old.count
count
Definition: captcha-old.py:225
SwiftFileBackend\getCustomHeaders
getCustomHeaders(array $rawHeaders)
Definition: SwiftFileBackend.php:185
LockManager\LOCK_UW
const LOCK_UW
Definition: LockManager.php:69
SwiftFileBackend\doGetFileXAttributes
doGetFileXAttributes(array $params)
Definition: SwiftFileBackend.php:1010
FileBackendError
File backend exception for checked exceptions (e.g.
Definition: FileBackendError.php:8
FileBackend\getScopedFileLocks
getScopedFileLocks(array $paths, $type, StatusValue $status, $timeout=0)
Lock the files at the given storage paths in the backend.
Definition: FileBackend.php:1344
$status
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition: hooks.txt:1049
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:974
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
FileBackendStore\fileExists
fileExists(array $params)
Check if a file exists at a storage path in the backend.
Definition: FileBackendStore.php:605
$req
this hook is for auditing only $req
Definition: hooks.txt:990
SwiftFileBackend\$swiftUser
string $swiftUser
Swift user (account:user) to authenticate as.
Definition: SwiftFileBackend.php:46
SwiftFileBackend\loadListingStatInternal
loadListingStatInternal( $path, array $val)
Do not call this function outside of SwiftFileBackendFileList.
Definition: SwiftFileBackend.php:1006
FileBackend\extensionFromPath
static extensionFromPath( $path, $case='lowercase')
Get the final extension from a storage or FS path.
Definition: FileBackend.php:1507
FileBackendStore\deleteFileCache
deleteFileCache( $path)
Delete the cached stat info for a file path.
Definition: FileBackendStore.php:1732
SwiftFileBackend\resolveContainerPath
resolveContainerPath( $container, $relStoragePath)
Resolve a relative storage path, checking if it's allowed by the backend.
Definition: SwiftFileBackend.php:149
$params
$params
Definition: styleTest.css.php:40
FileBackendStore\executeOpHandlesInternal
executeOpHandlesInternal(array $fileOpHandles)
Execute a list of FileBackendStoreOpHandle handles in parallel.
Definition: FileBackendStore.php:1206
BagOStuff
interface is intended to be more or less compatible with the PHP memcached client.
Definition: BagOStuff.php:47
$s
$s
Definition: mergeMessageFileList.php:188
SwiftFileBackend\doGetFileContentsMulti
doGetFileContentsMulti(array $params)
Definition: SwiftFileBackend.php:748
SwiftFileBackend\createContainer
createContainer( $container, array $params)
Create a Swift container.
Definition: SwiftFileBackend.php:1414
http
Apache License January http
Definition: APACHE-LICENSE-2.0.txt:3
$type
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition: hooks.txt:2536
SwiftFileBackend\getFileListPageInternal
getFileListPageInternal( $fullCont, $dir, &$after, $limit, array $params)
Do not call this function outside of SwiftFileBackendFileList.
Definition: SwiftFileBackend.php:920
SwiftFileBackendList\PAGE_SIZE
const PAGE_SIZE
Definition: SwiftFileBackend.php:1816
SwiftFileBackend\doGetFileSha1base36
doGetFileSha1base36(array $params)
Definition: SwiftFileBackend.php:1025
FileBackendStore\clearCache
clearCache(array $paths=null)
Invalidate any in-process file stat and property cache.
Definition: FileBackendStore.php:1283
SwiftFileBackend\getFeatures
getFeatures()
Get the a bitfield of extra features supported by the backend medium.
Definition: SwiftFileBackend.php:144
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
SwiftFileBackend\doDeleteInternal
doDeleteInternal(array $params)
Definition: SwiftFileBackend.php:469
SwiftFileBackend\headersFromParams
headersFromParams(array $params)
Get headers to send to Swift when reading a file based on a FileBackend params array,...
Definition: SwiftFileBackend.php:1241
SwiftFileBackend\deleteContainer
deleteContainer( $container, array $params)
Delete a Swift container.
Definition: SwiftFileBackend.php:1459
SwiftFileBackendList\$suffixStart
int $suffixStart
Definition: SwiftFileBackend.php:1814
SwiftFileBackend\getCredsCacheKey
getCredsCacheKey( $username)
Get the cache key for a container.
Definition: SwiftFileBackend.php:1734
$query
null for the wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1572
FormatJson\decode
static decode( $value, $assoc=false)
Decodes a JSON string.
Definition: FormatJson.php:187
FormatJson\encode
static encode( $value, $pretty=false, $escaping=0)
Returns the JSON representation of a value.
Definition: FormatJson.php:127
SwiftFileBackend\doDirectoryExists
doDirectoryExists( $fullCont, $dir, array $params)
Definition: SwiftFileBackend.php:797
SwiftFileBackendList\$params
array $params
Definition: SwiftFileBackend.php:1802
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:1791
SwiftFileBackend\addMissingMetadata
addMissingMetadata(array $objHdrs, $path)
Fill in any missing object metadata and save it to Swift.
Definition: SwiftFileBackend.php:696
SwiftFileBackendList\valid
valid()
Definition: SwiftFileBackend.php:1878
SwiftFileBackend\getDirectoryListInternal
getDirectoryListInternal( $fullCont, $dir, array $params)
Definition: SwiftFileBackend.php:814
FileBackendStoreOpHandle\$backend
FileBackendStore $backend
Definition: FileBackendStore.php:1867
SwiftFileBackend\getMetadata
getMetadata(array $rawHeaders)
Definition: SwiftFileBackend.php:238
SwiftFileBackend\getFileHttpUrl
getFileHttpUrl(array $params)
Definition: SwiftFileBackend.php:1175
SwiftFileBackend\doMoveInternal
doMoveInternal(array $params)
Definition: SwiftFileBackend.php:410
SwiftFileBackendList\$dir
string $dir
Storage directory.
Definition: SwiftFileBackend.php:1811
$limit
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers please use GetContentModels hook to make them known to core if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive $limit
Definition: hooks.txt:1049
FileBackend\ATTR_UNICODE_PATHS
const ATTR_UNICODE_PATHS
Definition: FileBackend.php:131
SwiftFileBackend\$authCreds
array $authCreds
Definition: SwiftFileBackend.php:67
SwiftFileBackendList\pageFromList
pageFromList( $container, $dir, &$after, $limit, array $params)
Get the given list portion (page)
$dirs
$dirs
Definition: mergeMessageFileList.php:195
SwiftFileBackend\doGetLocalCopyMulti
doGetLocalCopyMulti(array $params)
Definition: SwiftFileBackend.php:1106
SwiftFileBackend\onError
onError( $status, $func, array $params, $err='', $code=0, $desc='')
Log an unexpected exception for this backend.
Definition: SwiftFileBackend.php:1749
SwiftFileBackendDirList\current
current()
Definition: SwiftFileBackend.php:1907
StreamFile\send404Message
static send404Message( $fname, $flags=0)
Send out a standard 404 message for a file.
Definition: StreamFile.php:70
SwiftFileBackend\doDescribeInternal
doDescribeInternal(array $params)
Definition: SwiftFileBackend.php:509
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
$dir
$dir
Definition: Autoload.php:8
SwiftFileBackend\sanitizeHdrs
sanitizeHdrs(array $params)
Sanitize and filter the custom headers from a $params array.
Definition: SwiftFileBackend.php:175
SwiftFileBackend\storageUrl
storageUrl(array $creds, $container=null, $object=null)
Definition: SwiftFileBackend.php:1708
FileBackendStore\getContentType
getContentType( $storagePath, $content, $fsPath)
Get the content type to use in HEAD/GET requests for a file.
Definition: FileBackendStore.php:1839
TempFSFile\factory
static factory( $prefix, $extension='', $tmpDirectory=null)
Make a new temporary file on the file system.
Definition: TempFSFile.php:55
SwiftFileBackendFileList\current
current()
Definition: SwiftFileBackend.php:1924
SwiftFileBackendDirList
Iterator for listing directories.
Definition: SwiftFileBackend.php:1902
SwiftFileBackendList\$container
string $container
Container name.
Definition: SwiftFileBackend.php:1808
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2122
SwiftFileBackend\doSecureInternal
doSecureInternal( $fullCont, $dir, array $params)
Definition: SwiftFileBackend.php:588
FileBackend\ATTR_METADATA
const ATTR_METADATA
Definition: FileBackend.php:130
$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:626
$header
$header
Definition: updateCredits.php:35
SwiftFileBackend\__construct
__construct(array $config)
Definition: SwiftFileBackend.php:106
SwiftFileBackend\directoriesAreVirtual
directoriesAreVirtual()
Is this a key/value store where directories are just virtual? Virtual directories exists in so much a...
Definition: SwiftFileBackend.php:1229
FileBackendStore\resolveStoragePathReal
resolveStoragePathReal( $storagePath)
Like resolveStoragePath() except null values are returned if the container is sharded and the shard c...
Definition: FileBackendStore.php:1470
SwiftFileBackend\authTokenHeaders
authTokenHeaders(array $creds)
Definition: SwiftFileBackend.php:1724
SwiftFileBackendList\$bufferIter
array $bufferIter
List of path or (path,stat array) entries.
Definition: SwiftFileBackend.php:1793
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:81
SwiftFileBackendList\$backend
SwiftFileBackend $backend
Definition: SwiftFileBackend.php:1805
SwiftFileBackend\doCreateInternal
doCreateInternal(array $params)
Definition: SwiftFileBackend.php:247
SwiftFileBackend\$authSessionTimestamp
int $authSessionTimestamp
UNIX timestamp.
Definition: SwiftFileBackend.php:70
FileBackendStore
Base class for all backends using particular storage medium.
Definition: FileBackendStore.php:39
SwiftFileBackend\doExecuteOpHandlesInternal
doExecuteOpHandlesInternal(array $fileOpHandles)
Definition: SwiftFileBackend.php:1255
SwiftFileBackend\$swiftAuthUrl
string $swiftAuthUrl
Authentication base URL (without version)
Definition: SwiftFileBackend.php:43
FileBackendStoreOpHandle
FileBackendStore helper class for performing asynchronous file operations.
Definition: FileBackendStore.php:1863
$handler
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
Definition: hooks.txt:783
SwiftFileBackend\doStreamFile
doStreamFile(array $params)
Definition: SwiftFileBackend.php:1040
SwiftFileBackendList\$pos
int $pos
Definition: SwiftFileBackend.php:1799
SwiftFileBackendList\next
next()
Definition: SwiftFileBackend.php:1850
SwiftFileBackendList\$bufferAfter
string $bufferAfter
List items after this path.
Definition: SwiftFileBackend.php:1796
SwiftFileBackend\$rgwS3SecretKey
string $rgwS3SecretKey
S3 authentication key (RADOS Gateway)
Definition: SwiftFileBackend.php:58
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:1652
SwiftFileBackend\$authTTL
int $authTTL
TTL in seconds.
Definition: SwiftFileBackend.php:40
$ext
$ext
Definition: NoLocalSettings.php:25
$code
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition: hooks.txt:783
SwiftFileBackend\doCopyInternal
doCopyInternal(array $params)
Definition: SwiftFileBackend.php:362
FileBackend\$name
string $name
Unique backend name.
Definition: FileBackend.php:95
SwiftFileBackend\$rgwS3AccessKey
string $rgwS3AccessKey
S3 access key (RADOS Gateway)
Definition: SwiftFileBackend.php:55
FileBackend\getLocalCopy
getLocalCopy(array $params)
Get a local copy on disk of the file at a storage path in the backend.
Definition: FileBackend.php:1132
SwiftFileBackend\objectListing
objectListing( $fullCont, $type, $limit, $after=null, $prefix=null, $delim=null)
Get a list of objects under a container.
Definition: SwiftFileBackend.php:1500
SwiftFileOpHandle\$httpOp
array $httpOp
List of Requests for MultiHttpClient.
Definition: SwiftFileBackend.php:1768
$path
$path
Definition: NoLocalSettings.php:26
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:679
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:612
SwiftFileOpHandle\$callback
Closure $callback
Definition: SwiftFileBackend.php:1770
SwiftFileBackendFileList\pageFromList
pageFromList( $container, $dir, &$after, $limit, array $params)
Get the given list portion (page)
Definition: SwiftFileBackend.php:1935
SwiftFileBackend\doGetFileStat
doGetFileStat(array $params)
Definition: SwiftFileBackend.php:661
SwiftFileBackend\$swiftTempUrlKey
string $swiftTempUrlKey
Shared secret value for making temp URLs.
Definition: SwiftFileBackend.php:52
SwiftFileBackend\doStoreInternal
doStoreInternal(array $params)
Definition: SwiftFileBackend.php:296
SwiftFileBackend\doGetFileStatMulti
doGetFileStatMulti(array $params)
Get file stat information (concurrently if possible) for several files.
Definition: SwiftFileBackend.php:1557
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:1328
SwiftFileBackend\doCleanInternal
doCleanInternal( $fullCont, $dir, array $params)
Definition: SwiftFileBackend.php:633
ProcessCacheLRU
Handles per process caching of items.
Definition: ProcessCacheLRU.php:29
SwiftFileBackend\$srvCache
BagOStuff $srvCache
Definition: SwiftFileBackend.php:61
SwiftFileOpHandle\__construct
__construct(SwiftFileBackend $backend, Closure $callback, array $httpOp)
Definition: SwiftFileBackend.php:1777
SwiftFileBackendFileList
Iterator for listing regular files.
Definition: SwiftFileBackend.php:1919
SwiftFileBackend\getFileListInternal
getFileListInternal( $fullCont, $dir, array $params)
Definition: SwiftFileBackend.php:825
SwiftFileBackend\doPrimeContainerCache
doPrimeContainerCache(array $containerInfo)
Fill the backend-specific process cache given an array of resolved container names and their correspo...
Definition: SwiftFileBackend.php:1551
$username
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:783
$flags
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2749
FileBackendStore\setContainerCache
setContainerCache( $container, array $val)
Set the cached info for a container.
Definition: FileBackendStore.php:1629
FileBackend\scopedProfileSection
scopedProfileSection( $section)
Definition: FileBackend.php:1621
SwiftFileBackend\getDirListPageInternal
getDirListPageInternal( $fullCont, $dir, &$after, $limit, array $params)
Do not call this function outside of SwiftFileBackendFileList.
Definition: SwiftFileBackend.php:840
FileBackend\newStatus
newStatus()
Yields the result of the status wrapper callback on either:
Definition: FileBackend.php:1598
StreamFile\STREAM_HEADLESS
const STREAM_HEADLESS
Definition: StreamFile.php:28
array
the array() calling protocol came about after MediaWiki 1.4rc1.
SwiftFileBackend\getMetadataHeaders
getMetadataHeaders(array $rawHeaders)
Definition: SwiftFileBackend.php:222
SwiftFileBackendList\__construct
__construct(SwiftFileBackend $backend, $fullCont, $dir, array $params)
Definition: SwiftFileBackend.php:1824
SwiftFileBackend\$http
MultiHttpClient $http
Definition: SwiftFileBackend.php:37
SwiftFileBackendList\key
key()
Definition: SwiftFileBackend.php:1843