001: /*
002: * Copyright 2005-2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025: package sun.tools.attach;
026:
027: import com.sun.tools.attach.VirtualMachine;
028: import com.sun.tools.attach.AgentLoadException;
029: import com.sun.tools.attach.AttachNotSupportedException;
030: import com.sun.tools.attach.spi.AttachProvider;
031: import java.io.InputStream;
032: import java.io.IOException;
033: import java.io.File;
034: import java.util.Properties;
035:
036: /*
037: * Linux implementation of HotSpotVirtualMachine
038: */
039: public class LinuxVirtualMachine extends HotSpotVirtualMachine {
040:
041: // Indicates if this machine uses the old LinuxThreads
042: static boolean isLinuxThreads;
043:
044: // The patch to the socket file created by the target VM
045: String path;
046:
047: /**
048: * Attaches to the target VM
049: */
050: LinuxVirtualMachine(AttachProvider provider, String vmid)
051: throws AttachNotSupportedException, IOException {
052: super (provider, vmid);
053:
054: // This provider only understands pids
055: int pid;
056: try {
057: pid = Integer.parseInt(vmid);
058: } catch (NumberFormatException x) {
059: throw new AttachNotSupportedException(
060: "Invalid process identifier");
061: }
062:
063: // Find the socket file. If not found then we attempt to start the
064: // attach mechanism in the target VM by sending it a QUIT signal.
065: // Then we attempt to find the socket file again.
066: path = findSocketFile(pid);
067: if (path == null) {
068: File f = createAttachFile(pid);
069: try {
070: // On LinuxThreads each thread is a process and we don't have the
071: // pid of the VMThread which has SIGQUIT unblocked. To workaround
072: // this we get the pid of the "manager thread" that is created
073: // by the first call to pthread_create. This is parent of all
074: // threads (except the initial thread).
075: if (isLinuxThreads) {
076: int mpid;
077: try {
078: mpid = getLinuxThreadsManager(pid);
079: } catch (IOException x) {
080: throw new AttachNotSupportedException(x
081: .getMessage());
082: }
083: assert (mpid >= 1);
084: sendQuitToChildrenOf(mpid);
085: } else {
086: sendQuitTo(pid);
087: }
088:
089: // give the target VM time to start the attach mechanism
090: int i = 0;
091: long delay = 200;
092: int retries = (int) (attachTimeout() / delay);
093: do {
094: try {
095: Thread.sleep(delay);
096: } catch (InterruptedException x) {
097: }
098: path = findSocketFile(pid);
099: i++;
100: } while (i <= retries && path == null);
101: if (path == null) {
102: throw new AttachNotSupportedException(
103: "Unable to open socket file: target process not responding "
104: + "or HotSpot VM not loaded");
105: }
106: } finally {
107: f.delete();
108: }
109: }
110:
111: // Check that the file owner/permission to avoid attaching to
112: // bogus process
113: checkPermissions(path);
114:
115: // Check that we can connect to the process
116: // - this ensures we throw the permission denied error now rather than
117: // later when we attempt to enqueue a command.
118: int s = socket();
119: try {
120: connect(s, path);
121: } finally {
122: close(s);
123: }
124: }
125:
126: /**
127: * Detach from the target VM
128: */
129: public void detach() throws IOException {
130: synchronized (this ) {
131: if (this .path != null) {
132: this .path = null;
133: }
134: }
135: }
136:
137: // protocol version
138: private final static String PROTOCOL_VERSION = "1";
139:
140: // known errors
141: private final static int ATTACH_ERROR_BADVERSION = 101;
142:
143: /**
144: * Execute the given command in the target VM.
145: */
146: InputStream execute(String cmd, Object... args)
147: throws AgentLoadException, IOException {
148: assert args.length <= 3; // includes null
149:
150: // did we detach?
151: String p;
152: synchronized (this ) {
153: if (this .path == null) {
154: throw new IOException("Detached from target VM");
155: }
156: p = this .path;
157: }
158:
159: // create UNIX socket
160: int s = socket();
161:
162: // connect to target VM
163: try {
164: connect(s, p);
165: } catch (IOException x) {
166: close(s);
167: throw x;
168: }
169:
170: IOException ioe = null;
171:
172: // connected - write request
173: // <ver> <cmd> <args...>
174: try {
175: writeString(s, PROTOCOL_VERSION);
176: writeString(s, cmd);
177:
178: for (int i = 0; i < 3; i++) {
179: if (i < args.length && args[i] != null) {
180: writeString(s, (String) args[i]);
181: } else {
182: writeString(s, "");
183: }
184: }
185: } catch (IOException x) {
186: ioe = x;
187: }
188:
189: // Create an input stream to read reply
190: SocketInputStream sis = new SocketInputStream(s);
191:
192: // Read the command completion status
193: int completionStatus;
194: try {
195: completionStatus = readInt(sis);
196: } catch (IOException x) {
197: sis.close();
198: if (ioe != null) {
199: throw ioe;
200: } else {
201: throw x;
202: }
203: }
204:
205: if (completionStatus != 0) {
206: sis.close();
207:
208: // In the event of a protocol mismatch then the target VM
209: // returns a known error so that we can throw a reasonable
210: // error.
211: if (completionStatus == ATTACH_ERROR_BADVERSION) {
212: throw new IOException(
213: "Protocol mismatch with target VM");
214: }
215:
216: // Special-case the "load" command so that the right exception is
217: // thrown.
218: if (cmd.equals("load")) {
219: throw new AgentLoadException(
220: "Failed to load agent library");
221: } else {
222: throw new IOException("Command failed in target VM");
223: }
224: }
225:
226: // Return the input stream so that the command output can be read
227: return sis;
228: }
229:
230: /*
231: * InputStream for the socket connection to get target VM
232: */
233: private class SocketInputStream extends InputStream {
234: int s;
235:
236: public SocketInputStream(int s) {
237: this .s = s;
238: }
239:
240: public synchronized int read() throws IOException {
241: byte b[] = new byte[1];
242: int n = this .read(b, 0, 1);
243: if (n == 1) {
244: return b[0] & 0xff;
245: } else {
246: return -1;
247: }
248: }
249:
250: public synchronized int read(byte[] bs, int off, int len)
251: throws IOException {
252: if ((off < 0) || (off > bs.length) || (len < 0)
253: || ((off + len) > bs.length) || ((off + len) < 0)) {
254: throw new IndexOutOfBoundsException();
255: } else if (len == 0)
256: return 0;
257:
258: return LinuxVirtualMachine.read(s, bs, off, len);
259: }
260:
261: public void close() throws IOException {
262: LinuxVirtualMachine.close(s);
263: }
264: }
265:
266: // Return the socket file for the given process.
267: // Checks working directory of process for .java_pid<pid>. If not
268: // found it looks in /tmp.
269: private String findSocketFile(int pid) {
270: // First check for a .java_pid<pid> file in the working directory
271: // of the target process
272: String fn = ".java_pid" + pid;
273: String path = "/proc/" + pid + "/cwd/" + fn;
274: File f = new File(path);
275: if (!f.exists()) {
276: // Not found, so try /tmp
277: path = "/tmp/" + fn;
278: f = new File(path);
279: if (!f.exists()) {
280: return null; // not found
281: }
282: }
283: return path;
284: }
285:
286: // On Solaris/Linux a simple handshake is used to start the attach mechanism
287: // if not already started. The client creates a .attach_pid<pid> file in the
288: // target VM's working directory (or /tmp), and the SIGQUIT handler checks
289: // for the file.
290: private File createAttachFile(int pid) throws IOException {
291: String fn = ".attach_pid" + pid;
292: String path = "/proc/" + pid + "/cwd/" + fn;
293: File f = new File(path);
294: try {
295: f.createNewFile();
296: } catch (IOException x) {
297: path = "/tmp/" + fn;
298: f = new File(path);
299: f.createNewFile();
300: }
301: return f;
302: }
303:
304: /*
305: * Write/sends the given to the target VM. String is transmitted in
306: * UTF-8 encoding.
307: */
308: private void writeString(int fd, String s) throws IOException {
309: if (s.length() > 0) {
310: byte b[];
311: try {
312: b = s.getBytes("UTF-8");
313: } catch (java.io.UnsupportedEncodingException x) {
314: throw new InternalError();
315: }
316: LinuxVirtualMachine.write(fd, b, 0, b.length);
317: }
318: byte b[] = new byte[1];
319: b[0] = 0;
320: write(fd, b, 0, 1);
321: }
322:
323: //-- native methods
324:
325: static native boolean isLinuxThreads();
326:
327: static native int getLinuxThreadsManager(int pid)
328: throws IOException;
329:
330: static native void sendQuitToChildrenOf(int pid) throws IOException;
331:
332: static native void sendQuitTo(int pid) throws IOException;
333:
334: static native void checkPermissions(String path) throws IOException;
335:
336: static native int socket() throws IOException;
337:
338: static native void connect(int fd, String path) throws IOException;
339:
340: static native void close(int fd) throws IOException;
341:
342: static native int read(int fd, byte buf[], int off, int bufLen)
343: throws IOException;
344:
345: static native void write(int fd, byte buf[], int off, int bufLen)
346: throws IOException;
347:
348: static {
349: System.loadLibrary("attach");
350: isLinuxThreads = isLinuxThreads();
351: }
352: }
|