Switch to LOCK IN SHARE MODE in recordUpload2()
[lhc/web/wiklou.git] / includes / GlobalFunctions.php
index 3306acd..11388e8 100644 (file)
@@ -102,19 +102,30 @@ if ( !function_exists( 'gzdecode' ) ) {
 }
 
 // hash_equals function only exists in PHP >= 5.6.0
+// http://php.net/hash_equals
 if ( !function_exists( 'hash_equals' ) ) {
        /**
-        * Check whether a user-provided string is equal to a fixed-length secret without
-        * revealing bytes of the secret through timing differences.
+        * Check whether a user-provided string is equal to a fixed-length secret string
+        * without revealing bytes of the secret string through timing differences.
         *
-        * This timing guarantee -- that a partial match takes the same time as a complete
-        * mismatch -- is why this function is used in some security-sensitive parts of the code.
-        * For example, it shouldn't be possible to guess an HMAC signature one byte at a time.
+        * The usual way to compare strings (PHP's === operator or the underlying memcmp()
+        * function in C) is to compare corresponding bytes and stop at the first difference,
+        * which would take longer for a partial match than for a complete mismatch. This
+        * is not secure when one of the strings (e.g. an HMAC or token) must remain secret
+        * and the other may come from an attacker. Statistical analysis of timing measurements
+        * over many requests may allow the attacker to guess the string's bytes one at a time
+        * (and check his guesses) even if the timing differences are extremely small.
+        *
+        * When making such a security-sensitive comparison, it is essential that the sequence
+        * in which instructions are executed and memory locations are accessed not depend on
+        * the secret string's value. HOWEVER, for simplicity, we do not attempt to minimize
+        * the inevitable leakage of the string's length. That is generally known anyway as
+        * a chararacteristic of the hash function used to compute the secret value.
         *
         * Longer explanation: http://www.emerose.com/timing-attacks-explained
         *
         * @codeCoverageIgnore
-        * @param string $known_string Fixed-length secret to compare against
+        * @param string $known_string Fixed-length secret string to compare against
         * @param string $user_string User-provided string
         * @return bool True if the strings are the same, false otherwise
         */
@@ -134,14 +145,14 @@ if ( !function_exists( 'hash_equals' ) ) {
                        return false;
                }
 
-               // Note that we do one thing PHP doesn't: try to avoid leaking information about
-               // relative lengths of $known_string and $user_string, and of multiple $known_strings.
-               // However, lengths may still inevitably leak through, for example, CPU cache misses.
                $known_string_len = strlen( $known_string );
-               $user_string_len = strlen( $user_string );
-               $result = $known_string_len ^ $user_string_len;
-               for ( $i = 0; $i < $user_string_len; $i++ ) {
-                       $result |= ord( $known_string[$i % $known_string_len] ) ^ ord( $user_string[$i] );
+               if ( $known_string_len !== strlen( $user_string ) ) {
+                       return false;
+               }
+
+               $result = 0;
+               for ( $i = 0; $i < $known_string_len; $i++ ) {
+                       $result |= ord( $known_string[$i] ) ^ ord( $user_string[$i] );
                }
 
                return ( $result === 0 );
@@ -2975,7 +2986,9 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(),
  * function, as all the arguments to wfShellExec can become unwieldy.
  *
  * @note This also includes errors from limit.sh, e.g. if $wgMaxShellFileSize is exceeded.
- * @param string $cmd Command line, properly escaped for shell.
+ * @param string|string[] $cmd If string, a properly shell-escaped command line,
+ *   or an array of unescaped arguments, in which case each value will be escaped
+ *   Example:   [ 'convert', '-font', 'font name' ] would produce "'convert' '-font' 'font name'"
  * @param null|mixed &$retval Optional, will receive the program's exit code.
  *   (non-zero is usually failure)
  * @param array $environ Optional environment variables which should be
@@ -3752,11 +3765,19 @@ function wfGetNull() {
 }
 
 /**
- * Modern version of wfWaitForSlaves(). Instead of looking at replication lag
- * and waiting for it to go down, this waits for the slaves to catch up to the
- * master position. Use this when updating very large numbers of rows, as
- * in maintenance scripts, to avoid causing too much lag.  Of course, this is
- * a no-op if there are no slaves.
+ * Waits for the slaves to catch up to the master position
+ *
+ * Use this when updating very large numbers of rows, as in maintenance scripts,
+ * to avoid causing too much lag. Of course, this is a no-op if there are no slaves.
+ *
+ * By default this waits on the main DB cluster of the current wiki.
+ * If $cluster is set to "*" it will wait on all DB clusters, including
+ * external ones. If the lag being waiting on is caused by the code that
+ * does this check, it makes since to use $ifWritesSince, particularly if
+ * cluster is "*", to avoid excess overhead.
+ *
+ * Never call this function after a big DB write that is still in a transaction.
+ * This only makes sense after the possible lag inducing changes were committed.
  *
  * @param float|null $ifWritesSince Only wait if writes were done since this UNIX timestamp
  * @param string|bool $wiki Wiki identifier accepted by wfGetLB
@@ -3774,31 +3795,47 @@ function wfWaitForSlaves(
                $timeout = ( PHP_SAPI === 'cli' ) ? 86400 : 10;
        }
 
-       if ( $cluster !== false ) {
-               $lb = wfGetLBFactory()->getExternalLB( $cluster );
+       // Figure out which clusters need to be checked
+       $lbs = array();
+       if ( $cluster === '*' ) {
+               wfGetLBFactory()->forEachLB( function( LoadBalancer $lb ) use ( &$lbs ) {
+                       $lbs[] = $lb;
+               } );
+       } elseif ( $cluster !== false ) {
+               $lbs[] = wfGetLBFactory()->getExternalLB( $cluster );
        } else {
-               $lb = wfGetLB( $wiki );
+               $lbs[] = wfGetLB( $wiki );
+       }
+
+       // Get all the master positions of applicable DBs right now.
+       // This can be faster since waiting on one cluster reduces the
+       // time needed to wait on the next clusters.
+       $masterPositions = array_fill( 0, count( $lbs ), false );
+       foreach ( $lbs as $i => $lb ) {
+               // bug 27975 - Don't try to wait for slaves if there are none
+               // Prevents permission error when getting master position
+               if ( $lb->getServerCount() > 1 ) {
+                       if ( $ifWritesSince && !$lb->hasMasterConnection() ) {
+                               continue; // assume no writes done
+                       }
+                       $dbw = $lb->getConnection( DB_MASTER, array(), $wiki );
+                       if ( $ifWritesSince && $dbw->lastDoneWrites() < $ifWritesSince ) {
+                               continue; // no writes since the last wait
+                       }
+                       $masterPositions[$i] = $dbw->getMasterPos();
+               }
        }
 
-       // bug 27975 - Don't try to wait for slaves if there are none
-       // Prevents permission error when getting master position
-       if ( $lb->getServerCount() > 1 ) {
-               if ( $ifWritesSince && !$lb->hasMasterConnection() ) {
-                       return true; // assume no writes done
-               }
-               $dbw = $lb->getConnection( DB_MASTER, array(), $wiki );
-               if ( $ifWritesSince && $dbw->lastDoneWrites() < $ifWritesSince ) {
-                       return true; // no writes since the last wait
-               }
-               $pos = $dbw->getMasterPos();
-               // The DBMS may not support getMasterPos() or the whole
-               // load balancer might be fake (e.g. $wgAllDBsAreLocalhost).
-               if ( $pos !== false ) {
-                       return $lb->waitForAll( $pos, $timeout );
+       $ok = true;
+       foreach ( $lbs as $i => $lb ) {
+               if ( $masterPositions[$i] ) {
+                       // The DBMS may not support getMasterPos() or the whole
+                       // load balancer might be fake (e.g. $wgAllDBsAreLocalhost).
+                       $ok = $lb->waitForAll( $masterPositions[$i], $timeout ) && $ok;
                }
        }
 
-       return true;
+       return $ok;
 }
 
 /**