Skip to content

Commit b475e34

Browse files
committed
[lldb-dap] Implement runInTerminal for Windows
Currently, the named pipe is passed by name and a transient ofstream is constructed at each I/O request. This assumes, - Blocking semantics: FIFO I/O waits for the other side to connect. - Buffered semantics: Closing one side does not discard existing data. The former can be replaced by WaitNamedPipe/ConnectNamedPipe on Win32, but the second cannot be easily worked around. It is also impossible to have another "keep-alive" pipe server instance, as server-client pairs are fixed on connection on Win32 and the client may get connected to it instead of the real one. Refactor FifoFile[IO] to use an open file handles rather than file name.
1 parent c3fc41c commit b475e34

File tree

5 files changed

+138
-47
lines changed

5 files changed

+138
-47
lines changed

lldb/tools/lldb-dap/FifoFiles.cpp

Lines changed: 92 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@
99
#include "FifoFiles.h"
1010
#include "JSONUtils.h"
1111

12-
#if !defined(_WIN32)
12+
#include "llvm/Support/FileSystem.h"
13+
14+
#if defined(_WIN32)
15+
#include <Windows.h>
16+
#include <fcntl.h>
17+
#include <io.h>
18+
#else
1319
#include <sys/stat.h>
1420
#include <sys/types.h>
1521
#include <unistd.h>
@@ -24,26 +30,65 @@ using namespace llvm;
2430

2531
namespace lldb_dap {
2632

27-
FifoFile::FifoFile(StringRef path) : m_path(path) {}
33+
std::error_code EC;
2834

35+
FifoFile::FifoFile(StringRef path)
36+
: m_path(path), m_file(fopen(path.data(), "r+")) {
37+
if (m_file == nullptr) {
38+
EC = std::error_code(errno, std::generic_category());
39+
llvm::errs() << "Failed to open fifo file: " << path << EC.message()
40+
<< "\n";
41+
return;
42+
}
43+
if (setvbuf(m_file, NULL, _IONBF, 0))
44+
llvm::errs() << "Error setting unbuffered mode on C FILE\n";
45+
}
46+
FifoFile::FifoFile(StringRef path, FILE *f) : m_path(path), m_file(f) {}
2947
FifoFile::~FifoFile() {
48+
fclose(m_file);
3049
#if !defined(_WIN32)
3150
unlink(m_path.c_str());
3251
#endif
52+
// Unreferenced named pipes are deleted automatically on Win32
3353
}
3454

35-
Expected<std::shared_ptr<FifoFile>> CreateFifoFile(StringRef path) {
36-
#if defined(_WIN32)
37-
return createStringError(inconvertibleErrorCode(), "Unimplemented");
55+
// This probably belongs to llvm::sys::fs as another FSEntity type
56+
std::error_code createNamedPipe(const Twine &Prefix, StringRef Suffix,
57+
int &ResultFd,
58+
SmallVectorImpl<char> &ResultPath) {
59+
const char *Middle = Suffix.empty() ? "-%%%%%%" : "-%%%%%%.";
60+
auto EC = sys::fs::getPotentiallyUniqueFileName(
61+
#ifdef _WIN32
62+
"\\\\.\\pipe\\LOCAL\\"
63+
#else
64+
"/tmp/"
65+
#endif
66+
+ Prefix + Middle + Suffix,
67+
ResultPath);
68+
if (EC)
69+
return EC;
70+
ResultPath.push_back(0);
71+
const char *path = ResultPath.data();
72+
#ifdef _WIN32
73+
HANDLE h = ::CreateNamedPipeA(
74+
path, PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE,
75+
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 1024, 1024, 0, NULL);
76+
if (h == INVALID_HANDLE_VALUE)
77+
return std::error_code(::GetLastError(), std::system_category());
78+
ResultFd = _open_osfhandle((intptr_t)h, _O_TEXT | _O_RDWR);
79+
if (ResultFd == -1)
80+
return std::error_code(::GetLastError(), std::system_category());
3881
#else
39-
if (int err = mkfifo(path.data(), 0600))
40-
return createStringError(std::error_code(err, std::generic_category()),
41-
"Couldn't create fifo file: %s", path.data());
42-
return std::make_shared<FifoFile>(path);
82+
if (mkfifo(path, 0600) == -1)
83+
return std::error_code(errno, std::generic_category());
84+
EC = openFileForWrite(ResultPath, ResultFd, sys::fs::CD_OpenExisting, sys::fs::OF_None, 0600);
85+
if (EC)
86+
return EC;
4387
#endif
88+
return std::error_code();
4489
}
4590

46-
FifoFileIO::FifoFileIO(StringRef fifo_file, StringRef other_endpoint_name)
91+
FifoFileIO::FifoFileIO(FifoFile fifo_file, StringRef other_endpoint_name)
4792
: m_fifo_file(fifo_file), m_other_endpoint_name(other_endpoint_name) {}
4893

4994
Expected<json::Value> FifoFileIO::ReadJSON(std::chrono::milliseconds timeout) {
@@ -52,13 +97,20 @@ Expected<json::Value> FifoFileIO::ReadJSON(std::chrono::milliseconds timeout) {
5297
std::optional<std::string> line;
5398
std::future<void> *future =
5499
new std::future<void>(std::async(std::launch::async, [&]() {
55-
std::ifstream reader(m_fifo_file, std::ifstream::in);
56-
std::string buffer;
57-
std::getline(reader, buffer);
58-
if (!buffer.empty())
59-
line = buffer;
100+
rewind(m_fifo_file.m_file);
101+
constexpr size_t buffer_size = 2048;
102+
char buffer[buffer_size];
103+
char *ptr = fgets(buffer, buffer_size, m_fifo_file.m_file);
104+
if (ptr == nullptr || *ptr == 0)
105+
return;
106+
size_t len = strlen(buffer);
107+
if (len <= 1)
108+
return;
109+
buffer[len - 1] = '\0'; // remove newline
110+
line = buffer;
60111
}));
61-
if (future->wait_for(timeout) == std::future_status::timeout || !line)
112+
113+
if (future->wait_for(timeout) == std::future_status::timeout)
62114
// Indeed this is a leak, but it's intentional. "future" obj destructor
63115
// will block on waiting for the worker thread to join. And the worker
64116
// thread might be stuck in blocking I/O. Intentionally leaking the obj
@@ -69,6 +121,11 @@ Expected<json::Value> FifoFileIO::ReadJSON(std::chrono::milliseconds timeout) {
69121
return createStringError(inconvertibleErrorCode(),
70122
"Timed out trying to get messages from the " +
71123
m_other_endpoint_name);
124+
if (!line) {
125+
return createStringError(inconvertibleErrorCode(),
126+
"Failed to get messages from the " +
127+
m_other_endpoint_name);
128+
}
72129
delete future;
73130
return json::parse(*line);
74131
}
@@ -78,8 +135,12 @@ Error FifoFileIO::SendJSON(const json::Value &json,
78135
bool done = false;
79136
std::future<void> *future =
80137
new std::future<void>(std::async(std::launch::async, [&]() {
81-
std::ofstream writer(m_fifo_file, std::ofstream::out);
82-
writer << JSONToString(json) << std::endl;
138+
rewind(m_fifo_file.m_file);
139+
auto payload = JSONToString(json);
140+
if (fputs(payload.c_str(), m_fifo_file.m_file) == EOF ||
141+
fputc('\n', m_fifo_file.m_file) == EOF) {
142+
return;
143+
}
83144
done = true;
84145
}));
85146
if (future->wait_for(timeout) == std::future_status::timeout || !done) {
@@ -98,4 +159,17 @@ Error FifoFileIO::SendJSON(const json::Value &json,
98159
return Error::success();
99160
}
100161

162+
Error FifoFileIO::WaitForPeer() {
163+
#ifdef _WIN32
164+
if (!::ConnectNamedPipe((HANDLE)_get_osfhandle(fileno(m_fifo_file.m_file)),
165+
NULL) &&
166+
GetLastError() != ERROR_PIPE_CONNECTED) {
167+
return createStringError(
168+
std::error_code(GetLastError(), std::system_category()),
169+
"Failed to connect to the " + m_other_endpoint_name);
170+
}
171+
#endif
172+
return Error::success();
173+
}
174+
101175
} // namespace lldb_dap

lldb/tools/lldb-dap/FifoFiles.h

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111

1212
#include "llvm/Support/Error.h"
1313
#include "llvm/Support/JSON.h"
14+
#include "llvm/Support/raw_ostream.h"
1415

1516
#include <chrono>
17+
#include <fstream>
1618

1719
namespace lldb_dap {
1820

@@ -21,21 +23,18 @@ namespace lldb_dap {
2123
/// The file is destroyed when the destructor is invoked.
2224
struct FifoFile {
2325
FifoFile(llvm::StringRef path);
26+
FifoFile(llvm::StringRef path, FILE *f);
27+
// FifoFile(llvm::StringRef path, FILE *f);
2428

2529
~FifoFile();
2630

2731
std::string m_path;
32+
FILE *m_file;
2833
};
2934

30-
/// Create a fifo file in the filesystem.
31-
///
32-
/// \param[in] path
33-
/// The path for the fifo file.
34-
///
35-
/// \return
36-
/// A \a std::shared_ptr<FifoFile> if the file could be created, or an
37-
/// \a llvm::Error in case of failures.
38-
llvm::Expected<std::shared_ptr<FifoFile>> CreateFifoFile(llvm::StringRef path);
35+
std::error_code createNamedPipe(const llvm::Twine &Prefix,
36+
llvm::StringRef Suffix, int &ResultFd,
37+
llvm::SmallVectorImpl<char> &ResultPath);
3938

4039
class FifoFileIO {
4140
public:
@@ -45,7 +44,7 @@ class FifoFileIO {
4544
/// \param[in] other_endpoint_name
4645
/// A human readable name for the other endpoint that will communicate
4746
/// using this file. This is used for error messages.
48-
FifoFileIO(llvm::StringRef fifo_file, llvm::StringRef other_endpoint_name);
47+
FifoFileIO(FifoFile fifo_file, llvm::StringRef other_endpoint_name);
4948

5049
/// Read the next JSON object from the underlying input fifo file.
5150
///
@@ -71,12 +70,14 @@ class FifoFileIO {
7170
/// \return
7271
/// An \a llvm::Error object indicating whether the data was consumed by
7372
/// a reader or not.
74-
llvm::Error SendJSON(
75-
const llvm::json::Value &json,
76-
std::chrono::milliseconds timeout = std::chrono::milliseconds(20000));
73+
llvm::Error
74+
SendJSON(const llvm::json::Value &json,
75+
std::chrono::milliseconds timeout = std::chrono::milliseconds(2000));
76+
77+
llvm::Error WaitForPeer();
7778

78-
private:
79-
std::string m_fifo_file;
79+
// private:
80+
FifoFile m_fifo_file;
8081
std::string m_other_endpoint_name;
8182
};
8283

lldb/tools/lldb-dap/RunInTerminal.cpp

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ static Error ToError(const RunInTerminalMessage &message) {
9797

9898
RunInTerminalLauncherCommChannel::RunInTerminalLauncherCommChannel(
9999
StringRef comm_file)
100-
: m_io(comm_file, "debug adaptor") {}
100+
: m_io(FifoFile(comm_file), "debug adaptor") {}
101101

102102
Error RunInTerminalLauncherCommChannel::WaitUntilDebugAdaptorAttaches(
103103
std::chrono::milliseconds timeout) {
@@ -122,9 +122,13 @@ void RunInTerminalLauncherCommChannel::NotifyError(StringRef error) {
122122
}
123123

124124
RunInTerminalDebugAdapterCommChannel::RunInTerminalDebugAdapterCommChannel(
125-
StringRef comm_file)
125+
FifoFile comm_file)
126126
: m_io(comm_file, "runInTerminal launcher") {}
127127

128+
Error RunInTerminalDebugAdapterCommChannel::WaitForLauncher() {
129+
return m_io.WaitForPeer();
130+
}
131+
128132
// Can't use \a std::future<llvm::Error> because it doesn't compile on Windows
129133
std::future<lldb::SBError>
130134
RunInTerminalDebugAdapterCommChannel::NotifyDidAttach() {
@@ -158,13 +162,24 @@ std::string RunInTerminalDebugAdapterCommChannel::GetLauncherError() {
158162
}
159163

160164
Expected<std::shared_ptr<FifoFile>> CreateRunInTerminalCommFile() {
165+
int comm_fd;
161166
SmallString<256> comm_file;
162-
if (std::error_code EC = sys::fs::getPotentiallyUniqueTempFileName(
163-
"lldb-dap-run-in-terminal-comm", "", comm_file))
167+
if (std::error_code EC = createNamedPipe("lldb-dap-run-in-terminal-comm", "",
168+
comm_fd, comm_file))
164169
return createStringError(EC, "Error making unique file name for "
165170
"runInTerminal communication files");
166-
167-
return CreateFifoFile(comm_file.str());
171+
FILE *cf = fdopen(comm_fd, "r+");
172+
if (setvbuf(cf, NULL, _IONBF, 0))
173+
return createStringError(std::error_code(errno, std::generic_category()),
174+
"Error setting unbuffered mode on C FILE");
175+
// There is no portable way to conjure an ofstream from HANDLE, so use FILE *
176+
// llvm::raw_fd_stream does not support getline() and there is no
177+
// llvm::buffer_istream
178+
179+
if (cf == NULL)
180+
return createStringError(std::error_code(errno, std::generic_category()),
181+
"Error converting file descriptor to C FILE");
182+
return std::make_shared<FifoFile>(comm_file, cf);
168183
}
169184

170185
} // namespace lldb_dap

lldb/tools/lldb-dap/RunInTerminal.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ class RunInTerminalLauncherCommChannel {
9898

9999
class RunInTerminalDebugAdapterCommChannel {
100100
public:
101-
RunInTerminalDebugAdapterCommChannel(llvm::StringRef comm_file);
101+
RunInTerminalDebugAdapterCommChannel(FifoFile comm_file);
102102

103103
/// Notify the runInTerminal launcher that it was attached.
104104
///
@@ -118,6 +118,8 @@ class RunInTerminalDebugAdapterCommChannel {
118118
/// default error message if a certain timeout if reached.
119119
std::string GetLauncherError();
120120

121+
llvm::Error WaitForLauncher();
122+
121123
private:
122124
FifoFileIO m_io;
123125
};

lldb/tools/lldb-dap/lldb-dap.cpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2007,7 +2007,7 @@ llvm::Error request_runInTerminal(DAP &dap,
20072007
return comm_file_or_err.takeError();
20082008
FifoFile &comm_file = *comm_file_or_err.get();
20092009

2010-
RunInTerminalDebugAdapterCommChannel comm_channel(comm_file.m_path);
2010+
RunInTerminalDebugAdapterCommChannel comm_channel(comm_file);
20112011

20122012
lldb::pid_t debugger_pid = LLDB_INVALID_PROCESS_ID;
20132013
#if !defined(_WIN32)
@@ -2025,14 +2025,19 @@ llvm::Error request_runInTerminal(DAP &dap,
20252025
}
20262026
});
20272027

2028+
auto err = comm_channel.WaitForLauncher();
2029+
if (err)
2030+
return err;
20282031
if (llvm::Expected<lldb::pid_t> pid = comm_channel.GetLauncherPid())
20292032
attach_info.SetProcessID(*pid);
20302033
else
20312034
return pid.takeError();
20322035

20332036
dap.debugger.SetAsync(false);
20342037
lldb::SBError error;
2038+
llvm::errs() << "Attaching to target\n";
20352039
dap.target.Attach(attach_info, error);
2040+
llvm::errs() << "Attached to target\n";
20362041

20372042
if (error.Fail())
20382043
return llvm::createStringError(llvm::inconvertibleErrorCode(),
@@ -4860,11 +4865,6 @@ static void printHelp(LLDBDAPOptTable &table, llvm::StringRef tool_name) {
48604865
static void LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg,
48614866
llvm::StringRef comm_file,
48624867
lldb::pid_t debugger_pid, char *argv[]) {
4863-
#if defined(_WIN32)
4864-
llvm::errs() << "runInTerminal is only supported on POSIX systems\n";
4865-
exit(EXIT_FAILURE);
4866-
#else
4867-
48684868
// On Linux with the Yama security module enabled, a process can only attach
48694869
// to its descendants by default. In the runInTerminal case the target
48704870
// process is launched by the client so we need to allow tracing explicitly.
@@ -4899,7 +4899,6 @@ static void LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg,
48994899
comm_channel.NotifyError(error);
49004900
llvm::errs() << error << "\n";
49014901
exit(EXIT_FAILURE);
4902-
#endif
49034902
}
49044903

49054904
/// used only by TestVSCode_redirection_to_console.py

0 commit comments

Comments
 (0)