| //===-- ConnectionFileDescriptorPosix.cpp ---------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #if defined(__APPLE__) |
| // Enable this special support for Apple builds where we can have unlimited |
| // select bounds. We tried switching to poll() and kqueue and we were panicing |
| // the kernel, so we have to stick with select for now. |
| #define _DARWIN_UNLIMITED_SELECT |
| #endif |
| |
| #include "lldb/Host/posix/ConnectionFileDescriptorPosix.h" |
| #include "lldb/Host/Config.h" |
| #include "lldb/Host/FileSystem.h" |
| #include "lldb/Host/Socket.h" |
| #include "lldb/Host/SocketAddress.h" |
| #include "lldb/Utility/LLDBLog.h" |
| #include "lldb/Utility/SelectHelper.h" |
| #include "lldb/Utility/Timeout.h" |
| |
| #include <cerrno> |
| #include <cstdlib> |
| #include <cstring> |
| #include <fcntl.h> |
| #include <sys/types.h> |
| |
| #if LLDB_ENABLE_POSIX |
| #include <termios.h> |
| #include <unistd.h> |
| #endif |
| |
| #include <memory> |
| #include <sstream> |
| |
| #include "llvm/Support/Errno.h" |
| #include "llvm/Support/ErrorHandling.h" |
| #if defined(__APPLE__) |
| #include "llvm/ADT/SmallVector.h" |
| #endif |
| #include "lldb/Host/Host.h" |
| #include "lldb/Host/Socket.h" |
| #include "lldb/Host/common/TCPSocket.h" |
| #include "lldb/Host/common/UDPSocket.h" |
| #include "lldb/Utility/Log.h" |
| #include "lldb/Utility/StreamString.h" |
| #include "lldb/Utility/Timer.h" |
| |
| using namespace lldb; |
| using namespace lldb_private; |
| |
| ConnectionFileDescriptor::ConnectionFileDescriptor() |
| : Connection(), m_pipe(), m_mutex(), m_shutting_down(false) { |
| Log *log(GetLog(LLDBLog::Connection | LLDBLog::Object)); |
| LLDB_LOGF(log, "%p ConnectionFileDescriptor::ConnectionFileDescriptor ()", |
| static_cast<void *>(this)); |
| } |
| |
| ConnectionFileDescriptor::ConnectionFileDescriptor(int fd, bool owns_fd) |
| : Connection(), m_pipe(), m_mutex(), m_shutting_down(false) { |
| m_io_sp = |
| std::make_shared<NativeFile>(fd, File::eOpenOptionReadWrite, owns_fd); |
| |
| Log *log(GetLog(LLDBLog::Connection | LLDBLog::Object)); |
| LLDB_LOGF(log, |
| "%p ConnectionFileDescriptor::ConnectionFileDescriptor (fd = " |
| "%i, owns_fd = %i)", |
| static_cast<void *>(this), fd, owns_fd); |
| OpenCommandPipe(); |
| } |
| |
| ConnectionFileDescriptor::ConnectionFileDescriptor(Socket *socket) |
| : Connection(), m_pipe(), m_mutex(), m_shutting_down(false) { |
| InitializeSocket(socket); |
| } |
| |
| ConnectionFileDescriptor::~ConnectionFileDescriptor() { |
| Log *log(GetLog(LLDBLog::Connection | LLDBLog::Object)); |
| LLDB_LOGF(log, "%p ConnectionFileDescriptor::~ConnectionFileDescriptor ()", |
| static_cast<void *>(this)); |
| Disconnect(nullptr); |
| CloseCommandPipe(); |
| } |
| |
| void ConnectionFileDescriptor::OpenCommandPipe() { |
| CloseCommandPipe(); |
| |
| Log *log = GetLog(LLDBLog::Connection); |
| // Make the command file descriptor here: |
| Status result = m_pipe.CreateNew(/*child_processes_inherit=*/false); |
| if (!result.Success()) { |
| LLDB_LOGF(log, |
| "%p ConnectionFileDescriptor::OpenCommandPipe () - could not " |
| "make pipe: %s", |
| static_cast<void *>(this), result.AsCString()); |
| } else { |
| LLDB_LOGF(log, |
| "%p ConnectionFileDescriptor::OpenCommandPipe() - success " |
| "readfd=%d writefd=%d", |
| static_cast<void *>(this), m_pipe.GetReadFileDescriptor(), |
| m_pipe.GetWriteFileDescriptor()); |
| } |
| } |
| |
| void ConnectionFileDescriptor::CloseCommandPipe() { |
| Log *log = GetLog(LLDBLog::Connection); |
| LLDB_LOGF(log, "%p ConnectionFileDescriptor::CloseCommandPipe()", |
| static_cast<void *>(this)); |
| |
| m_pipe.Close(); |
| } |
| |
| bool ConnectionFileDescriptor::IsConnected() const { |
| return m_io_sp && m_io_sp->IsValid(); |
| } |
| |
| ConnectionStatus ConnectionFileDescriptor::Connect(llvm::StringRef path, |
| Status *error_ptr) { |
| return Connect(path, [](llvm::StringRef) {}, error_ptr); |
| } |
| |
| ConnectionStatus |
| ConnectionFileDescriptor::Connect(llvm::StringRef path, |
| socket_id_callback_type socket_id_callback, |
| Status *error_ptr) { |
| std::lock_guard<std::recursive_mutex> guard(m_mutex); |
| Log *log = GetLog(LLDBLog::Connection); |
| LLDB_LOGF(log, "%p ConnectionFileDescriptor::Connect (url = '%s')", |
| static_cast<void *>(this), path.str().c_str()); |
| |
| OpenCommandPipe(); |
| |
| if (path.empty()) { |
| if (error_ptr) |
| *error_ptr = Status::FromErrorString("invalid connect arguments"); |
| return eConnectionStatusError; |
| } |
| |
| llvm::StringRef scheme; |
| std::tie(scheme, path) = path.split("://"); |
| |
| if (!path.empty()) { |
| auto method = |
| llvm::StringSwitch<ConnectionStatus (ConnectionFileDescriptor::*)( |
| llvm::StringRef, socket_id_callback_type, Status *)>(scheme) |
| .Case("listen", &ConnectionFileDescriptor::AcceptTCP) |
| .Cases("accept", "unix-accept", |
| &ConnectionFileDescriptor::AcceptNamedSocket) |
| .Case("unix-abstract-accept", |
| &ConnectionFileDescriptor::AcceptAbstractSocket) |
| .Cases("connect", "tcp-connect", |
| &ConnectionFileDescriptor::ConnectTCP) |
| .Case("udp", &ConnectionFileDescriptor::ConnectUDP) |
| .Case("unix-connect", &ConnectionFileDescriptor::ConnectNamedSocket) |
| .Case("unix-abstract-connect", |
| &ConnectionFileDescriptor::ConnectAbstractSocket) |
| #if LLDB_ENABLE_POSIX |
| .Case("fd", &ConnectionFileDescriptor::ConnectFD) |
| .Case("file", &ConnectionFileDescriptor::ConnectFile) |
| .Case("serial", &ConnectionFileDescriptor::ConnectSerialPort) |
| #endif |
| .Default(nullptr); |
| |
| if (method) { |
| if (error_ptr) |
| *error_ptr = Status(); |
| return (this->*method)(path, socket_id_callback, error_ptr); |
| } |
| } |
| |
| if (error_ptr) |
| *error_ptr = Status::FromErrorStringWithFormat( |
| "unsupported connection URL: '%s'", path.str().c_str()); |
| return eConnectionStatusError; |
| } |
| |
| bool ConnectionFileDescriptor::InterruptRead() { |
| size_t bytes_written = 0; |
| Status result = m_pipe.Write("i", 1, bytes_written); |
| return result.Success(); |
| } |
| |
| ConnectionStatus ConnectionFileDescriptor::Disconnect(Status *error_ptr) { |
| Log *log = GetLog(LLDBLog::Connection); |
| LLDB_LOGF(log, "%p ConnectionFileDescriptor::Disconnect ()", |
| static_cast<void *>(this)); |
| |
| ConnectionStatus status = eConnectionStatusSuccess; |
| |
| if (!IsConnected()) { |
| LLDB_LOGF( |
| log, "%p ConnectionFileDescriptor::Disconnect(): Nothing to disconnect", |
| static_cast<void *>(this)); |
| return eConnectionStatusSuccess; |
| } |
| |
| // Try to get the ConnectionFileDescriptor's mutex. If we fail, that is |
| // quite likely because somebody is doing a blocking read on our file |
| // descriptor. If that's the case, then send the "q" char to the command |
| // file channel so the read will wake up and the connection will then know to |
| // shut down. |
| std::unique_lock<std::recursive_mutex> locker(m_mutex, std::defer_lock); |
| if (!locker.try_lock()) { |
| if (m_pipe.CanWrite()) { |
| size_t bytes_written = 0; |
| Status result = m_pipe.Write("q", 1, bytes_written); |
| LLDB_LOGF(log, |
| "%p ConnectionFileDescriptor::Disconnect(): Couldn't get " |
| "the lock, sent 'q' to %d, error = '%s'.", |
| static_cast<void *>(this), m_pipe.GetWriteFileDescriptor(), |
| result.AsCString()); |
| } else if (log) { |
| LLDB_LOGF(log, |
| "%p ConnectionFileDescriptor::Disconnect(): Couldn't get the " |
| "lock, but no command pipe is available.", |
| static_cast<void *>(this)); |
| } |
| locker.lock(); |
| } |
| |
| // Prevents reads and writes during shutdown. |
| m_shutting_down = true; |
| |
| Status error = m_io_sp->Close(); |
| if (error.Fail()) |
| status = eConnectionStatusError; |
| if (error_ptr) |
| *error_ptr = std::move(error); |
| |
| // Close any pipes we were using for async interrupts |
| m_pipe.Close(); |
| |
| m_uri.clear(); |
| m_shutting_down = false; |
| return status; |
| } |
| |
| size_t ConnectionFileDescriptor::Read(void *dst, size_t dst_len, |
| const Timeout<std::micro> &timeout, |
| ConnectionStatus &status, |
| Status *error_ptr) { |
| Log *log = GetLog(LLDBLog::Connection); |
| |
| std::unique_lock<std::recursive_mutex> locker(m_mutex, std::defer_lock); |
| if (!locker.try_lock()) { |
| LLDB_LOGF(log, |
| "%p ConnectionFileDescriptor::Read () failed to get the " |
| "connection lock.", |
| static_cast<void *>(this)); |
| if (error_ptr) |
| *error_ptr = Status::FromErrorString( |
| "failed to get the connection lock for read."); |
| |
| status = eConnectionStatusTimedOut; |
| return 0; |
| } |
| |
| if (m_shutting_down) { |
| if (error_ptr) |
| *error_ptr = Status::FromErrorString("shutting down"); |
| status = eConnectionStatusError; |
| return 0; |
| } |
| |
| status = BytesAvailable(timeout, error_ptr); |
| if (status != eConnectionStatusSuccess) |
| return 0; |
| |
| Status error; |
| size_t bytes_read = dst_len; |
| error = m_io_sp->Read(dst, bytes_read); |
| |
| if (log) { |
| LLDB_LOGF(log, |
| "%p ConnectionFileDescriptor::Read() fd = %" PRIu64 |
| ", dst = %p, dst_len = %" PRIu64 ") => %" PRIu64 ", error = %s", |
| static_cast<void *>(this), |
| static_cast<uint64_t>(m_io_sp->GetWaitableHandle()), |
| static_cast<void *>(dst), static_cast<uint64_t>(dst_len), |
| static_cast<uint64_t>(bytes_read), error.AsCString()); |
| } |
| |
| if (bytes_read == 0) { |
| error.Clear(); // End-of-file. Do not automatically close; pass along for |
| // the end-of-file handlers. |
| status = eConnectionStatusEndOfFile; |
| } |
| |
| if (error_ptr) |
| *error_ptr = error.Clone(); |
| |
| if (error.Fail()) { |
| uint32_t error_value = error.GetError(); |
| switch (error_value) { |
| case EAGAIN: // The file was marked for non-blocking I/O, and no data were |
| // ready to be read. |
| if (m_io_sp->GetFdType() == IOObject::eFDTypeSocket) |
| status = eConnectionStatusTimedOut; |
| else |
| status = eConnectionStatusSuccess; |
| return 0; |
| |
| case EFAULT: // Buf points outside the allocated address space. |
| case EINTR: // A read from a slow device was interrupted before any data |
| // arrived by the delivery of a signal. |
| case EINVAL: // The pointer associated with fildes was negative. |
| case EIO: // An I/O error occurred while reading from the file system. |
| // The process group is orphaned. |
| // The file is a regular file, nbyte is greater than 0, the |
| // starting position is before the end-of-file, and the |
| // starting position is greater than or equal to the offset |
| // maximum established for the open file descriptor |
| // associated with fildes. |
| case EISDIR: // An attempt is made to read a directory. |
| case ENOBUFS: // An attempt to allocate a memory buffer fails. |
| case ENOMEM: // Insufficient memory is available. |
| status = eConnectionStatusError; |
| break; // Break to close.... |
| |
| case ENOENT: // no such file or directory |
| case EBADF: // fildes is not a valid file or socket descriptor open for |
| // reading. |
| case ENXIO: // An action is requested of a device that does not exist.. |
| // A requested action cannot be performed by the device. |
| case ECONNRESET: // The connection is closed by the peer during a read |
| // attempt on a socket. |
| case ENOTCONN: // A read is attempted on an unconnected socket. |
| status = eConnectionStatusLostConnection; |
| break; // Break to close.... |
| |
| case ETIMEDOUT: // A transmission timeout occurs during a read attempt on a |
| // socket. |
| status = eConnectionStatusTimedOut; |
| return 0; |
| |
| default: |
| LLDB_LOG(log, "this = {0}, unexpected error: {1}", this, |
| llvm::sys::StrError(error_value)); |
| status = eConnectionStatusError; |
| break; // Break to close.... |
| } |
| |
| return 0; |
| } |
| return bytes_read; |
| } |
| |
| size_t ConnectionFileDescriptor::Write(const void *src, size_t src_len, |
| ConnectionStatus &status, |
| Status *error_ptr) { |
| Log *log = GetLog(LLDBLog::Connection); |
| LLDB_LOGF(log, |
| "%p ConnectionFileDescriptor::Write (src = %p, src_len = %" PRIu64 |
| ")", |
| static_cast<void *>(this), static_cast<const void *>(src), |
| static_cast<uint64_t>(src_len)); |
| |
| if (!IsConnected()) { |
| if (error_ptr) |
| *error_ptr = Status::FromErrorString("not connected"); |
| status = eConnectionStatusNoConnection; |
| return 0; |
| } |
| |
| if (m_shutting_down) { |
| if (error_ptr) |
| *error_ptr = Status::FromErrorString("shutting down"); |
| status = eConnectionStatusError; |
| return 0; |
| } |
| |
| Status error; |
| |
| size_t bytes_sent = src_len; |
| error = m_io_sp->Write(src, bytes_sent); |
| |
| if (log) { |
| LLDB_LOGF(log, |
| "%p ConnectionFileDescriptor::Write(fd = %" PRIu64 |
| ", src = %p, src_len = %" PRIu64 ") => %" PRIu64 " (error = %s)", |
| static_cast<void *>(this), |
| static_cast<uint64_t>(m_io_sp->GetWaitableHandle()), |
| static_cast<const void *>(src), static_cast<uint64_t>(src_len), |
| static_cast<uint64_t>(bytes_sent), error.AsCString()); |
| } |
| |
| if (error_ptr) |
| *error_ptr = error.Clone(); |
| |
| if (error.Fail()) { |
| switch (error.GetError()) { |
| case EAGAIN: |
| case EINTR: |
| status = eConnectionStatusSuccess; |
| return 0; |
| |
| case ECONNRESET: // The connection is closed by the peer during a read |
| // attempt on a socket. |
| case ENOTCONN: // A read is attempted on an unconnected socket. |
| status = eConnectionStatusLostConnection; |
| break; // Break to close.... |
| |
| default: |
| status = eConnectionStatusError; |
| break; // Break to close.... |
| } |
| |
| return 0; |
| } |
| |
| status = eConnectionStatusSuccess; |
| return bytes_sent; |
| } |
| |
| std::string ConnectionFileDescriptor::GetURI() { return m_uri; } |
| |
| // This ConnectionFileDescriptor::BytesAvailable() uses select() via |
| // SelectHelper |
| // |
| // PROS: |
| // - select is consistent across most unix platforms |
| // - The Apple specific version allows for unlimited fds in the fd_sets by |
| // setting the _DARWIN_UNLIMITED_SELECT define prior to including the |
| // required header files. |
| // CONS: |
| // - on non-Apple platforms, only supports file descriptors up to FD_SETSIZE. |
| // This implementation will assert if it runs into that hard limit to let |
| // users know that another ConnectionFileDescriptor::BytesAvailable() should |
| // be used or a new version of ConnectionFileDescriptor::BytesAvailable() |
| // should be written for the system that is running into the limitations. |
| |
| ConnectionStatus |
| ConnectionFileDescriptor::BytesAvailable(const Timeout<std::micro> &timeout, |
| Status *error_ptr) { |
| // Don't need to take the mutex here separately since we are only called from |
| // Read. If we ever get used more generally we will need to lock here as |
| // well. |
| |
| Log *log = GetLog(LLDBLog::Connection); |
| LLDB_LOG(log, "this = {0}, timeout = {1}", this, timeout); |
| |
| // Make a copy of the file descriptors to make sure we don't have another |
| // thread change these values out from under us and cause problems in the |
| // loop below where like in FS_SET() |
| const IOObject::WaitableHandle handle = m_io_sp->GetWaitableHandle(); |
| const int pipe_fd = m_pipe.GetReadFileDescriptor(); |
| |
| if (handle != IOObject::kInvalidHandleValue) { |
| SelectHelper select_helper; |
| if (timeout) |
| select_helper.SetTimeout(*timeout); |
| |
| select_helper.FDSetRead(handle); |
| #if defined(_WIN32) |
| // select() won't accept pipes on Windows. The entire Windows codepath |
| // needs to be converted over to using WaitForMultipleObjects and event |
| // HANDLEs, but for now at least this will allow ::select() to not return |
| // an error. |
| const bool have_pipe_fd = false; |
| #else |
| const bool have_pipe_fd = pipe_fd >= 0; |
| #endif |
| if (have_pipe_fd) |
| select_helper.FDSetRead(pipe_fd); |
| |
| while (handle == m_io_sp->GetWaitableHandle()) { |
| |
| Status error = select_helper.Select(); |
| |
| if (error_ptr) |
| *error_ptr = error.Clone(); |
| |
| if (error.Fail()) { |
| switch (error.GetError()) { |
| case EBADF: // One of the descriptor sets specified an invalid |
| // descriptor. |
| return eConnectionStatusLostConnection; |
| |
| case EINVAL: // The specified time limit is invalid. One of its |
| // components is negative or too large. |
| default: // Other unknown error |
| return eConnectionStatusError; |
| |
| case ETIMEDOUT: |
| return eConnectionStatusTimedOut; |
| |
| case EAGAIN: // The kernel was (perhaps temporarily) unable to |
| // allocate the requested number of file descriptors, or |
| // we have non-blocking IO |
| case EINTR: // A signal was delivered before the time limit |
| // expired and before any of the selected events occurred. |
| break; // Lets keep reading to until we timeout |
| } |
| } else { |
| if (select_helper.FDIsSetRead(handle)) |
| return eConnectionStatusSuccess; |
| |
| if (select_helper.FDIsSetRead(pipe_fd)) { |
| // There is an interrupt or exit command in the command pipe Read the |
| // data from that pipe: |
| char c; |
| |
| ssize_t bytes_read = |
| llvm::sys::RetryAfterSignal(-1, ::read, pipe_fd, &c, 1); |
| assert(bytes_read == 1); |
| UNUSED_IF_ASSERT_DISABLED(bytes_read); |
| switch (c) { |
| case 'q': |
| LLDB_LOGF(log, |
| "%p ConnectionFileDescriptor::BytesAvailable() " |
| "got data: %c from the command channel.", |
| static_cast<void *>(this), c); |
| return eConnectionStatusEndOfFile; |
| case 'i': |
| // Interrupt the current read |
| return eConnectionStatusInterrupted; |
| } |
| } |
| } |
| } |
| } |
| |
| if (error_ptr) |
| *error_ptr = Status::FromErrorString("not connected"); |
| return eConnectionStatusLostConnection; |
| } |
| |
| lldb::ConnectionStatus ConnectionFileDescriptor::AcceptSocket( |
| Socket::SocketProtocol socket_protocol, llvm::StringRef socket_name, |
| llvm::function_ref<void(Socket &)> post_listen_callback, |
| Status *error_ptr) { |
| Status error; |
| std::unique_ptr<Socket> listening_socket = |
| Socket::Create(socket_protocol, error); |
| Socket *accepted_socket; |
| |
| if (!error.Fail()) |
| error = listening_socket->Listen(socket_name, 5); |
| |
| if (!error.Fail()) { |
| post_listen_callback(*listening_socket); |
| error = listening_socket->Accept(/*timeout=*/std::nullopt, accepted_socket); |
| } |
| |
| if (!error.Fail()) { |
| m_io_sp.reset(accepted_socket); |
| m_uri.assign(socket_name.str()); |
| return eConnectionStatusSuccess; |
| } |
| |
| if (error_ptr) |
| *error_ptr = error.Clone(); |
| return eConnectionStatusError; |
| } |
| |
| lldb::ConnectionStatus |
| ConnectionFileDescriptor::ConnectSocket(Socket::SocketProtocol socket_protocol, |
| llvm::StringRef socket_name, |
| Status *error_ptr) { |
| Status error; |
| std::unique_ptr<Socket> socket = Socket::Create(socket_protocol, error); |
| |
| if (!error.Fail()) |
| error = socket->Connect(socket_name); |
| |
| if (!error.Fail()) { |
| m_io_sp = std::move(socket); |
| m_uri.assign(socket_name.str()); |
| return eConnectionStatusSuccess; |
| } |
| |
| if (error_ptr) |
| *error_ptr = error.Clone(); |
| return eConnectionStatusError; |
| } |
| |
| ConnectionStatus ConnectionFileDescriptor::AcceptNamedSocket( |
| llvm::StringRef socket_name, socket_id_callback_type socket_id_callback, |
| Status *error_ptr) { |
| return AcceptSocket( |
| Socket::ProtocolUnixDomain, socket_name, |
| [socket_id_callback, socket_name](Socket &listening_socket) { |
| socket_id_callback(socket_name); |
| }, |
| error_ptr); |
| } |
| |
| ConnectionStatus ConnectionFileDescriptor::ConnectNamedSocket( |
| llvm::StringRef socket_name, socket_id_callback_type socket_id_callback, |
| Status *error_ptr) { |
| return ConnectSocket(Socket::ProtocolUnixDomain, socket_name, error_ptr); |
| } |
| |
| ConnectionStatus ConnectionFileDescriptor::AcceptAbstractSocket( |
| llvm::StringRef socket_name, socket_id_callback_type socket_id_callback, |
| Status *error_ptr) { |
| return AcceptSocket( |
| Socket::ProtocolUnixAbstract, socket_name, |
| [socket_id_callback, socket_name](Socket &listening_socket) { |
| socket_id_callback(socket_name); |
| }, |
| error_ptr); |
| } |
| |
| lldb::ConnectionStatus ConnectionFileDescriptor::ConnectAbstractSocket( |
| llvm::StringRef socket_name, socket_id_callback_type socket_id_callback, |
| Status *error_ptr) { |
| return ConnectSocket(Socket::ProtocolUnixAbstract, socket_name, error_ptr); |
| } |
| |
| ConnectionStatus |
| ConnectionFileDescriptor::AcceptTCP(llvm::StringRef socket_name, |
| socket_id_callback_type socket_id_callback, |
| Status *error_ptr) { |
| ConnectionStatus ret = AcceptSocket( |
| Socket::ProtocolTcp, socket_name, |
| [socket_id_callback](Socket &listening_socket) { |
| uint16_t port = |
| static_cast<TCPSocket &>(listening_socket).GetLocalPortNumber(); |
| socket_id_callback(std::to_string(port)); |
| }, |
| error_ptr); |
| if (ret == eConnectionStatusSuccess) |
| m_uri.assign( |
| static_cast<TCPSocket *>(m_io_sp.get())->GetRemoteConnectionURI()); |
| return ret; |
| } |
| |
| ConnectionStatus |
| ConnectionFileDescriptor::ConnectTCP(llvm::StringRef socket_name, |
| socket_id_callback_type socket_id_callback, |
| Status *error_ptr) { |
| return ConnectSocket(Socket::ProtocolTcp, socket_name, error_ptr); |
| } |
| |
| ConnectionStatus |
| ConnectionFileDescriptor::ConnectUDP(llvm::StringRef s, |
| socket_id_callback_type socket_id_callback, |
| Status *error_ptr) { |
| if (error_ptr) |
| *error_ptr = Status(); |
| llvm::Expected<std::unique_ptr<UDPSocket>> socket = Socket::UdpConnect(s); |
| if (!socket) { |
| if (error_ptr) |
| *error_ptr = Status::FromError(socket.takeError()); |
| else |
| LLDB_LOG_ERROR(GetLog(LLDBLog::Connection), socket.takeError(), |
| "tcp connect failed: {0}"); |
| return eConnectionStatusError; |
| } |
| m_io_sp = std::move(*socket); |
| m_uri.assign(std::string(s)); |
| return eConnectionStatusSuccess; |
| } |
| |
| ConnectionStatus |
| ConnectionFileDescriptor::ConnectFD(llvm::StringRef s, |
| socket_id_callback_type socket_id_callback, |
| Status *error_ptr) { |
| #if LLDB_ENABLE_POSIX |
| // Just passing a native file descriptor within this current process that |
| // is already opened (possibly from a service or other source). |
| int fd = -1; |
| |
| if (!s.getAsInteger(0, fd)) { |
| // We have what looks to be a valid file descriptor, but we should make |
| // sure it is. We currently are doing this by trying to get the flags |
| // from the file descriptor and making sure it isn't a bad fd. |
| errno = 0; |
| int flags = ::fcntl(fd, F_GETFL, 0); |
| if (flags == -1 || errno == EBADF) { |
| if (error_ptr) |
| *error_ptr = Status::FromErrorStringWithFormat( |
| "stale file descriptor: %s", s.str().c_str()); |
| m_io_sp.reset(); |
| return eConnectionStatusError; |
| } else { |
| // Don't take ownership of a file descriptor that gets passed to us |
| // since someone else opened the file descriptor and handed it to us. |
| // TODO: Since are using a URL to open connection we should |
| // eventually parse options using the web standard where we have |
| // "fd://123?opt1=value;opt2=value" and we can have an option be |
| // "owns=1" or "owns=0" or something like this to allow us to specify |
| // this. For now, we assume we must assume we don't own it. |
| |
| std::unique_ptr<TCPSocket> tcp_socket; |
| tcp_socket = std::make_unique<TCPSocket>(fd, /*should_close=*/false); |
| // Try and get a socket option from this file descriptor to see if |
| // this is a socket and set m_is_socket accordingly. |
| int resuse; |
| bool is_socket = |
| !!tcp_socket->GetOption(SOL_SOCKET, SO_REUSEADDR, resuse); |
| if (is_socket) |
| m_io_sp = std::move(tcp_socket); |
| else |
| m_io_sp = |
| std::make_shared<NativeFile>(fd, File::eOpenOptionReadWrite, false); |
| m_uri = s.str(); |
| return eConnectionStatusSuccess; |
| } |
| } |
| |
| if (error_ptr) |
| *error_ptr = Status::FromErrorStringWithFormat( |
| "invalid file descriptor: \"%s\"", s.str().c_str()); |
| m_io_sp.reset(); |
| return eConnectionStatusError; |
| #endif // LLDB_ENABLE_POSIX |
| llvm_unreachable("this function should be only called w/ LLDB_ENABLE_POSIX"); |
| } |
| |
| ConnectionStatus ConnectionFileDescriptor::ConnectFile( |
| llvm::StringRef s, socket_id_callback_type socket_id_callback, |
| Status *error_ptr) { |
| #if LLDB_ENABLE_POSIX |
| std::string addr_str = s.str(); |
| // file:///PATH |
| int fd = FileSystem::Instance().Open(addr_str.c_str(), O_RDWR); |
| if (fd == -1) { |
| if (error_ptr) |
| *error_ptr = Status::FromErrno(); |
| return eConnectionStatusError; |
| } |
| |
| if (::isatty(fd)) { |
| // Set up serial terminal emulation |
| struct termios options; |
| ::tcgetattr(fd, &options); |
| |
| // Set port speed to the available maximum |
| #ifdef B115200 |
| ::cfsetospeed(&options, B115200); |
| ::cfsetispeed(&options, B115200); |
| #elif B57600 |
| ::cfsetospeed(&options, B57600); |
| ::cfsetispeed(&options, B57600); |
| #elif B38400 |
| ::cfsetospeed(&options, B38400); |
| ::cfsetispeed(&options, B38400); |
| #else |
| #error "Maximum Baud rate is Unknown" |
| #endif |
| |
| // Raw input, disable echo and signals |
| options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); |
| |
| // Make sure only one character is needed to return from a read |
| options.c_cc[VMIN] = 1; |
| options.c_cc[VTIME] = 0; |
| |
| llvm::sys::RetryAfterSignal(-1, ::tcsetattr, fd, TCSANOW, &options); |
| } |
| |
| m_io_sp = std::make_shared<NativeFile>(fd, File::eOpenOptionReadWrite, true); |
| return eConnectionStatusSuccess; |
| #endif // LLDB_ENABLE_POSIX |
| llvm_unreachable("this function should be only called w/ LLDB_ENABLE_POSIX"); |
| } |
| |
| ConnectionStatus ConnectionFileDescriptor::ConnectSerialPort( |
| llvm::StringRef s, socket_id_callback_type socket_id_callback, |
| Status *error_ptr) { |
| #if LLDB_ENABLE_POSIX |
| llvm::StringRef path, qs; |
| // serial:///PATH?k1=v1&k2=v2... |
| std::tie(path, qs) = s.split('?'); |
| |
| llvm::Expected<SerialPort::Options> serial_options = |
| SerialPort::OptionsFromURL(qs); |
| if (!serial_options) { |
| if (error_ptr) |
| *error_ptr = Status::FromError(serial_options.takeError()); |
| else |
| llvm::consumeError(serial_options.takeError()); |
| return eConnectionStatusError; |
| } |
| |
| int fd = FileSystem::Instance().Open(path.str().c_str(), O_RDWR); |
| if (fd == -1) { |
| if (error_ptr) |
| *error_ptr = Status::FromErrno(); |
| return eConnectionStatusError; |
| } |
| |
| llvm::Expected<std::unique_ptr<SerialPort>> serial_sp = SerialPort::Create( |
| fd, File::eOpenOptionReadWrite, serial_options.get(), true); |
| if (!serial_sp) { |
| if (error_ptr) |
| *error_ptr = Status::FromError(serial_sp.takeError()); |
| else |
| llvm::consumeError(serial_sp.takeError()); |
| return eConnectionStatusError; |
| } |
| m_io_sp = std::move(serial_sp.get()); |
| |
| return eConnectionStatusSuccess; |
| #endif // LLDB_ENABLE_POSIX |
| llvm_unreachable("this function should be only called w/ LLDB_ENABLE_POSIX"); |
| } |
| |
| void ConnectionFileDescriptor::InitializeSocket(Socket *socket) { |
| m_io_sp.reset(socket); |
| m_uri = socket->GetRemoteConnectionURI(); |
| } |