/******************************************************************************* * Copyright (c) 2008, 2009 Nokia Corporation. * All rights reserved. This fProgram 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: * Nokia - initial version. Sep 28, 2008 *******************************************************************************/ package org.eclipse.cdt.examples.dsf.pda.service; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; import com.ibm.icu.text.MessageFormat; import java.util.ArrayList; import java.util.Hashtable; import java.util.List; import org.eclipse.cdt.dsf.concurrent.RequestMonitor; import org.eclipse.cdt.dsf.concurrent.Sequence; import org.eclipse.cdt.dsf.concurrent.ThreadSafe; import org.eclipse.cdt.dsf.concurrent.Sequence.Step; import org.eclipse.cdt.dsf.service.AbstractDsfService; import org.eclipse.cdt.dsf.service.DsfSession; import org.eclipse.cdt.examples.dsf.pda.PDAPlugin; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.Launch; import org.osgi.framework.BundleContext; /** * Service that manages the backend process: starting the process * and monitoring for its shutdown. */ public class PDABackend extends AbstractDsfService { private int fRequestPort; private int fEventPort; @ThreadSafe private OutputStream fRequestOutputStream; @ThreadSafe private InputStream fRequestInputStream; @ThreadSafe private InputStream fEventInputStream; private String fProgram; private Process fBackendProcess; private String fBackendProcessName; /** * * @param session * @param launch - can be null, e.g. for JUnit test. * @param program - can be a full path or a workspace resource path, must denote an existing file. */ public PDABackend(DsfSession session, Launch launch, String program) { super(session); fProgram = program; } @Override protected BundleContext getBundleContext() { return PDAPlugin.getBundleContext(); } public Process getProcess() { return fBackendProcess; } public String getProcessName() { return fBackendProcessName; } public String getPorgramName() { return fProgram; } @Override public void initialize(final RequestMonitor rm) { super.initialize(new RequestMonitor(getExecutor(), rm) { @Override protected void handleSuccess() { doInitialize(rm); } }); } private void doInitialize(final RequestMonitor requestMonitor) { final Sequence.Step[] initializeSteps = new Sequence.Step[] { new Step() { // Launch the back end debugger process. @Override public void execute(final RequestMonitor rm) { new Job("Start PDA Virtual Machine") { @Override protected IStatus run(IProgressMonitor monitor) { try { fBackendProcess = launchPDABackendDebugger(); } catch (CoreException e) { rm.setStatus(e.getStatus()); } rm.done(); return Status.OK_STATUS; } }.schedule(); } @Override public void rollBack(RequestMonitor rm) { if (fBackendProcess != null) fBackendProcess.destroy(); rm.done(); } }, new Step() { @Override public void execute(final RequestMonitor rm) { // To avoid blocking the DSF dispatch thread use a job to initialize communication sockets. new Job("PDA Socket Initialize") { @Override protected IStatus run(IProgressMonitor monitor) { try { // give interpreter a chance to start try { Thread.sleep(1000); } catch (InterruptedException e) { } Socket socket = new Socket("localhost", fRequestPort); fRequestOutputStream = socket.getOutputStream(); fRequestInputStream = socket.getInputStream(); // give interpreter a chance to open next socket try { Thread.sleep(1000); } catch (InterruptedException e) { } socket = new Socket("localhost", fEventPort); fEventInputStream = socket.getInputStream(); } catch (UnknownHostException e) { rm.setStatus(new Status( IStatus.ERROR, PDAPlugin.PLUGIN_ID, REQUEST_FAILED, "Unable to connect to PDA VM", e)); } catch (IOException e) { rm.setStatus(new Status( IStatus.ERROR, PDAPlugin.PLUGIN_ID, REQUEST_FAILED, "Unable to connect to PDA VM", e)); } rm.done(); return Status.OK_STATUS; } }.schedule(); } }, new Step() { // register the service @Override public void execute(RequestMonitor rm) { // Register this service register(new String[] { PDABackend.class.getName() }, new Hashtable<String, String>()); rm.done(); } }, }; Sequence startupSequence = new Sequence(getExecutor(), requestMonitor) { @Override public Step[] getSteps() { return initializeSteps; } }; getExecutor().execute(startupSequence); } /** * Returns a free port number on localhost, or -1 if unable to find a free port. */ public static int findFreePort() { ServerSocket socket= null; try { socket= new ServerSocket(0); return socket.getLocalPort(); } catch (IOException e) { } finally { if (socket != null) { try { socket.close(); } catch (IOException e) { } } } return -1; } private void abort(String message, Throwable e) throws CoreException { throw new CoreException(new Status(IStatus.ERROR, PDAPlugin.PLUGIN_ID, 0, message, e)); } private Process launchPDABackendDebugger() throws CoreException { List<String> commandList = new ArrayList<String>(); // Get Java VM path String javaVMHome = System.getProperty("java.home"); String javaVMExec = javaVMHome + File.separatorChar + "bin" + File.separatorChar + "java"; if (File.separatorChar == '\\') { javaVMExec += ".exe"; } File exe = new File(javaVMExec); if (!exe.exists()) { abort(MessageFormat.format("Specified java VM executable {0} does not exist.", new Object[]{javaVMExec}), null); } fBackendProcessName = javaVMExec; commandList.add(javaVMExec); commandList.add("-cp"); try { commandList.add( File.pathSeparator + PDAPlugin.getFileInPlugin(new Path("bin")) + File.pathSeparator + new File(Platform.asLocalURL(PDAPlugin.getDefault().getDescriptor().getInstallURL()).getFile())); } catch (IOException e) { } commandList.add("org.eclipse.cdt.examples.pdavm.PDAVirtualMachine"); String absolutePath = fProgram; // check if fProgram is already a full path of an existing file // Note if "fProgram" is workspace resource path like /ProjectName/file.pda, we should not // change it to absolute path, otherwise the breakpoints in the PDA file won't work. // See PDABreakpoints.doInsertBreakpoint() for more. File f = new File(fProgram); if (! f.exists()) { // Try to locate it in workspace IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(fProgram)); if (file.exists()) absolutePath = file.getLocation().toPortableString(); else abort(MessageFormat.format("PDA program {0} does not exist.", new Object[] {file.getFullPath().toPortableString()}), null); } commandList.add(absolutePath); fRequestPort = findFreePort(); fEventPort = findFreePort(); if (fRequestPort == -1 || fEventPort == -1) { abort("Unable to find free port", null); } // Add debug arguments - i.e. '-debug fRequestPort fEventPort' commandList.add("-debug"); commandList.add("" + fRequestPort); commandList.add("" + fEventPort); // Launch the perl process. String[] commandLine = commandList.toArray(new String[commandList.size()]); PDAPlugin.debug("Start PDA Virtual Machine:\n" + commandList); Process process = DebugPlugin.exec(commandLine, null); return process; } @Override public void shutdown(final RequestMonitor rm) { fBackendProcess.destroy(); try { if (fRequestInputStream != null) fRequestInputStream.close(); if (fRequestOutputStream != null) fRequestOutputStream.close(); if (fEventInputStream != null) fEventInputStream.close(); } catch (IOException e) { // ignore } unregister(); rm.done(); } /* * =========== Following are PDA debugger specific ==================== * * Caller should make sure these are called after the PDABackend is initialized. */ public OutputStream getRequestOutputStream() { return fRequestOutputStream; } public InputStream getRequestInputStream() { return fRequestInputStream; } public InputStream getEventInputStream() { return fEventInputStream; } }