1 /* 2 * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. 3 * Copyright (c) 2015 SAP SE. All rights reserved. 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * This code is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU General Public License version 2 only, as 8 * published by the Free Software Foundation. Oracle designates this 9 * particular file as subject to the "Classpath" exception as provided 10 * by Oracle in the LICENSE file that accompanied this code. 11 * 12 * This code is distributed in the hope that it will be useful, but WITHOUT 13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 * version 2 for more details (a copy is included in the LICENSE file that 16 * accompanied this code). 17 * 18 * You should have received a copy of the GNU General Public License version 19 * 2 along with this work; if not, write to the Free Software Foundation, 20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 21 * 22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 23 * or visit www.oracle.com if you need additional information or have any 24 * questions. 25 */ 26 package sun.tools.attach; 27 28 import com.sun.tools.attach.AttachOperationFailedException; 29 import com.sun.tools.attach.AgentLoadException; 30 import com.sun.tools.attach.AttachNotSupportedException; 31 import com.sun.tools.attach.spi.AttachProvider; 32 33 import java.io.InputStream; 34 import java.io.IOException; 35 import java.io.File; 36 37 // Based on linux/classes/sun/tools/attach/VirtualMachineImpl.java. 38 39 /* 40 * Aix implementation of HotSpotVirtualMachine 41 */ 42 public class VirtualMachineImpl extends HotSpotVirtualMachine { 43 // "/tmp" is used as a global well-known location for the files 44 // .java_pid<pid>. and .attach_pid<pid>. It is important that this 45 // location is the same for all processes, otherwise the tools 46 // will not be able to find all Hotspot processes. 47 // Any changes to this needs to be synchronized with HotSpot. 48 private static final String tmpdir = "/tmp"; 49 50 // The patch to the socket file created by the target VM 51 String path; 52 53 /** 54 * Attaches to the target VM 55 */ 56 VirtualMachineImpl(AttachProvider provider, String vmid) 57 throws AttachNotSupportedException, IOException 58 { 59 super(provider, vmid); 60 61 // This provider only understands pids 62 int pid; 63 try { 64 pid = Integer.parseInt(vmid); 65 } catch (NumberFormatException x) { 66 throw new AttachNotSupportedException("Invalid process identifier"); 67 } 68 69 // Find the socket file. If not found then we attempt to start the 70 // attach mechanism in the target VM by sending it a QUIT signal. 71 // Then we attempt to find the socket file again. 72 path = findSocketFile(pid); 73 if (path == null) { 74 File f = createAttachFile(pid); 75 try { 76 sendQuitTo(pid); 77 78 // give the target VM time to start the attach mechanism 79 int i = 0; 80 long delay = 200; 81 int retries = (int)(attachTimeout() / delay); 82 do { 83 try { 84 Thread.sleep(delay); 85 } catch (InterruptedException x) { } 86 path = findSocketFile(pid); 87 i++; 88 } while (i <= retries && path == null); 89 if (path == null) { 90 throw new AttachNotSupportedException( 91 "Unable to open socket file: target process not responding " + 92 "or HotSpot VM not loaded"); 93 } 94 } finally { 95 f.delete(); 96 } 97 } 98 99 // Check that the file owner/permission to avoid attaching to 100 // bogus process 101 checkPermissions(path); 102 103 // Check that we can connect to the process 104 // - this ensures we throw the permission denied error now rather than 105 // later when we attempt to enqueue a command. 106 int s = socket(); 107 try { 108 connect(s, path); 109 } finally { 110 close(s); 111 } 112 } 113 114 /** 115 * Detach from the target VM 116 */ 117 public void detach() throws IOException { 118 synchronized (this) { 119 if (this.path != null) { 120 this.path = null; 121 } 122 } 123 } 124 125 // protocol version 126 private final static String PROTOCOL_VERSION = "1"; 127 128 // known errors 129 private final static int ATTACH_ERROR_BADVERSION = 101; 130 131 /** 132 * Execute the given command in the target VM. 133 */ 134 InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException { 135 assert args.length <= 3; // includes null 136 137 // did we detach? 138 String p; 139 synchronized (this) { 140 if (this.path == null) { 141 throw new IOException("Detached from target VM"); 142 } 143 p = this.path; 144 } 145 146 // create UNIX socket 147 int s = socket(); 148 149 // connect to target VM 150 try { 151 connect(s, p); 152 } catch (IOException x) { 153 close(s); 154 throw x; 155 } 156 157 IOException ioe = null; 158 159 // connected - write request 160 // <ver> <cmd> <args...> 161 try { 162 writeString(s, PROTOCOL_VERSION); 163 writeString(s, cmd); 164 165 for (int i=0; i<3; i++) { 166 if (i < args.length && args[i] != null) { 167 writeString(s, (String)args[i]); 168 } else { 169 writeString(s, ""); 170 } 171 } 172 } catch (IOException x) { 173 ioe = x; 174 } 175 176 177 // Create an input stream to read reply 178 SocketInputStream sis = new SocketInputStream(s); 179 180 // Read the command completion status 181 int completionStatus; 182 try { 183 completionStatus = readInt(sis); 184 } catch (IOException x) { 185 sis.close(); 186 if (ioe != null) { 187 throw ioe; 188 } else { 189 throw x; 190 } 191 } 192 193 if (completionStatus != 0) { 194 // read from the stream and use that as the error message 195 String message = readErrorMessage(sis); 196 sis.close(); 197 198 // In the event of a protocol mismatch then the target VM 199 // returns a known error so that we can throw a reasonable 200 // error. 201 if (completionStatus == ATTACH_ERROR_BADVERSION) { 202 throw new IOException("Protocol mismatch with target VM"); 203 } 204 205 // Special-case the "load" command so that the right exception is 206 // thrown. 207 if (cmd.equals("load")) { 208 throw new AgentLoadException("Failed to load agent library"); 209 } else { 210 if (message == null) { 211 throw new AttachOperationFailedException("Command failed in target VM"); 212 } else { 213 throw new AttachOperationFailedException(message); 214 } 215 } 216 } 217 218 // Return the input stream so that the command output can be read 219 return sis; 220 } 221 222 /* 223 * InputStream for the socket connection to get target VM 224 */ 225 private class SocketInputStream extends InputStream { 226 int s; 227 228 public SocketInputStream(int s) { 229 this.s = s; 230 } 231 232 public synchronized int read() throws IOException { 233 byte b[] = new byte[1]; 234 int n = this.read(b, 0, 1); 235 if (n == 1) { 236 return b[0] & 0xff; 237 } else { 238 return -1; 239 } 240 } 241 242 public synchronized int read(byte[] bs, int off, int len) throws IOException { 243 if ((off < 0) || (off > bs.length) || (len < 0) || 244 ((off + len) > bs.length) || ((off + len) < 0)) { 245 throw new IndexOutOfBoundsException(); 246 } else if (len == 0) 247 return 0; 248 249 return VirtualMachineImpl.read(s, bs, off, len); 250 } 251 252 public void close() throws IOException { 253 VirtualMachineImpl.close(s); 254 } 255 } 256 257 // Return the socket file for the given process. 258 private String findSocketFile(int pid) { 259 File f = new File(tmpdir, ".java_pid" + pid); 260 if (!f.exists()) { 261 return null; 262 } 263 return f.getPath(); 264 } 265 266 // On Solaris/Linux/Aix a simple handshake is used to start the attach mechanism 267 // if not already started. The client creates a .attach_pid<pid> file in the 268 // target VM's working directory (or temp directory), and the SIGQUIT handler 269 // checks for the file. 270 private File createAttachFile(int pid) throws IOException { 271 String fn = ".attach_pid" + pid; 272 String path = "/proc/" + pid + "/cwd/" + fn; 273 File f = new File(path); 274 try { 275 f.createNewFile(); 276 } catch (IOException x) { 277 f = new File(tmpdir, fn); 278 f.createNewFile(); 279 } 280 return f; 281 } 282 283 /* 284 * Write/sends the given to the target VM. String is transmitted in 285 * UTF-8 encoding. 286 */ 287 private void writeString(int fd, String s) throws IOException { 288 if (s.length() > 0) { 289 byte b[]; 290 try { 291 b = s.getBytes("UTF-8"); 292 } catch (java.io.UnsupportedEncodingException x) { 293 throw new InternalError(x); 294 } 295 VirtualMachineImpl.write(fd, b, 0, b.length); 296 } 297 byte b[] = new byte[1]; 298 b[0] = 0; 299 write(fd, b, 0, 1); 300 } 301 302 303 //-- native methods 304 305 static native void sendQuitTo(int pid) throws IOException; 306 307 static native void checkPermissions(String path) throws IOException; 308 309 static native int socket() throws IOException; 310 311 static native void connect(int fd, String path) throws IOException; 312 313 static native void close(int fd) throws IOException; 314 315 static native int read(int fd, byte buf[], int off, int bufLen) throws IOException; 316 317 static native void write(int fd, byte buf[], int off, int bufLen) throws IOException; 318 319 static { 320 System.loadLibrary("attach"); 321 } 322 }