hotspot/src/os/solaris/vm/attachListener_solaris.cpp
changeset 1 489c9b5090e2
child 5089 0cce506a0158
--- /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();
+}