--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/src/os/solaris/vm/attachListener_solaris.cpp Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,682 @@
+/*
+ * Copyright 2005-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ *
+ */
+
+# include "incls/_precompiled.incl"
+# include "incls/_attachListener_solaris.cpp.incl"
+
+#include <door.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+// stropts.h uses STR in stream ioctl defines
+#undef STR
+#include <stropts.h>
+#undef STR
+#define STR(a) #a
+
+// The attach mechanism on Solaris is implemented using the Doors IPC
+// mechanism. The first tool to attempt to attach causes the attach
+// listener thread to startup. This thread creats a door that is
+// associated with a function that enqueues an operation to the attach
+// listener. The door is attached to a file in the file system so that
+// client (tools) can locate it. To enqueue an operation to the VM the
+// client calls through the door which invokes the enqueue function in
+// this process. The credentials of the client are checked and if the
+// effective uid matches this process then the operation is enqueued.
+// When an operation completes the attach listener is required to send the
+// operation result and any result data to the client. In this implementation
+// the result is returned via a UNIX domain socket. A pair of connected
+// sockets (socketpair) is created in the enqueue function and the file
+// descriptor for one of the sockets is returned to the client as the
+// return from the door call. The other end is retained in this process.
+// When the operation completes the result is sent to the client and
+// the socket is closed.
+
+// forward reference
+class SolarisAttachOperation;
+
+class SolarisAttachListener: AllStatic {
+ private:
+
+ // the path to which we attach the door file descriptor
+ static char _door_path[PATH_MAX+1];
+ static volatile bool _has_door_path;
+
+ // door descriptor returned by door_create
+ static int _door_descriptor;
+
+ static void set_door_path(char* path) {
+ if (path == NULL) {
+ _has_door_path = false;
+ } else {
+ strncpy(_door_path, path, PATH_MAX);
+ _door_path[PATH_MAX] = '\0'; // ensure it's nul terminated
+ _has_door_path = true;
+ }
+ }
+
+ static void set_door_descriptor(int dd) { _door_descriptor = dd; }
+
+ // mutex to protect operation list
+ static mutex_t _mutex;
+
+ // semaphore to wakeup listener thread
+ static sema_t _wakeup;
+
+ static mutex_t* mutex() { return &_mutex; }
+ static sema_t* wakeup() { return &_wakeup; }
+
+ // enqueued operation list
+ static SolarisAttachOperation* _head;
+ static SolarisAttachOperation* _tail;
+
+ static SolarisAttachOperation* head() { return _head; }
+ static void set_head(SolarisAttachOperation* head) { _head = head; }
+
+ static SolarisAttachOperation* tail() { return _tail; }
+ static void set_tail(SolarisAttachOperation* tail) { _tail = tail; }
+
+ // create the door
+ static int create_door();
+
+ public:
+ enum {
+ ATTACH_PROTOCOL_VER = 1 // protocol version
+ };
+ enum {
+ ATTACH_ERROR_BADREQUEST = 100, // error code returned by
+ ATTACH_ERROR_BADVERSION = 101, // the door call
+ ATTACH_ERROR_RESOURCE = 102,
+ ATTACH_ERROR_INTERNAL = 103,
+ ATTACH_ERROR_DENIED = 104
+ };
+
+ // initialize the listener
+ static int init();
+
+ static bool has_door_path() { return _has_door_path; }
+ static char* door_path() { return _door_path; }
+ static int door_descriptor() { return _door_descriptor; }
+
+ // enqueue an operation
+ static void enqueue(SolarisAttachOperation* op);
+
+ // dequeue an operation
+ static SolarisAttachOperation* dequeue();
+};
+
+
+// SolarisAttachOperation is an AttachOperation that additionally encapsulates
+// a socket connection to the requesting client/tool. SolarisAttachOperation
+// can additionally be held in a linked list.
+
+class SolarisAttachOperation: public AttachOperation {
+ private:
+ friend class SolarisAttachListener;
+
+ // connection to client
+ int _socket;
+
+ // linked list support
+ SolarisAttachOperation* _next;
+
+ SolarisAttachOperation* next() { return _next; }
+ void set_next(SolarisAttachOperation* next) { _next = next; }
+
+ public:
+ void complete(jint res, bufferedStream* st);
+
+ int socket() const { return _socket; }
+ void set_socket(int s) { _socket = s; }
+
+ SolarisAttachOperation(char* name) : AttachOperation(name) {
+ set_socket(-1);
+ set_next(NULL);
+ }
+};
+
+// statics
+char SolarisAttachListener::_door_path[PATH_MAX+1];
+volatile bool SolarisAttachListener::_has_door_path;
+int SolarisAttachListener::_door_descriptor = -1;
+mutex_t SolarisAttachListener::_mutex;
+sema_t SolarisAttachListener::_wakeup;
+SolarisAttachOperation* SolarisAttachListener::_head = NULL;
+SolarisAttachOperation* SolarisAttachListener::_tail = NULL;
+
+// Supporting class to help split a buffer into individual components
+class ArgumentIterator : public StackObj {
+ private:
+ char* _pos;
+ char* _end;
+ public:
+ ArgumentIterator(char* arg_buffer, size_t arg_size) {
+ _pos = arg_buffer;
+ _end = _pos + arg_size - 1;
+ }
+ char* next() {
+ if (*_pos == '\0') {
+ return NULL;
+ }
+ char* res = _pos;
+ char* next_pos = strchr(_pos, '\0');
+ if (next_pos < _end) {
+ next_pos++;
+ }
+ _pos = next_pos;
+ return res;
+ }
+};
+
+// Calls from the door function to check that the client credentials
+// match this process. Returns 0 if credentials okay, otherwise -1.
+static int check_credentials() {
+ door_cred_t cred_info;
+
+ // get client credentials
+ if (door_cred(&cred_info) == -1) {
+ return -1; // unable to get them
+ }
+
+ // get our euid/eguid (probably could cache these)
+ uid_t euid = geteuid();
+ gid_t egid = getegid();
+
+ // check that the effective uid/gid matches - discuss this with Jeff.
+ if (cred_info.dc_euid == euid && cred_info.dc_egid == egid) {
+ return 0; // okay
+ } else {
+ return -1; // denied
+ }
+}
+
+
+// Parses the argument buffer to create an AttachOperation that we should
+// enqueue to the attach listener.
+// The buffer is expected to be formatted as follows:
+// <ver>0<cmd>0<arg>0<arg>0<arg>0
+// where <ver> is the version number (must be "1"), <cmd> is the command
+// name ("load, "datadump", ...) and <arg> is an argument.
+//
+static SolarisAttachOperation* create_operation(char* argp, size_t arg_size, int* err) {
+ // assume bad request until parsed
+ *err = SolarisAttachListener::ATTACH_ERROR_BADREQUEST;
+
+ if (arg_size < 2 || argp[arg_size-1] != '\0') {
+ return NULL; // no ver or not null terminated
+ }
+
+ // Use supporting class to iterate over the buffer
+ ArgumentIterator args(argp, arg_size);
+
+ // First check the protocol version
+ char* ver = args.next();
+ if (ver == NULL) {
+ return NULL;
+ }
+ if (atoi(ver) != SolarisAttachListener::ATTACH_PROTOCOL_VER) {
+ *err = SolarisAttachListener::ATTACH_ERROR_BADVERSION;
+ return NULL;
+ }
+
+ // Get command name and create the operation
+ char* name = args.next();
+ if (name == NULL || strlen(name) > AttachOperation::name_length_max) {
+ return NULL;
+ }
+ SolarisAttachOperation* op = new SolarisAttachOperation(name);
+
+ // Iterate over the arguments
+ for (int i=0; i<AttachOperation::arg_count_max; i++) {
+ char* arg = args.next();
+ if (arg == NULL) {
+ op->set_arg(i, NULL);
+ } else {
+ if (strlen(arg) > AttachOperation::arg_length_max) {
+ delete op;
+ return NULL;
+ }
+ op->set_arg(i, arg);
+ }
+ }
+
+ // return operation
+ *err = 0;
+ return op;
+}
+
+// create special operation to indicate all clients have detached
+static SolarisAttachOperation* create_detachall_operation() {
+ return new SolarisAttachOperation(AttachOperation::detachall_operation_name());
+}
+
+// This is door function which the client executes via a door_call.
+extern "C" {
+ static void enqueue_proc(void* cookie, char* argp, size_t arg_size,
+ door_desc_t* dt, uint_t n_desc)
+ {
+ int return_fd = -1;
+ SolarisAttachOperation* op = NULL;
+
+ // no listener
+ jint res = 0;
+ if (!AttachListener::is_initialized()) {
+ // how did we get here?
+ debug_only(warning("door_call when not enabled"));
+ res = (jint)SolarisAttachListener::ATTACH_ERROR_INTERNAL;
+ }
+
+ // check client credentials
+ if (res == 0) {
+ if (check_credentials() != 0) {
+ res = (jint)SolarisAttachListener::ATTACH_ERROR_DENIED;
+ }
+ }
+
+ // if we are stopped at ShowMessageBoxOnError then maybe we can
+ // load a diagnostic library
+ if (res == 0 && is_error_reported()) {
+ if (ShowMessageBoxOnError) {
+ // TBD - support loading of diagnostic library here
+ }
+
+ // can't enqueue operation after fatal error
+ res = (jint)SolarisAttachListener::ATTACH_ERROR_RESOURCE;
+ }
+
+ // create the operation
+ if (res == 0) {
+ int err;
+ op = create_operation(argp, arg_size, &err);
+ res = (op == NULL) ? (jint)err : 0;
+ }
+
+ // create a pair of connected sockets. Store the file descriptor
+ // for one end in the operation and enqueue the operation. The
+ // file descriptor for the other end wil be returned to the client.
+ if (res == 0) {
+ int s[2];
+ if (socketpair(PF_UNIX, SOCK_STREAM, 0, s) < 0) {
+ delete op;
+ res = (jint)SolarisAttachListener::ATTACH_ERROR_RESOURCE;
+ } else {
+ op->set_socket(s[0]);
+ return_fd = s[1];
+ SolarisAttachListener::enqueue(op);
+ }
+ }
+
+ // Return 0 (success) + file descriptor, or non-0 (error)
+ if (res == 0) {
+ door_desc_t desc;
+ desc.d_attributes = DOOR_DESCRIPTOR;
+ desc.d_data.d_desc.d_descriptor = return_fd;
+ door_return((char*)&res, sizeof(res), &desc, 1);
+ } else {
+ door_return((char*)&res, sizeof(res), NULL, 0);
+ }
+ }
+}
+
+// atexit hook to detach the door and remove the file
+extern "C" {
+ static void listener_cleanup() {
+ static int cleanup_done;
+ if (!cleanup_done) {
+ cleanup_done = 1;
+ int dd = SolarisAttachListener::door_descriptor();
+ if (dd >= 0) {
+ ::close(dd);
+ }
+ if (SolarisAttachListener::has_door_path()) {
+ char* path = SolarisAttachListener::door_path();
+ ::fdetach(path);
+ ::unlink(path);
+ }
+ }
+ }
+}
+
+// Create the door
+int SolarisAttachListener::create_door() {
+ char door_path[PATH_MAX+1];
+ int fd, res;
+
+ // register exit function
+ ::atexit(listener_cleanup);
+
+ // create the door descriptor
+ int dd = ::door_create(enqueue_proc, NULL, 0);
+ if (dd < 0) {
+ return -1;
+ }
+
+ sprintf(door_path, "%s/.java_pid%d", os::get_temp_directory(), os::current_process_id());
+ RESTARTABLE(::creat(door_path, S_IRUSR | S_IWUSR), fd);
+
+ if (fd == -1) {
+ debug_only(warning("attempt to create %s failed", door_path));
+ return -1;
+ }
+ assert(fd >= 0, "bad file descriptor");
+ set_door_path(door_path);
+ RESTARTABLE(::close(fd), res);
+
+ // attach the door descriptor to the file
+ if ((res = ::fattach(dd, door_path)) == -1) {
+ // if busy then detach and try again
+ if (errno == EBUSY) {
+ ::fdetach(door_path);
+ res = ::fattach(dd, door_path);
+ }
+ if (res == -1) {
+ ::door_revoke(dd);
+ dd = -1;
+ }
+ }
+ if (dd >= 0) {
+ set_door_descriptor(dd);
+ } else {
+ // unable to create door or attach it to the file
+ ::unlink(door_path);
+ set_door_path(NULL);
+ return -1;
+ }
+
+ return 0;
+}
+
+// Initialization - create the door, locks, and other initialization
+int SolarisAttachListener::init() {
+ if (create_door()) {
+ return -1;
+ }
+
+ int status = os::Solaris::mutex_init(&_mutex);
+ assert_status(status==0, status, "mutex_init");
+
+ status = ::sema_init(&_wakeup, 0, NULL, NULL);
+ assert_status(status==0, status, "sema_init");
+
+ set_head(NULL);
+ set_tail(NULL);
+
+ return 0;
+}
+
+// Dequeue an operation
+SolarisAttachOperation* SolarisAttachListener::dequeue() {
+ for (;;) {
+ int res;
+
+ // wait for somebody to enqueue something
+ while ((res = ::sema_wait(wakeup())) == EINTR)
+ ;
+ if (res) {
+ warning("sema_wait failed: %s", strerror(res));
+ return NULL;
+ }
+
+ // lock the list
+ res = os::Solaris::mutex_lock(mutex());
+ assert(res == 0, "mutex_lock failed");
+
+ // remove the head of the list
+ SolarisAttachOperation* op = head();
+ if (op != NULL) {
+ set_head(op->next());
+ if (head() == NULL) {
+ set_tail(NULL);
+ }
+ }
+
+ // unlock
+ os::Solaris::mutex_unlock(mutex());
+
+ // if we got an operation when return it.
+ if (op != NULL) {
+ return op;
+ }
+ }
+}
+
+// Enqueue an operation
+void SolarisAttachListener::enqueue(SolarisAttachOperation* op) {
+ // lock list
+ int res = os::Solaris::mutex_lock(mutex());
+ assert(res == 0, "mutex_lock failed");
+
+ // enqueue at tail
+ op->set_next(NULL);
+ if (head() == NULL) {
+ set_head(op);
+ } else {
+ tail()->set_next(op);
+ }
+ set_tail(op);
+
+ // wakeup the attach listener
+ RESTARTABLE(::sema_post(wakeup()), res);
+ assert(res == 0, "sema_post failed");
+
+ // unlock
+ os::Solaris::mutex_unlock(mutex());
+}
+
+
+// support function - writes the (entire) buffer to a socket
+static int write_fully(int s, char* buf, int len) {
+ do {
+ int n = ::write(s, buf, len);
+ if (n == -1) {
+ if (errno != EINTR) return -1;
+ } else {
+ buf += n;
+ len -= n;
+ }
+ }
+ while (len > 0);
+ return 0;
+}
+
+// Complete an operation by sending the operation result and any result
+// output to the client. At this time the socket is in blocking mode so
+// potentially we can block if there is a lot of data and the client is
+// non-responsive. For most operations this is a non-issue because the
+// default send buffer is sufficient to buffer everything. In the future
+// if there are operations that involves a very big reply then it the
+// socket could be made non-blocking and a timeout could be used.
+
+void SolarisAttachOperation::complete(jint res, bufferedStream* st) {
+ if (this->socket() >= 0) {
+ JavaThread* thread = JavaThread::current();
+ ThreadBlockInVM tbivm(thread);
+
+ thread->set_suspend_equivalent();
+ // cleared by handle_special_suspend_equivalent_condition() or
+ // java_suspend_self() via check_and_wait_while_suspended()
+
+ // write operation result
+ char msg[32];
+ sprintf(msg, "%d\n", res);
+ int rc = write_fully(this->socket(), msg, strlen(msg));
+
+ // write any result data
+ if (rc == 0) {
+ write_fully(this->socket(), (char*) st->base(), st->size());
+ ::shutdown(this->socket(), 2);
+ }
+
+ // close socket and we're done
+ RESTARTABLE(::close(this->socket()), rc);
+
+ // were we externally suspended while we were waiting?
+ thread->check_and_wait_while_suspended();
+ }
+ delete this;
+}
+
+
+// AttachListener functions
+
+AttachOperation* AttachListener::dequeue() {
+ JavaThread* thread = JavaThread::current();
+ ThreadBlockInVM tbivm(thread);
+
+ thread->set_suspend_equivalent();
+ // cleared by handle_special_suspend_equivalent_condition() or
+ // java_suspend_self() via check_and_wait_while_suspended()
+
+ AttachOperation* op = SolarisAttachListener::dequeue();
+
+ // were we externally suspended while we were waiting?
+ thread->check_and_wait_while_suspended();
+
+ return op;
+}
+
+int AttachListener::pd_init() {
+ JavaThread* thread = JavaThread::current();
+ ThreadBlockInVM tbivm(thread);
+
+ thread->set_suspend_equivalent();
+ // cleared by handle_special_suspend_equivalent_condition() or
+ // java_suspend_self()
+
+ int ret_code = SolarisAttachListener::init();
+
+ // were we externally suspended while we were waiting?
+ thread->check_and_wait_while_suspended();
+
+ return ret_code;
+}
+
+// Attach Listener is started lazily except in the case when
+// +ReduseSignalUsage is used
+bool AttachListener::init_at_startup() {
+ if (ReduceSignalUsage) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// If the file .attach_pid<pid> exists in the working directory
+// or /tmp then this is the trigger to start the attach mechanism
+bool AttachListener::is_init_trigger() {
+ if (init_at_startup() || is_initialized()) {
+ return false; // initialized at startup or already initialized
+ }
+ char fn[32];
+ sprintf(fn, ".attach_pid%d", os::current_process_id());
+ int ret;
+ struct stat64 st;
+ RESTARTABLE(::stat64(fn, &st), ret);
+ if (ret == -1) {
+ sprintf(fn, "/tmp/.attach_pid%d", os::current_process_id());
+ RESTARTABLE(::stat64(fn, &st), ret);
+ }
+ if (ret == 0) {
+ // simple check to avoid starting the attach mechanism when
+ // a bogus user creates the file
+ if (st.st_uid == geteuid()) {
+ init();
+ return true;
+ }
+ }
+ return false;
+}
+
+// if VM aborts then detach/cleanup
+void AttachListener::abort() {
+ listener_cleanup();
+}
+
+void AttachListener::pd_data_dump() {
+ os::signal_notify(SIGQUIT);
+}
+
+static jint enable_dprobes(AttachOperation* op, outputStream* out) {
+ const char* probe = op->arg(0);
+ if (probe == NULL || probe[0] == '\0') {
+ out->print_cr("No probe specified");
+ return JNI_ERR;
+ } else {
+ int probe_typess = atoi(probe);
+ if (errno) {
+ out->print_cr("invalid probe type");
+ return JNI_ERR;
+ } else {
+ DTrace::enable_dprobes(probe_typess);
+ return JNI_OK;
+ }
+ }
+}
+
+// platform specific operations table
+static AttachOperationFunctionInfo funcs[] = {
+ { "enabledprobes", enable_dprobes },
+ { NULL, NULL }
+};
+
+AttachOperationFunctionInfo* AttachListener::pd_find_operation(const char* name) {
+ int i;
+ for (i = 0; funcs[i].name != NULL; i++) {
+ if (strcmp(funcs[i].name, name) == 0) {
+ return &funcs[i];
+ }
+ }
+ return NULL;
+}
+
+// Solaris specific global flag set. Currently, we support only
+// changing ExtendedDTraceProbes flag.
+jint AttachListener::pd_set_flag(AttachOperation* op, outputStream* out) {
+ const char* name = op->arg(0);
+ assert(name != NULL, "flag name should not be null");
+ bool flag = true;
+ const char* arg1;
+ if ((arg1 = op->arg(1)) != NULL) {
+ flag = (atoi(arg1) != 0);
+ if (errno) {
+ out->print_cr("flag value has to be an integer");
+ return JNI_ERR;
+ }
+ }
+
+ if (strcmp(name, "ExtendedDTraceProbes") != 0) {
+ out->print_cr("flag '%s' cannot be changed", name);
+ return JNI_ERR;
+ }
+
+ DTrace::set_extended_dprobes(flag);
+ return JNI_OK;
+}
+
+void AttachListener::pd_detachall() {
+ DTrace::detach_all_clients();
+}