/* * Copyright (c) 2005, 2019, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. * */ #include "precompiled.hpp" #include "runtime/interfaceSupport.hpp" #include "runtime/os.hpp" #include "services/attachListener.hpp" #include "services/dtraceAttacher.hpp" #include #include #include #include #include #include // stropts.h uses STR in stream ioctl defines #undef STR #include #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 bool _atexit_registered; // 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 }; static void set_door_path(char* path) { if (path == NULL) { _door_path[0] = '\0'; _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; } // 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; bool SolarisAttachListener::_atexit_registered = false; 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: // 00000 // where is the version number (must be "1"), is the command // name ("load, "datadump", ...) and 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; iset_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; // DOOR_RELEASE flag makes sure fd is closed after passing it to // the client. See door_return(3DOOR) man page. desc.d_attributes = DOOR_DESCRIPTOR | DOOR_RELEASE; 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() { int dd = SolarisAttachListener::door_descriptor(); if (dd >= 0) { SolarisAttachListener::set_door_descriptor(-1); ::close(dd); } if (SolarisAttachListener::has_door_path()) { char* path = SolarisAttachListener::door_path(); ::fdetach(path); ::unlink(path); SolarisAttachListener::set_door_path(NULL); } } } // Create the door int SolarisAttachListener::create_door() { char door_path[PATH_MAX+1]; char initial_path[PATH_MAX+1]; int fd, res; // register exit function if (!_atexit_registered) { _atexit_registered = true; ::atexit(listener_cleanup); } // create the door descriptor int dd = ::door_create(enqueue_proc, NULL, 0); if (dd < 0) { return -1; } // create initial file to attach door descriptor snprintf(door_path, sizeof(door_path), "%s/.java_pid%d", os::get_temp_directory(), os::current_process_id()); snprintf(initial_path, sizeof(initial_path), "%s.tmp", door_path); RESTARTABLE(::creat(initial_path, S_IRUSR | S_IWUSR), fd); if (fd == -1) { debug_only(warning("attempt to create %s failed", initial_path)); ::door_revoke(dd); return -1; } assert(fd >= 0, "bad file descriptor"); ::close(fd); // attach the door descriptor to the file if ((res = ::fattach(dd, initial_path)) == -1) { // if busy then detach and try again if (errno == EBUSY) { ::fdetach(initial_path); res = ::fattach(dd, initial_path); } if (res == -1) { ::door_revoke(dd); dd = -1; } } // rename file so that clients can attach if (dd >= 0) { if (::rename(initial_path, door_path) == -1) { ::close(dd); ::fdetach(initial_path); dd = -1; } } if (dd >= 0) { set_door_descriptor(dd); set_door_path(door_path); } else { // unable to create door, attach it to file, or rename file into place ::unlink(initial_path); 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 ::close(this->socket()); // 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; } // Performs initialization at vm startup // For Solaris we remove any stale .java_pid file which could cause // an attaching process to think we are ready to receive a door_call // before we are properly initialized void AttachListener::vm_start() { char fn[PATH_MAX+1]; struct stat64 st; int ret; int n = snprintf(fn, sizeof(fn), "%s/.java_pid%d", os::get_temp_directory(), os::current_process_id()); assert(n < sizeof(fn), "java_pid file name buffer overflow"); RESTARTABLE(::stat64(fn, &st), ret); if (ret == 0) { ret = ::unlink(fn); if (ret == -1) { debug_only(warning("failed to remove stale attach pid file at %s", fn)); } } } 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; } } bool AttachListener::check_socket_file() { int ret; struct stat64 st; ret = stat64(SolarisAttachListener::door_path(), &st); if (ret == -1) { // need to restart attach listener. debug_only(warning("Door file %s does not exist - Restart Attach Listener", SolarisAttachListener::door_path())); listener_cleanup(); // wait to terminate current attach listener instance... while (AttachListener::transit_state(AL_INITIALIZING, AL_NOT_INITIALIZED) != AL_NOT_INITIALIZED) { os::yield(); } return is_init_trigger(); } return false; } // If the file .attach_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[PATH_MAX+1]; sprintf(fn, ".attach_pid%d", os::current_process_id()); int ret; struct stat64 st; RESTARTABLE(::stat64(fn, &st), ret); if (ret == -1) { snprintf(fn, sizeof(fn), "%s/.attach_pid%d", os::get_temp_directory(), 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) { DTrace::set_extended_dprobes(flag); return JNI_OK; } if (strcmp(name, "DTraceMonitorProbes") == 0) { DTrace::set_monitor_dprobes(flag); return JNI_OK; } out->print_cr("flag '%s' cannot be changed", name); return JNI_ERR; } void AttachListener::pd_detachall() { DTrace::detach_all_clients(); }