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