/* Copyright (C) 2017 The Android Open Source Project * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This file implements interfaces from the file jdwpTransport.h. This * implementation is licensed under the same terms as the file * jdwpTransport.h. The copyright and license information for the file * jdwpTransport.h follows. * * Copyright (c) 2003, 2016, 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * 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 "dt_fd_forward.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace dt_fd_forward { // Helper that puts line-number in error message. #define DT_IO_ERROR(f) \ SetLastError(::android::base::StringPrintf("%s:%d - %s: %s", \ __FILE__, __LINE__, f, strerror(errno))) extern const jdwpTransportNativeInterface_ gTransportInterface; template static T HostToNetwork(T in); template static T NetworkToHost(T in); template<> int8_t HostToNetwork(int8_t in) { return in; } template<> int8_t NetworkToHost(int8_t in) { return in; } template<> int16_t HostToNetwork(int16_t in) { return htons(in); } template<> int16_t NetworkToHost(int16_t in) { return ntohs(in); } template<> int32_t HostToNetwork(int32_t in) { return htonl(in); } template<> int32_t NetworkToHost(int32_t in) { return ntohl(in); } FdForwardTransport::FdForwardTransport(jdwpTransportCallback* cb) : mem_(*cb), read_fd_(-1), write_fd_(-1), wakeup_fd_(eventfd(0, EFD_NONBLOCK)), listen_fd_(-1), close_notify_fd_(-1), state_(TransportState::kClosed), current_seq_num_(0) {} FdForwardTransport::~FdForwardTransport() { } bool FdForwardTransport::ChangeState(TransportState old_state, TransportState new_state) { if (old_state == state_) { state_ = new_state; state_cv_.notify_all(); return true; } else { return false; } } jdwpTransportError FdForwardTransport::PerformAttach(int listen_fd) { jdwpTransportError err = SetupListen(listen_fd); if (err != OK) { return OK; } err = Accept(); StopListening(); return err; } static void SendListenMessage(const android::base::unique_fd& fd) { TEMP_FAILURE_RETRY(send(fd, kListenStartMessage, sizeof(kListenStartMessage), MSG_EOR)); } // Copy from file_utils, so we do not need to depend on libartbase. static int DupCloexec(int fd) { #if defined(__linux__) return fcntl(fd, F_DUPFD_CLOEXEC, 0); #else return dup(fd); #endif } jdwpTransportError FdForwardTransport::SetupListen(int listen_fd) { std::lock_guard lk(state_mutex_); if (!ChangeState(TransportState::kClosed, TransportState::kListenSetup)) { return ERR(ILLEGAL_STATE); } else { listen_fd_.reset(DupCloexec(listen_fd)); SendListenMessage(listen_fd_); CHECK(ChangeState(TransportState::kListenSetup, TransportState::kListening)); return OK; } } static void SendListenEndMessage(const android::base::unique_fd& fd) { TEMP_FAILURE_RETRY(send(fd, kListenEndMessage, sizeof(kListenEndMessage), MSG_EOR)); } jdwpTransportError FdForwardTransport::StopListening() { std::lock_guard lk(state_mutex_); if (listen_fd_ != -1) { SendListenEndMessage(listen_fd_); } // Don't close the listen_fd_ since we might need it for later calls to listen. if (ChangeState(TransportState::kListening, TransportState::kClosed) || state_ == TransportState::kOpen) { listen_fd_.reset(); } return OK; } // Last error message. thread_local std::string global_last_error_; void FdForwardTransport::SetLastError(const std::string& desc) { LOG(ERROR) << desc; global_last_error_ = desc; } IOResult FdForwardTransport::ReadFullyWithoutChecks(void* data, size_t ndata) { uint8_t* bdata = reinterpret_cast(data); size_t nbytes = 0; while (nbytes < ndata) { int res = TEMP_FAILURE_RETRY(read(read_fd_, bdata + nbytes, ndata - nbytes)); if (res < 0) { DT_IO_ERROR("Failed read()"); return IOResult::kError; } else if (res == 0) { return IOResult::kEOF; } else { nbytes += res; } } return IOResult::kOk; } IOResult FdForwardTransport::ReadUpToMax(void* data, size_t ndata, /*out*/size_t* read_amount) { CHECK_GE(read_fd_.get(), 0); int avail; int res = TEMP_FAILURE_RETRY(ioctl(read_fd_, FIONREAD, &avail)); if (res < 0) { DT_IO_ERROR("Failed ioctl(read_fd_, FIONREAD, &avail)"); return IOResult::kError; } size_t to_read = std::min(static_cast(avail), ndata); *read_amount = to_read; if (*read_amount == 0) { // Check if the read would cause an EOF. struct pollfd pollfd = { read_fd_, POLLRDHUP, 0 }; res = TEMP_FAILURE_RETRY(poll(&pollfd, /*nfds*/1, /*timeout*/0)); if (res < 0 || (pollfd.revents & POLLERR) == POLLERR) { DT_IO_ERROR("Failed poll on read fd."); return IOResult::kError; } return ((pollfd.revents & (POLLRDHUP | POLLHUP)) == 0) ? IOResult::kOk : IOResult::kEOF; } return ReadFullyWithoutChecks(data, to_read); } IOResult FdForwardTransport::ReadFully(void* data, size_t ndata) { uint64_t seq_num = current_seq_num_; size_t nbytes = 0; while (nbytes < ndata) { size_t read_len; struct pollfd pollfds[2]; { std::lock_guard lk(state_mutex_); // Operations in this block must not cause an unbounded pause. if (state_ != TransportState::kOpen || seq_num != current_seq_num_) { // Async-close occurred! return IOResult::kInterrupt; } else { CHECK_GE(read_fd_.get(), 0); } IOResult res = ReadUpToMax(reinterpret_cast(data) + nbytes, ndata - nbytes, /*out*/&read_len); if (res != IOResult::kOk) { return res; } else { nbytes += read_len; } pollfds[0] = { read_fd_, POLLRDHUP | POLLIN, 0 }; pollfds[1] = { wakeup_fd_, POLLIN, 0 }; } if (read_len == 0) { // No more data. Sleep without locks until more is available. We don't actually check for any // errors since possible ones are (1) the read_fd_ is closed or wakeup happens which are both // fine since the wakeup_fd_ or the poll failing will wake us up. int poll_res = TEMP_FAILURE_RETRY(poll(pollfds, 2, -1)); if (poll_res < 0) { DT_IO_ERROR("Failed to poll!"); } // Clear the wakeup_fd regardless. uint64_t val; int unused = TEMP_FAILURE_RETRY(read(wakeup_fd_, &val, sizeof(val))); DCHECK(unused == sizeof(val) || errno == EAGAIN); if (poll_res < 0) { return IOResult::kError; } } } return IOResult::kOk; } // A helper that allows us to lock the eventfd 'fd'. class ScopedEventFdLock { public: explicit ScopedEventFdLock(const android::base::unique_fd& fd) : fd_(fd), data_(0) { TEMP_FAILURE_RETRY(read(fd_, &data_, sizeof(data_))); } ~ScopedEventFdLock() { TEMP_FAILURE_RETRY(write(fd_, &data_, sizeof(data_))); } private: const android::base::unique_fd& fd_; uint64_t data_; }; IOResult FdForwardTransport::WriteFullyWithoutChecks(const void* data, size_t ndata) { ScopedEventFdLock sefdl(write_lock_fd_); const uint8_t* bdata = static_cast(data); size_t nbytes = 0; while (nbytes < ndata) { int res = TEMP_FAILURE_RETRY(write(write_fd_, bdata + nbytes, ndata - nbytes)); if (res < 0) { DT_IO_ERROR("Failed write()"); return IOResult::kError; } else if (res == 0) { return IOResult::kEOF; } else { nbytes += res; } } return IOResult::kOk; } IOResult FdForwardTransport::WriteFully(const void* data, size_t ndata) { std::lock_guard lk(state_mutex_); if (state_ != TransportState::kOpen) { return IOResult::kInterrupt; } return WriteFullyWithoutChecks(data, ndata); } static void SendAcceptMessage(int fd) { TEMP_FAILURE_RETRY(send(fd, kAcceptMessage, sizeof(kAcceptMessage), MSG_EOR)); } IOResult FdForwardTransport::ReceiveFdsFromSocket(bool* do_handshake) { union { cmsghdr cm; uint8_t buffer[CMSG_SPACE(sizeof(FdSet))]; } msg_union; // This lets us know if we need to do a handshake or not. char message[128]; iovec iov; iov.iov_base = message; iov.iov_len = sizeof(message); msghdr msg; memset(&msg, 0, sizeof(msg)); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = msg_union.buffer; msg.msg_controllen = sizeof(msg_union.buffer); cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_len = msg.msg_controllen; cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; memset(reinterpret_cast(CMSG_DATA(cmsg)), -1, FdSet::kDataLength); int res = TEMP_FAILURE_RETRY(recvmsg(listen_fd_, &msg, 0)); if (res <= 0) { DT_IO_ERROR("Failed to receive fds!"); return IOResult::kError; } FdSet out_fds = FdSet::ReadData(CMSG_DATA(cmsg)); bool failed = false; if (out_fds.read_fd_ < 0 || out_fds.write_fd_ < 0 || out_fds.write_lock_fd_ < 0) { DT_IO_ERROR("Received fds were invalid!"); failed = true; } else if (strcmp(kPerformHandshakeMessage, message) == 0) { *do_handshake = true; } else if (strcmp(kSkipHandshakeMessage, message) == 0) { *do_handshake = false; } else { DT_IO_ERROR("Unknown message sent with fds."); failed = true; } if (failed) { if (out_fds.read_fd_ >= 0) { close(out_fds.read_fd_); } if (out_fds.write_fd_ >= 0) { close(out_fds.write_fd_); } if (out_fds.write_lock_fd_ >= 0) { close(out_fds.write_lock_fd_); } return IOResult::kError; } read_fd_.reset(out_fds.read_fd_); write_fd_.reset(out_fds.write_fd_); write_lock_fd_.reset(out_fds.write_lock_fd_); // We got the fds. Send ack. close_notify_fd_.reset(DupCloexec(listen_fd_)); SendAcceptMessage(close_notify_fd_); return IOResult::kOk; } // Accept the connection. Note that we match the behavior of other transports which is to just close // the connection and try again if we get a bad handshake. jdwpTransportError FdForwardTransport::Accept() { // TODO Work with timeouts. while (true) { std::unique_lock lk(state_mutex_); while (!ChangeState(TransportState::kListening, TransportState::kOpening)) { if (state_ == TransportState::kClosed || state_ == TransportState::kOpen) { return ERR(ILLEGAL_STATE); } state_cv_.wait(lk); } bool do_handshake = false; DCHECK_NE(listen_fd_.get(), -1); if (ReceiveFdsFromSocket(&do_handshake) != IOResult::kOk) { CHECK(ChangeState(TransportState::kOpening, TransportState::kListening)); return ERR(IO_ERROR); } current_seq_num_++; // Moved to the opening state. if (do_handshake) { // Perform the handshake char handshake_recv[sizeof(kJdwpHandshake)]; memset(handshake_recv, 0, sizeof(handshake_recv)); IOResult res = ReadFullyWithoutChecks(handshake_recv, sizeof(handshake_recv)); if (res != IOResult::kOk || strncmp(handshake_recv, kJdwpHandshake, sizeof(kJdwpHandshake)) != 0) { DT_IO_ERROR("Failed to read handshake"); CHECK(ChangeState(TransportState::kOpening, TransportState::kListening)); CloseFdsLocked(); // Retry. continue; } res = WriteFullyWithoutChecks(kJdwpHandshake, sizeof(kJdwpHandshake)); if (res != IOResult::kOk) { DT_IO_ERROR("Failed to write handshake"); CHECK(ChangeState(TransportState::kOpening, TransportState::kListening)); CloseFdsLocked(); // Retry. continue; } } break; } CHECK(ChangeState(TransportState::kOpening, TransportState::kOpen)); return OK; } void SendClosingMessage(int fd) { if (fd >= 0) { TEMP_FAILURE_RETRY(send(fd, kCloseMessage, sizeof(kCloseMessage), MSG_EOR)); } } // Actually close the fds associated with this transport. void FdForwardTransport::CloseFdsLocked() { // We have a different set of fd's now. Increase the seq number. current_seq_num_++; // All access to these is locked under the state_mutex_ so we are safe to close these. { ScopedEventFdLock sefdl(write_lock_fd_); if (close_notify_fd_ >= 0) { SendClosingMessage(close_notify_fd_); } close_notify_fd_.reset(); read_fd_.reset(); write_fd_.reset(); close_notify_fd_.reset(); } write_lock_fd_.reset(); // Send a wakeup in case we have any in-progress reads/writes. uint64_t data = 1; TEMP_FAILURE_RETRY(write(wakeup_fd_, &data, sizeof(data))); } jdwpTransportError FdForwardTransport::Close() { std::lock_guard lk(state_mutex_); jdwpTransportError res = ChangeState(TransportState::kOpen, TransportState::kClosed) ? OK : ERR(ILLEGAL_STATE); // Send a wakeup after changing the state even if nothing actually happened. uint64_t data = 1; TEMP_FAILURE_RETRY(write(wakeup_fd_, &data, sizeof(data))); if (res == OK) { CloseFdsLocked(); } return res; } // A helper class to read and parse the JDWP packet. class PacketReader { public: PacketReader(FdForwardTransport* transport, jdwpPacket* pkt) : transport_(transport), pkt_(pkt), is_eof_(false), is_err_(false) {} bool ReadFully() { // Zero out. memset(pkt_, 0, sizeof(jdwpPacket)); int32_t len = ReadInt32(); // read len if (is_err_) { return false; } else if (is_eof_) { return true; } else if (len < 11) { transport_->DT_IO_ERROR("Packet with len < 11 received!"); return false; } pkt_->type.cmd.len = len; pkt_->type.cmd.id = ReadInt32(); pkt_->type.cmd.flags = ReadByte(); if (is_err_) { return false; } else if (is_eof_) { return true; } else if ((pkt_->type.reply.flags & JDWPTRANSPORT_FLAGS_REPLY) == JDWPTRANSPORT_FLAGS_REPLY) { ReadReplyPacket(); } else { ReadCmdPacket(); } return !is_err_; } private: void ReadReplyPacket() { pkt_->type.reply.errorCode = ReadInt16(); pkt_->type.reply.data = ReadRemaining(); } void ReadCmdPacket() { pkt_->type.cmd.cmdSet = ReadByte(); pkt_->type.cmd.cmd = ReadByte(); pkt_->type.cmd.data = ReadRemaining(); } template T HandleResult(IOResult res, T val, T fail) { switch (res) { case IOResult::kError: is_err_ = true; return fail; case IOResult::kOk: return val; case IOResult::kEOF: is_eof_ = true; pkt_->type.cmd.len = 0; return fail; case IOResult::kInterrupt: transport_->DT_IO_ERROR("Failed to read, concurrent close!"); is_err_ = true; return fail; } } jbyte* ReadRemaining() { if (is_eof_ || is_err_) { return nullptr; } jbyte* out = nullptr; jint rem = pkt_->type.cmd.len - 11; CHECK_GE(rem, 0); if (rem == 0) { return nullptr; } else { out = reinterpret_cast(transport_->Alloc(rem)); IOResult res = transport_->ReadFully(out, rem); jbyte* ret = HandleResult(res, out, static_cast(nullptr)); if (ret != out) { transport_->Free(out); } return ret; } } jbyte ReadByte() { if (is_eof_ || is_err_) { return -1; } jbyte out; IOResult res = transport_->ReadFully(&out, sizeof(out)); return HandleResult(res, NetworkToHost(out), static_cast(-1)); } jshort ReadInt16() { if (is_eof_ || is_err_) { return -1; } jshort out; IOResult res = transport_->ReadFully(&out, sizeof(out)); return HandleResult(res, NetworkToHost(out), static_cast(-1)); } jint ReadInt32() { if (is_eof_ || is_err_) { return -1; } jint out; IOResult res = transport_->ReadFully(&out, sizeof(out)); return HandleResult(res, NetworkToHost(out), -1); } FdForwardTransport* transport_; jdwpPacket* pkt_; bool is_eof_; bool is_err_; }; jdwpTransportError FdForwardTransport::ReadPacket(jdwpPacket* pkt) { if (pkt == nullptr) { return ERR(ILLEGAL_ARGUMENT); } PacketReader reader(this, pkt); if (reader.ReadFully()) { return OK; } else { return ERR(IO_ERROR); } } // A class that writes a packet to the transport. class PacketWriter { public: PacketWriter(FdForwardTransport* transport, const jdwpPacket* pkt) : transport_(transport), pkt_(pkt), data_() {} bool WriteFully() { PushInt32(pkt_->type.cmd.len); PushInt32(pkt_->type.cmd.id); PushByte(pkt_->type.cmd.flags); if ((pkt_->type.reply.flags & JDWPTRANSPORT_FLAGS_REPLY) == JDWPTRANSPORT_FLAGS_REPLY) { PushInt16(pkt_->type.reply.errorCode); PushData(pkt_->type.reply.data, pkt_->type.reply.len - 11); } else { PushByte(pkt_->type.cmd.cmdSet); PushByte(pkt_->type.cmd.cmd); PushData(pkt_->type.cmd.data, pkt_->type.cmd.len - 11); } IOResult res = transport_->WriteFully(data_.data(), data_.size()); return res == IOResult::kOk; } private: void PushInt32(int32_t data) { data = HostToNetwork(data); PushData(&data, sizeof(data)); } void PushInt16(int16_t data) { data = HostToNetwork(data); PushData(&data, sizeof(data)); } void PushByte(jbyte data) { data_.push_back(HostToNetwork(data)); } void PushData(void* d, size_t size) { uint8_t* bytes = reinterpret_cast(d); data_.insert(data_.end(), bytes, bytes + size); } FdForwardTransport* transport_; const jdwpPacket* pkt_; std::vector data_; }; jdwpTransportError FdForwardTransport::WritePacket(const jdwpPacket* pkt) { if (pkt == nullptr) { return ERR(ILLEGAL_ARGUMENT); } PacketWriter writer(this, pkt); if (writer.WriteFully()) { return OK; } else { return ERR(IO_ERROR); } } jboolean FdForwardTransport::IsOpen() { return state_ == TransportState::kOpen; } void* FdForwardTransport::Alloc(size_t s) { return mem_.alloc(s); } void FdForwardTransport::Free(void* data) { mem_.free(data); } jdwpTransportError FdForwardTransport::GetLastError(/*out*/char** err) { std::string data = global_last_error_; *err = reinterpret_cast(Alloc(data.size() + 1)); strlcpy(*err, data.c_str(), data.size() + 1); return OK; } static FdForwardTransport* AsFdForward(jdwpTransportEnv* env) { return reinterpret_cast(env); } static jdwpTransportError ParseAddress(const std::string& addr, /*out*/int* listen_sock) { if (!android::base::ParseInt(addr.c_str(), listen_sock) || *listen_sock < 0) { LOG(ERROR) << "address format is not " << addr; return ERR(ILLEGAL_ARGUMENT); } return OK; } class JdwpTransportFunctions { public: static jdwpTransportError GetCapabilities(jdwpTransportEnv* env ATTRIBUTE_UNUSED, /*out*/ JDWPTransportCapabilities* capabilities_ptr) { // We don't support any of the optional capabilities (can_timeout_attach, can_timeout_accept, // can_timeout_handshake) so just return a zeroed capabilities ptr. // TODO We should maybe support these timeout options. memset(capabilities_ptr, 0, sizeof(JDWPTransportCapabilities)); return OK; } // Address is static jdwpTransportError Attach(jdwpTransportEnv* env, const char* address, jlong attach_timeout ATTRIBUTE_UNUSED, jlong handshake_timeout ATTRIBUTE_UNUSED) { if (address == nullptr || *address == '\0') { return ERR(ILLEGAL_ARGUMENT); } int listen_fd; jdwpTransportError err = ParseAddress(address, &listen_fd); if (err != OK) { return err; } return AsFdForward(env)->PerformAttach(listen_fd); } static jdwpTransportError StartListening(jdwpTransportEnv* env, const char* address, /*out*/ char** actual_address) { if (address == nullptr || *address == '\0') { return ERR(ILLEGAL_ARGUMENT); } int listen_fd; jdwpTransportError err = ParseAddress(address, &listen_fd); if (err != OK) { return err; } err = AsFdForward(env)->SetupListen(listen_fd); if (err != OK) { return err; } if (actual_address != nullptr) { *actual_address = reinterpret_cast(AsFdForward(env)->Alloc(strlen(address) + 1)); memcpy(*actual_address, address, strlen(address) + 1); } return OK; } static jdwpTransportError StopListening(jdwpTransportEnv* env) { return AsFdForward(env)->StopListening(); } static jdwpTransportError Accept(jdwpTransportEnv* env, jlong accept_timeout ATTRIBUTE_UNUSED, jlong handshake_timeout ATTRIBUTE_UNUSED) { return AsFdForward(env)->Accept(); } static jboolean IsOpen(jdwpTransportEnv* env) { return AsFdForward(env)->IsOpen(); } static jdwpTransportError Close(jdwpTransportEnv* env) { return AsFdForward(env)->Close(); } static jdwpTransportError ReadPacket(jdwpTransportEnv* env, jdwpPacket *pkt) { return AsFdForward(env)->ReadPacket(pkt); } static jdwpTransportError WritePacket(jdwpTransportEnv* env, const jdwpPacket* pkt) { return AsFdForward(env)->WritePacket(pkt); } static jdwpTransportError GetLastError(jdwpTransportEnv* env, char** error) { return AsFdForward(env)->GetLastError(error); } }; // The actual struct holding all the entrypoints into the jdwpTransport interface. const jdwpTransportNativeInterface_ gTransportInterface = { nullptr, // reserved1 JdwpTransportFunctions::GetCapabilities, JdwpTransportFunctions::Attach, JdwpTransportFunctions::StartListening, JdwpTransportFunctions::StopListening, JdwpTransportFunctions::Accept, JdwpTransportFunctions::IsOpen, JdwpTransportFunctions::Close, JdwpTransportFunctions::ReadPacket, JdwpTransportFunctions::WritePacket, JdwpTransportFunctions::GetLastError, }; extern "C" JNIEXPORT jint JNICALL jdwpTransport_OnLoad(JavaVM* vm ATTRIBUTE_UNUSED, jdwpTransportCallback* cb, jint version, jdwpTransportEnv** /*out*/env) { if (version != JDWPTRANSPORT_VERSION_1_0) { LOG(ERROR) << "unknown version " << version; return JNI_EVERSION; } void* data = cb->alloc(sizeof(FdForwardTransport)); if (data == nullptr) { LOG(ERROR) << "Failed to allocate data for transport!"; return JNI_ENOMEM; } FdForwardTransport* transport = new (data) FdForwardTransport(cb); transport->functions = &gTransportInterface; *env = transport; return JNI_OK; } } // namespace dt_fd_forward