MWDebug::debugMsg( $text );
}
- if ( wfRunHooks( 'Debug', array( $text, null /* no log group */ ) ) ) {
- if ( $wgDebugLogFile != '' && !$wgProfileOnly ) {
- # Strip unprintables; they can switch terminal modes when binary data
- # gets dumped, which is pretty annoying.
- $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $text );
- $text = $wgDebugLogPrefix . $text;
- wfErrorLog( $text, $wgDebugLogFile );
- }
+ if ( $wgDebugLogFile != '' && !$wgProfileOnly ) {
+ # Strip unprintables; they can switch terminal modes when binary data
+ # gets dumped, which is pretty annoying.
+ $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $text );
+ $text = $wgDebugLogPrefix . $text;
+ wfErrorLog( $text, $wgDebugLogFile );
}
}
$time = wfTimestamp( TS_DB );
$wiki = wfWikiID();
$host = wfHostname();
- if ( wfRunHooks( 'Debug', array( $text, $logGroup ) ) ) {
- wfErrorLog( "$time $host $wiki: $text", $wgDebugLogGroups[$logGroup] );
- }
+ wfErrorLog( "$time $host $wiki: $text", $wgDebugLogGroups[$logGroup] );
} elseif ( $public === true ) {
wfDebug( "[$logGroup] $text", false );
}
* Throw a debugging exception. This function previously once exited the process,
* but now throws an exception instead, with similar results.
*
+ * @deprecated since 1.22; just throw an MWException yourself
* @param string $msg message shown when dying.
* @throws MWException
*/
function wfDebugDieBacktrace( $msg = '' ) {
+ wfDeprecated( __FUNCTION__, '1.22' );
throw new MWException( $msg );
}
return $wgLang->viewPrevNext( $title, $offset, $limit, $query, $atend );
}
-/**
- * Make a list item, used by various special pages
- *
- * @param string $page Page link
- * @param string $details Text between brackets
- * @param $oppositedm Boolean Add the direction mark opposite to your
- * language, to display text properly
- * @return String
- * @deprecated since 1.19; use Language::specialList() instead
- */
-function wfSpecialList( $page, $details, $oppositedm = true ) {
- wfDeprecated( __METHOD__, '1.19' );
-
- global $wgLang;
- return $wgLang->specialList( $page, $details, $oppositedm );
-}
-
/**
* @todo document
* @todo FIXME: We may want to blacklist some broken browsers
* @return Bool
*/
function wfIsHipHop() {
- return function_exists( 'hphp_thread_set_warmup_enabled' );
+ return defined( 'HPHP_VERSION' );
}
/**
|| preg_match( "/^\s*[+-]?0*[1-9]/", $val ); // approx C atoi() function
}
-/**
- * Wrapper function for PHP's dl(). This doesn't work in most situations from
- * PHP 5.3 onward, and is usually disabled in shared environments anyway.
- *
- * @param string $extension A PHP extension. The file suffix (.so or .dll)
- * should be omitted
- * @param string $fileName Name of the library, if not $extension.suffix
- * @return Bool - Whether or not the extension is loaded
- */
-function wfDl( $extension, $fileName = null ) {
- if ( extension_loaded( $extension ) ) {
- return true;
- }
-
- $canDl = false;
- if ( PHP_SAPI == 'cli' || PHP_SAPI == 'cgi' || PHP_SAPI == 'embed' ) {
- $canDl = ( function_exists( 'dl' ) && is_callable( 'dl' )
- && wfIniGetBool( 'enable_dl' ) && !wfIniGetBool( 'safe_mode' ) );
- }
-
- if ( $canDl ) {
- $fileName = $fileName ? $fileName : $extension;
- if ( wfIsWindows() ) {
- $fileName = 'php_' . $fileName;
- }
- wfSuppressWarnings();
- dl( $fileName . '.' . PHP_SHLIB_SUFFIX );
- wfRestoreWarnings();
- }
- return extension_loaded( $extension );
-}
-
/**
* Windows-compatible version of escapeshellarg()
* Windows doesn't recognise single-quotes in the shell, but the escapeshellarg()
$functions = explode( ',', ini_get( 'disable_functions' ) );
$functions = array_map( 'trim', $functions );
$functions = array_map( 'strtolower', $functions );
- if ( in_array( 'passthru', $functions ) ) {
- wfDebug( "passthru is in disabled_functions\n" );
- $disabled = 'passthru';
+ if ( in_array( 'proc_open', $functions ) ) {
+ wfDebug( "proc_open is in disabled_functions\n" );
+ $disabled = 'disabled';
}
}
}
* configuration if supported.
* @param string $cmd Command line, properly escaped for shell.
* @param &$retval null|Mixed optional, will receive the program's exit code.
- * (non-zero is usually failure)
+ * (non-zero is usually failure). If there is an error from
+ * read, select, or proc_open(), this will be set to -1.
* @param array $environ optional environment variables which should be
* added to the executed command environment.
* @param array $limits optional array with limits(filesize, memory, time, walltime)
* this overwrites the global wgShellMax* limits.
- * @return string collected stdout as a string (trailing newlines stripped)
+ * @param array $options Array of options:
+ * - duplicateStderr: Set this to true to duplicate stderr to stdout,
+ * including errors from limit.sh
+ *
+ * @return string collected stdout as a string
*/
-function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array() ) {
+function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array(), $options = array() ) {
global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime,
$wgMaxShellWallClockTime, $wgShellCgroup;
$retval = 1;
return $disabled == 'safemode' ?
'Unable to run external programs in safe mode.' :
- 'Unable to run external programs, passthru() is disabled.';
+ 'Unable to run external programs, proc_open() is disabled.';
}
+ $includeStderr = isset( $options['duplicateStderr'] ) && $options['duplicateStderr'];
+
wfInitShellLocale();
$envcmd = '';
}
$cmd = $envcmd . $cmd;
+ $useLogPipe = false;
if ( php_uname( 's' ) == 'Linux' ) {
$time = intval ( isset( $limits['time'] ) ? $limits['time'] : $wgMaxShellTime );
if ( isset( $limits['walltime'] ) ) {
$cmd = '/bin/bash ' . escapeshellarg( "$IP/includes/limit.sh" ) . ' ' .
escapeshellarg( $cmd ) . ' ' .
escapeshellarg(
+ "MW_INCLUDE_STDERR=" . ( $includeStderr ? '1' : '' ) . ';' .
"MW_CPU_LIMIT=$time; " .
'MW_CGROUP=' . escapeshellarg( $wgShellCgroup ) . '; ' .
"MW_MEM_LIMIT=$mem; " .
"MW_FILE_SIZE_LIMIT=$filesize; " .
- "MW_WALL_CLOCK_LIMIT=$wallTime"
+ "MW_WALL_CLOCK_LIMIT=$wallTime; " .
+ "MW_USE_LOG_PIPE=yes"
);
+ $useLogPipe = true;
+ } elseif ( $includeStderr ) {
+ $cmd .= ' 2>&1';
}
+ } elseif ( $includeStderr ) {
+ $cmd .= ' 2>&1';
}
wfDebug( "wfShellExec: $cmd\n" );
- $retval = 1; // error by default?
- ob_start();
- passthru( $cmd, $retval );
- $output = ob_get_contents();
- ob_end_clean();
+ $desc = array(
+ 0 => array( 'file', 'php://stdin', 'r' ),
+ 1 => array( 'pipe', 'w' ),
+ 2 => array( 'file', 'php://stderr', 'w' ) );
+ if ( $useLogPipe ) {
+ $desc[3] = array( 'pipe', 'w' );
+ }
+ $pipes = null;
+ $proc = proc_open( $cmd, $desc, $pipes );
+ if ( !$proc ) {
+ wfDebugLog( 'exec', "proc_open() failed: $cmd\n" );
+ $retval = -1;
+ return '';
+ }
+ $outBuffer = $logBuffer = '';
+ $emptyArray = array();
+ $status = false;
+ $logMsg = false;
+
+ // According to the documentation, it is possible for stream_select()
+ // to fail due to EINTR. I haven't managed to induce this in testing
+ // despite sending various signals. If it did happen, the error
+ // message would take the form:
+ //
+ // stream_select(): unable to select [4]: Interrupted system call (max_fd=5)
+ //
+ // where [4] is the value of the macro EINTR and "Interrupted system
+ // call" is string which according to the Linux manual is "possibly"
+ // localised according to LC_MESSAGES.
+ $eintr = defined( 'SOCKET_EINTR' ) ? SOCKET_EINTR : 4;
+ $eintrMessage = "stream_select(): unable to select [$eintr]";
+
+ // Build a table mapping resource IDs to pipe FDs to work around a
+ // PHP 5.3 issue in which stream_select() does not preserve array keys
+ // <https://bugs.php.net/bug.php?id=53427>.
+ $fds = array();
+ foreach ( $pipes as $fd => $pipe ) {
+ $fds[(int)$pipe] = $fd;
+ }
+
+ while ( true ) {
+ $status = proc_get_status( $proc );
+ if ( !$status['running'] ) {
+ break;
+ }
+ $status = false;
+
+ $readyPipes = $pipes;
- if ( $retval == 127 ) {
- wfDebugLog( 'exec', "Possibly missing executable file: $cmd\n" );
+ // Clear last error
+ @trigger_error( '' );
+ if ( @stream_select( $readyPipes, $emptyArray, $emptyArray, null ) === false ) {
+ $error = error_get_last();
+ if ( strncmp( $error['message'], $eintrMessage, strlen( $eintrMessage ) ) == 0 ) {
+ continue;
+ } else {
+ trigger_error( $error['message'], E_USER_WARNING );
+ $logMsg = $error['message'];
+ break;
+ }
+ }
+ foreach ( $readyPipes as $pipe ) {
+ $block = fread( $pipe, 65536 );
+ $fd = $fds[(int)$pipe];
+ if ( $block === '' ) {
+ // End of file
+ fclose( $pipes[$fd] );
+ unset( $pipes[$fd] );
+ if ( !$pipes ) {
+ break 2;
+ }
+ } elseif ( $block === false ) {
+ // Read error
+ $logMsg = "Error reading from pipe";
+ break 2;
+ } elseif ( $fd == 1 ) {
+ // From stdout
+ $outBuffer .= $block;
+ } elseif ( $fd == 3 ) {
+ // From log FD
+ $logBuffer .= $block;
+ if ( strpos( $block, "\n" ) !== false ) {
+ $lines = explode( "\n", $logBuffer );
+ $logBuffer = array_pop( $lines );
+ foreach ( $lines as $line ) {
+ wfDebugLog( 'exec', $line );
+ }
+ }
+ }
+ }
}
- return $output;
+
+ foreach ( $pipes as $pipe ) {
+ fclose( $pipe );
+ }
+
+ // Use the status previously collected if possible, since proc_get_status()
+ // just calls waitpid() which will not return anything useful the second time.
+ if ( $status === false ) {
+ $status = proc_get_status( $proc );
+ }
+
+ if ( $logMsg !== false ) {
+ // Read/select error
+ $retval = -1;
+ proc_close( $proc );
+ } elseif ( $status['signaled'] ) {
+ $logMsg = "Exited with signal {$status['termsig']}";
+ $retval = 128 + $status['termsig'];
+ proc_close( $proc );
+ } else {
+ if ( $status['running'] ) {
+ $retval = proc_close( $proc );
+ } else {
+ $retval = $status['exitcode'];
+ proc_close( $proc );
+ }
+ if ( $retval == 127 ) {
+ $logMsg = "Possibly missing executable file";
+ } elseif ( $retval >= 129 && $retval <= 192 ) {
+ $logMsg = "Probably exited with signal " . ( $retval - 128 );
+ }
+ }
+
+ if ( $logMsg !== false ) {
+ wfDebugLog( 'exec', "$logMsg: $cmd\n" );
+ }
+
+ return $outBuffer;
+}
+
+/**
+ * Execute a shell command, returning both stdout and stderr. Convenience
+ * 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 &$retval null|Mixed optional, will receive the program's exit code.
+ * (non-zero is usually failure)
+ * @param array $environ optional environment variables which should be
+ * added to the executed command environment.
+ * @param array $limits optional array with limits(filesize, memory, time, walltime)
+ * this overwrites the global wgShellMax* limits.
+ * @return string collected stdout and stderr as a string
+ */
+function wfShellExecWithStderr( $cmd, &$retval = null, $environ = array(), $limits = array() ) {
+ return wfShellExec( $cmd, $retval, $environ, $limits, array( 'duplicateStderr' => true ) );
}
/**
* This is useful for extensions which due to their nature are not kept in sync
* with releases
*
+ * Note: Due to the behavior of PHP's version_compare() which is used in this
+ * fuction, if you want to allow the 'wmf' development versions add a 'c' (or
+ * any single letter other than 'a', 'b' or 'p') as a post-fix to your
+ * targeted version number. For example if you wanted to allow any variation
+ * of 1.22 use `wfUseMW( '1.22c' )`. Using an 'a' or 'b' instead of 'c' will
+ * not result in the same comparison due to the internal logic of
+ * version_compare().
+ *
* @see perldoc -f use
*
* @param $req_ver Mixed: the version to check, can be a string, an integer, or