/*******************************************************************************
* Copyright (c) 2006,2012 IBM Corporation.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - Jeff Briggs, Henry Hughes, Ryan Morse
*******************************************************************************/
package org.eclipse.linuxtools.internal.systemtap.ui.ide.structures.tparsers;
import java.io.File;
import java.io.IOException;
import java.net.ConnectException;
import java.text.MessageFormat;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.IDEPlugin;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.StringOutputStream;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.preferences.EnvironmentVariablesPreferencePage;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.preferences.IDEPreferenceConstants;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.structures.Messages;
import org.eclipse.linuxtools.systemtap.structures.runnable.StringStreamGobbler;
import org.eclipse.linuxtools.systemtap.ui.consolelog.internal.ConsoleLogPlugin;
import org.eclipse.linuxtools.systemtap.ui.consolelog.preferences.ConsoleLogPreferenceConstants;
import org.eclipse.linuxtools.tools.launch.core.factory.LinuxtoolsProcessFactory;
import org.eclipse.linuxtools.tools.launch.core.factory.RuntimeProcessFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.PreferencesUtil;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.JSchException;
/**
* Runs stap -vp1 & stap -up2 in order to get all of the probes/functions
* that are defined in the tapsets. Builds probeAlias and function trees
* with the values obtained from the tapsets.
*
* Ugly code is a result of two issues with getting stap output. First,
* many tapsets do not work under stap -up2. Second since the output
* is not a regular language, we can't create a nice lexor/parser combination
* to do everything nicely.
* @author Ryan Morse
*/
public abstract class TapsetParser extends Job {
private static AtomicBoolean displayingCredentialDialog = new AtomicBoolean(false);
protected TapsetParser(String jobTitle) {
super(jobTitle);
}
@Override
protected void canceling() {
Thread thread = getThread();
if (thread != null) {
thread.interrupt();
}
}
/**
* Generates a {@link Status} with the provided severity,
* and given an appropriate error message.
*/
protected IStatus createStatus(int severity) {
String message;
IPreferenceStore ps = IDEPlugin.getDefault().getPreferenceStore();
switch (severity) {
case IStatus.ERROR:
if (ps.getBoolean(IDEPreferenceConstants.P_REMOTE_PROBES)) {
IPreferenceStore p = ConsoleLogPlugin.getDefault().getPreferenceStore();
message = MessageFormat.format(
Messages.TapsetParser_ErrorCannotRunRemoteStap,
p.getString(ConsoleLogPreferenceConstants.SCP_USER),
p.getString(ConsoleLogPreferenceConstants.HOST_NAME));
} else {
message = Messages.TapsetParser_ErrorCannotRunStap;
}
break;
default:
message = ""; //$NON-NLS-1$
}
return createStatus(severity, message);
}
/**
* Generates a {@link Status} with the provided severity and message.
*/
protected IStatus createStatus(int severity, String message) {
return new Status(severity, IDEPlugin.PLUGIN_ID, message);
}
/**
* Runs stap with the given options and returns the output generated,
* or <code>null</code> if the case of an error.
* @param options String[] of any optional parameters to pass to stap
* @param probe String containing the script to run stap on,
* or <code>null</code> for scriptless commands
* @param getErrors Set this to <code>true</code> if the script's error
* stream contents should be returned instead of its standard output
* @return The output generated from the stap run, or <code>null</code>
* in the case of an error, or an empty string if the run was canceled.
*/
protected String runStap(String[] options, String probe, boolean getErrors) {
String[] args = null;
String[] tapsets = IDEPlugin.getDefault().getPreferenceStore()
.getString(IDEPreferenceConstants.P_TAPSETS).split(File.pathSeparator);
boolean noTapsets = tapsets[0].trim().isEmpty();
boolean noOptions = options[0].trim().isEmpty();
final boolean remote = IDEPlugin.getDefault().getPreferenceStore().getBoolean(IDEPreferenceConstants.P_REMOTE_PROBES);
int size = probe != null ? 2 : 1;
if (!noTapsets) {
size += tapsets.length<<1;
}
if (!noOptions) {
size += options.length;
}
args = new String[size];
args[0] = "stap"; //$NON-NLS-1$
if (probe != null) {
// Workaround for the fact that remote & local execution methods use string args differently
args[size - 1] = !remote ? probe : '\'' + probe + '\'';
}
//Add extra tapset directories
if (!noTapsets) {
for (int i = 0; i < tapsets.length; i++) {
args[1 + 2*i] = "-I"; //$NON-NLS-1$
args[2 + 2*i] = tapsets[i];
}
}
if (!noOptions) {
for (int i = 0, s = noTapsets ? 1 : 1 + tapsets.length*2; i < options.length; i++) {
args[s + i] = options[i];
}
}
if (!remote) {
try {
return runLocalStap(args, getErrors);
} catch (IOException e) {
return null;
}
} else {
return runRemoteStap(args, getErrors);
}
}
/**
* Return an {@link IStatus} severity constant for the result of a call to
* {@link TapsetParser#runStap(String[], String, boolean)}.
* @param result The output generated by a call to {@link #runStap(String[], String, boolean)}.
*/
protected int verifyRunResult(String result) {
if (result == null) {
return IStatus.ERROR;
} else if (result.isEmpty()) {
return IStatus.CANCEL;
}
return IStatus.OK;
}
private String runLocalStap(String[] args, boolean getErrors) throws IOException {
Process process = RuntimeProcessFactory.getFactory().exec(
args, EnvironmentVariablesPreferencePage.getEnvironmentVariables(), null);
// An IOException should be thrown if there's a problem with exec, but to cover possible error
// cases where an exception is not thrown (process is null), return null to signify an error.
if (process == null) {
return null;
}
StringStreamGobbler gobbler = new StringStreamGobbler(process.getInputStream());
StringStreamGobbler egobbler = null;
gobbler.start();
if (getErrors) {
egobbler = new StringStreamGobbler(process.getErrorStream());
egobbler.start();
}
try {
process.waitFor();
} catch (InterruptedException e) {
process.destroy();
}
gobbler.stop();
if (egobbler == null) {
return gobbler.getOutput().toString();
} else {
egobbler.stop();
return egobbler.getOutput().toString();
}
}
private String runRemoteStap(String[] args, boolean getErrors) {
int attemptsLeft = 3;
while (true) {
try {
if (Thread.currentThread().isInterrupted()) {
return ""; //$NON-NLS-1$
}
return runRemoteStapAttempt(args, getErrors);
} catch (JSchException e) {
if (!(e.getCause() instanceof ConnectException) || --attemptsLeft == 0) {
askIfEditCredentials();
// Return empty string instead of null to act as "cancel" signal, to
// avoid showing another error dialog on top of the credential edit dialog.
return ""; //$NON-NLS-1$
}
}
}
}
private String runRemoteStapAttempt(String[] args, boolean getErrors) throws JSchException {
StringOutputStream str = new StringOutputStream();
StringOutputStream strErr = new StringOutputStream();
IPreferenceStore p = ConsoleLogPlugin.getDefault().getPreferenceStore();
String user = p.getString(ConsoleLogPreferenceConstants.SCP_USER);
String host = p.getString(ConsoleLogPreferenceConstants.HOST_NAME);
String password = p.getString(ConsoleLogPreferenceConstants.SCP_PASSWORD);
int port = p.getInt(ConsoleLogPreferenceConstants.PORT_NUMBER);
Channel channel = LinuxtoolsProcessFactory.execRemoteAndWait(args, str, strErr, user, host, password,
port, EnvironmentVariablesPreferencePage.getEnvironmentVariables());
if (channel == null) {
return null;
}
channel.getSession().disconnect();
channel.disconnect();
return (!getErrors ? str : strErr).toString();
}
private void askIfEditCredentials() {
if (displayingCredentialDialog.compareAndSet(false, true)) {
PlatformUI.getWorkbench().getDisplay().asyncExec(() -> {
Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
MessageBox dialog = new MessageBox(shell, SWT.ICON_QUESTION | SWT.YES | SWT.NO);
dialog.setText(Messages.TapsetParser_RemoteCredentialErrorTitle);
dialog.setMessage(Messages.TapsetParser_RemoteCredentialErrorMessage);
if (dialog.open() == SWT.YES) {
String pageID = "org.eclipse.linuxtools.systemtap.prefs.consoleLog"; //$NON-NLS-1$
PreferencesUtil.createPreferenceDialogOn(shell, pageID, new String[]{pageID}, null).open();
}
displayingCredentialDialog.set(false);
});
}
}
}