duke@435: /* duke@435: * Copyright 2005-2006 Sun Microsystems, Inc. All Rights Reserved. duke@435: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. duke@435: * duke@435: * This code is free software; you can redistribute it and/or modify it duke@435: * under the terms of the GNU General Public License version 2 only, as duke@435: * published by the Free Software Foundation. duke@435: * duke@435: * This code is distributed in the hope that it will be useful, but WITHOUT duke@435: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or duke@435: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License duke@435: * version 2 for more details (a copy is included in the LICENSE file that duke@435: * accompanied this code). duke@435: * duke@435: * You should have received a copy of the GNU General Public License version duke@435: * 2 along with this work; if not, write to the Free Software Foundation, duke@435: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. duke@435: * duke@435: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, duke@435: * CA 95054 USA or visit www.sun.com if you need additional information or duke@435: * have any questions. duke@435: * duke@435: */ duke@435: duke@435: # include "incls/_precompiled.incl" duke@435: # include "incls/_attachListener_linux.cpp.incl" duke@435: duke@435: #include duke@435: #include duke@435: #include duke@435: #include duke@435: #include duke@435: #include duke@435: duke@435: // The attach mechanism on Linux uses a UNIX domain socket. An attach listener duke@435: // thread is created at startup or is created on-demand via a signal from duke@435: // the client tool. The attach listener creates a socket and binds it to a file duke@435: // in the filesystem. The attach listener then acts as a simple (single- duke@435: // threaded) server - tt waits for a client to connect, reads the request, duke@435: // executes it, and returns the response to the client via the socket duke@435: // connection. duke@435: // duke@435: // As the socket is a UNIX domain socket it means that only clients on the duke@435: // local machine can connect. In addition there are two other aspects to duke@435: // the security: duke@435: // 1. The well known file that the socket is bound to has permission 400 duke@435: // 2. When a client connect, the SO_PEERCRED socket option is used to duke@435: // obtain the credentials of client. We check that the effective uid duke@435: // of the client matches this process. duke@435: duke@435: // forward reference duke@435: class LinuxAttachOperation; duke@435: duke@435: class LinuxAttachListener: AllStatic { duke@435: private: duke@435: // the path to which we bind the UNIX domain socket duke@435: static char _path[PATH_MAX+1]; duke@435: static bool _has_path; duke@435: duke@435: // the file descriptor for the listening socket duke@435: static int _listener; duke@435: duke@435: static void set_path(char* path) { duke@435: if (path == NULL) { duke@435: _has_path = false; duke@435: } else { duke@435: strncpy(_path, path, PATH_MAX); duke@435: _path[PATH_MAX] = '\0'; duke@435: _has_path = true; duke@435: } duke@435: } duke@435: duke@435: static void set_listener(int s) { _listener = s; } duke@435: duke@435: // reads a request from the given connected socket duke@435: static LinuxAttachOperation* read_request(int s); duke@435: duke@435: public: duke@435: enum { duke@435: ATTACH_PROTOCOL_VER = 1 // protocol version duke@435: }; duke@435: enum { duke@435: ATTACH_ERROR_BADVERSION = 101 // error codes duke@435: }; duke@435: duke@435: // initialize the listener, returns 0 if okay duke@435: static int init(); duke@435: duke@435: static char* path() { return _path; } duke@435: static bool has_path() { return _has_path; } duke@435: static int listener() { return _listener; } duke@435: duke@435: // write the given buffer to a socket duke@435: static int write_fully(int s, char* buf, int len); duke@435: duke@435: static LinuxAttachOperation* dequeue(); duke@435: }; duke@435: duke@435: class LinuxAttachOperation: public AttachOperation { duke@435: private: duke@435: // the connection to the client duke@435: int _socket; duke@435: duke@435: public: duke@435: void complete(jint res, bufferedStream* st); duke@435: duke@435: void set_socket(int s) { _socket = s; } duke@435: int socket() const { return _socket; } duke@435: duke@435: LinuxAttachOperation(char* name) : AttachOperation(name) { duke@435: set_socket(-1); duke@435: } duke@435: }; duke@435: duke@435: // statics duke@435: char LinuxAttachListener::_path[PATH_MAX+1]; duke@435: bool LinuxAttachListener::_has_path; duke@435: int LinuxAttachListener::_listener = -1; duke@435: duke@435: // Supporting class to help split a buffer into individual components duke@435: class ArgumentIterator : public StackObj { duke@435: private: duke@435: char* _pos; duke@435: char* _end; duke@435: public: duke@435: ArgumentIterator(char* arg_buffer, size_t arg_size) { duke@435: _pos = arg_buffer; duke@435: _end = _pos + arg_size - 1; duke@435: } duke@435: char* next() { duke@435: if (*_pos == '\0') { duke@435: return NULL; duke@435: } duke@435: char* res = _pos; duke@435: char* next_pos = strchr(_pos, '\0'); duke@435: if (next_pos < _end) { duke@435: next_pos++; duke@435: } duke@435: _pos = next_pos; duke@435: return res; duke@435: } duke@435: }; duke@435: duke@435: duke@435: // atexit hook to stop listener and unlink the file that it is duke@435: // bound too. duke@435: extern "C" { duke@435: static void listener_cleanup() { duke@435: static int cleanup_done; duke@435: if (!cleanup_done) { duke@435: cleanup_done = 1; duke@435: int s = LinuxAttachListener::listener(); duke@435: if (s != -1) { duke@435: ::close(s); duke@435: } duke@435: if (LinuxAttachListener::has_path()) { duke@435: ::unlink(LinuxAttachListener::path()); duke@435: } duke@435: } duke@435: } duke@435: } duke@435: duke@435: // Initialization - create a listener socket and bind it to a file duke@435: duke@435: int LinuxAttachListener::init() { duke@435: char path[PATH_MAX+1]; // socket file duke@435: int listener; // listener socket (file descriptor) duke@435: duke@435: // register function to cleanup duke@435: ::atexit(listener_cleanup); duke@435: duke@435: // create the listener socket duke@435: listener = ::socket(PF_UNIX, SOCK_STREAM, 0); duke@435: if (listener == -1) { duke@435: return -1; duke@435: } duke@435: duke@435: int res = -1; duke@435: struct sockaddr_un addr; duke@435: addr.sun_family = AF_UNIX; duke@435: duke@435: // FIXME: Prior to b39 the tool-side API expected to find the well duke@435: // known file in the working directory. To allow this libjvm.so work with duke@435: // a pre-b39 SDK we create it in the working directory if duke@435: // +StartAttachListener is used is used. All unit tests for this feature duke@435: // currently used this flag. Once b39 SDK has been promoted we can remove duke@435: // this code. duke@435: if (StartAttachListener) { duke@435: sprintf(path, ".java_pid%d", os::current_process_id()); duke@435: strcpy(addr.sun_path, path); duke@435: ::unlink(path); duke@435: res = ::bind(listener, (struct sockaddr*)&addr, sizeof(addr)); duke@435: } duke@435: if (res == -1) { duke@435: sprintf(path, "%s/.java_pid%d", os::get_temp_directory(), os::current_process_id()); duke@435: strcpy(addr.sun_path, path); duke@435: ::unlink(path); duke@435: res = ::bind(listener, (struct sockaddr*)&addr, sizeof(addr)); duke@435: } duke@435: if (res == -1) { duke@435: RESTARTABLE(::close(listener), res); duke@435: return -1; duke@435: } duke@435: set_path(path); duke@435: duke@435: // put in listen mode and set permission duke@435: if ((::listen(listener, 5) == -1) || (::chmod(path, S_IREAD|S_IWRITE) == -1)) { duke@435: RESTARTABLE(::close(listener), res); duke@435: ::unlink(path); duke@435: set_path(NULL); duke@435: return -1; duke@435: } duke@435: set_listener(listener); duke@435: duke@435: return 0; duke@435: } duke@435: duke@435: // Given a socket that is connected to a peer we read the request and duke@435: // create an AttachOperation. As the socket is blocking there is potential duke@435: // for a denial-of-service if the peer does not response. However this happens duke@435: // after the peer credentials have been checked and in the worst case it just duke@435: // means that the attach listener thread is blocked. duke@435: // duke@435: LinuxAttachOperation* LinuxAttachListener::read_request(int s) { duke@435: char ver_str[8]; duke@435: sprintf(ver_str, "%d", ATTACH_PROTOCOL_VER); duke@435: duke@435: // The request is a sequence of strings so we first figure out the duke@435: // expected count and the maximum possible length of the request. duke@435: // The request is: duke@435: // 00000 duke@435: // where is the protocol version (1), is the command duke@435: // name ("load", "datadump", ...), and is an argument duke@435: int expected_str_count = 2 + AttachOperation::arg_count_max; duke@435: int max_len = (strlen(ver_str) + 1) + (AttachOperation::name_length_max + 1) + duke@435: AttachOperation::arg_count_max*(AttachOperation::arg_length_max + 1); duke@435: duke@435: char buf[max_len]; duke@435: int str_count = 0; duke@435: duke@435: // Read until all (expected) strings have been read, the buffer is duke@435: // full, or EOF. duke@435: duke@435: int off = 0; duke@435: int left = max_len; duke@435: duke@435: do { duke@435: int n; duke@435: RESTARTABLE(read(s, buf+off, left), n); duke@435: if (n == -1) { duke@435: return NULL; // reset by peer or other error duke@435: } duke@435: if (n == 0) { duke@435: break; duke@435: } duke@435: for (int i=0; i so check it now to duke@435: // check for protocol mis-match duke@435: if (str_count == 1) { duke@435: if ((strlen(buf) != strlen(ver_str)) || duke@435: (atoi(buf) != ATTACH_PROTOCOL_VER)) { duke@435: char msg[32]; duke@435: sprintf(msg, "%d\n", ATTACH_ERROR_BADVERSION); duke@435: write_fully(s, msg, strlen(msg)); duke@435: return NULL; duke@435: } duke@435: } duke@435: } duke@435: } duke@435: off += n; duke@435: left -= n; duke@435: } while (left > 0 && str_count < expected_str_count); duke@435: duke@435: if (str_count != expected_str_count) { duke@435: return NULL; // incomplete request duke@435: } duke@435: duke@435: // parse request duke@435: duke@435: ArgumentIterator args(buf, (max_len)-left); duke@435: duke@435: // version already checked duke@435: char* v = args.next(); duke@435: duke@435: char* name = args.next(); duke@435: if (name == NULL || strlen(name) > AttachOperation::name_length_max) { duke@435: return NULL; duke@435: } duke@435: duke@435: LinuxAttachOperation* op = new LinuxAttachOperation(name); duke@435: duke@435: for (int i=0; iset_arg(i, NULL); duke@435: } else { duke@435: if (strlen(arg) > AttachOperation::arg_length_max) { duke@435: delete op; duke@435: return NULL; duke@435: } duke@435: op->set_arg(i, arg); duke@435: } duke@435: } duke@435: duke@435: op->set_socket(s); duke@435: return op; duke@435: } duke@435: duke@435: duke@435: // Dequeue an operation duke@435: // duke@435: // In the Linux implementation there is only a single operation and clients duke@435: // cannot queue commands (except at the socket level). duke@435: // duke@435: LinuxAttachOperation* LinuxAttachListener::dequeue() { duke@435: for (;;) { duke@435: int s; duke@435: duke@435: // wait for client to connect duke@435: struct sockaddr addr; duke@435: socklen_t len = sizeof(addr); duke@435: RESTARTABLE(::accept(listener(), &addr, &len), s); duke@435: if (s == -1) { duke@435: return NULL; // log a warning? duke@435: } duke@435: duke@435: // get the credentials of the peer and check the effective uid/guid duke@435: // - check with jeff on this. duke@435: struct ucred cred_info; duke@435: socklen_t optlen = sizeof(cred_info); duke@435: if (::getsockopt(s, SOL_SOCKET, SO_PEERCRED, (void*)&cred_info, &optlen) == -1) { duke@435: int res; duke@435: RESTARTABLE(::close(s), res); duke@435: continue; duke@435: } duke@435: uid_t euid = geteuid(); duke@435: gid_t egid = getegid(); duke@435: duke@435: if (cred_info.uid != euid || cred_info.gid != egid) { duke@435: int res; duke@435: RESTARTABLE(::close(s), res); duke@435: continue; duke@435: } duke@435: duke@435: // peer credential look okay so we read the request duke@435: LinuxAttachOperation* op = read_request(s); duke@435: if (op == NULL) { duke@435: int res; duke@435: RESTARTABLE(::close(s), res); duke@435: continue; duke@435: } else { duke@435: return op; duke@435: } duke@435: } duke@435: } duke@435: duke@435: // write the given buffer to the socket duke@435: int LinuxAttachListener::write_fully(int s, char* buf, int len) { duke@435: do { duke@435: int n = ::write(s, buf, len); duke@435: if (n == -1) { duke@435: if (errno != EINTR) return -1; duke@435: } else { duke@435: buf += n; duke@435: len -= n; duke@435: } duke@435: } duke@435: while (len > 0); duke@435: return 0; duke@435: } duke@435: duke@435: // Complete an operation by sending the operation result and any result duke@435: // output to the client. At this time the socket is in blocking mode so duke@435: // potentially we can block if there is a lot of data and the client is duke@435: // non-responsive. For most operations this is a non-issue because the duke@435: // default send buffer is sufficient to buffer everything. In the future duke@435: // if there are operations that involves a very big reply then it the duke@435: // socket could be made non-blocking and a timeout could be used. duke@435: duke@435: void LinuxAttachOperation::complete(jint result, bufferedStream* st) { duke@435: JavaThread* thread = JavaThread::current(); duke@435: ThreadBlockInVM tbivm(thread); duke@435: duke@435: thread->set_suspend_equivalent(); duke@435: // cleared by handle_special_suspend_equivalent_condition() or duke@435: // java_suspend_self() via check_and_wait_while_suspended() duke@435: duke@435: // write operation result duke@435: char msg[32]; duke@435: sprintf(msg, "%d\n", result); duke@435: int rc = LinuxAttachListener::write_fully(this->socket(), msg, strlen(msg)); duke@435: duke@435: // write any result data duke@435: if (rc == 0) { duke@435: LinuxAttachListener::write_fully(this->socket(), (char*) st->base(), st->size()); duke@435: ::shutdown(this->socket(), 2); duke@435: } duke@435: duke@435: // done duke@435: RESTARTABLE(::close(this->socket()), rc); duke@435: duke@435: // were we externally suspended while we were waiting? duke@435: thread->check_and_wait_while_suspended(); duke@435: duke@435: delete this; duke@435: } duke@435: duke@435: duke@435: // AttachListener functions duke@435: duke@435: AttachOperation* AttachListener::dequeue() { duke@435: JavaThread* thread = JavaThread::current(); duke@435: ThreadBlockInVM tbivm(thread); duke@435: duke@435: thread->set_suspend_equivalent(); duke@435: // cleared by handle_special_suspend_equivalent_condition() or duke@435: // java_suspend_self() via check_and_wait_while_suspended() duke@435: duke@435: AttachOperation* op = LinuxAttachListener::dequeue(); duke@435: duke@435: // were we externally suspended while we were waiting? duke@435: thread->check_and_wait_while_suspended(); duke@435: duke@435: return op; duke@435: } duke@435: duke@435: int AttachListener::pd_init() { duke@435: JavaThread* thread = JavaThread::current(); duke@435: ThreadBlockInVM tbivm(thread); duke@435: duke@435: thread->set_suspend_equivalent(); duke@435: // cleared by handle_special_suspend_equivalent_condition() or duke@435: // java_suspend_self() via check_and_wait_while_suspended() duke@435: duke@435: int ret_code = LinuxAttachListener::init(); duke@435: duke@435: // were we externally suspended while we were waiting? duke@435: thread->check_and_wait_while_suspended(); duke@435: duke@435: return ret_code; duke@435: } duke@435: duke@435: // Attach Listener is started lazily except in the case when duke@435: // +ReduseSignalUsage is used duke@435: bool AttachListener::init_at_startup() { duke@435: if (ReduceSignalUsage) { duke@435: return true; duke@435: } else { duke@435: return false; duke@435: } duke@435: } duke@435: duke@435: // If the file .attach_pid exists in the working directory duke@435: // or /tmp then this is the trigger to start the attach mechanism duke@435: bool AttachListener::is_init_trigger() { duke@435: if (init_at_startup() || is_initialized()) { duke@435: return false; // initialized at startup or already initialized duke@435: } duke@435: char fn[32]; duke@435: sprintf(fn, ".attach_pid%d", os::current_process_id()); duke@435: int ret; duke@435: struct stat64 st; duke@435: RESTARTABLE(::stat64(fn, &st), ret); duke@435: if (ret == -1) { duke@435: sprintf(fn, "/tmp/.attach_pid%d", os::current_process_id()); duke@435: RESTARTABLE(::stat64(fn, &st), ret); duke@435: } duke@435: if (ret == 0) { duke@435: // simple check to avoid starting the attach mechanism when duke@435: // a bogus user creates the file duke@435: if (st.st_uid == geteuid()) { duke@435: init(); duke@435: return true; duke@435: } duke@435: } duke@435: return false; duke@435: } duke@435: duke@435: // if VM aborts then remove listener duke@435: void AttachListener::abort() { duke@435: listener_cleanup(); duke@435: } duke@435: duke@435: void AttachListener::pd_data_dump() { duke@435: os::signal_notify(SIGQUIT); duke@435: } duke@435: duke@435: AttachOperationFunctionInfo* AttachListener::pd_find_operation(const char* n) { duke@435: return NULL; duke@435: } duke@435: duke@435: jint AttachListener::pd_set_flag(AttachOperation* op, outputStream* out) { duke@435: out->print_cr("flag '%s' cannot be changed", op->arg(0)); duke@435: return JNI_ERR; duke@435: } duke@435: duke@435: void AttachListener::pd_detachall() { duke@435: // do nothing for now duke@435: }