Skip to content

Commit d7ac556

Browse files
committed
wip
1 parent 3439c70 commit d7ac556

File tree

1 file changed

+144
-77
lines changed

1 file changed

+144
-77
lines changed

src/vcpkg/base/system.process.cpp

Lines changed: 144 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ extern char** environ;
2828
#if defined(_WIN32)
2929
#include <Psapi.h>
3030
#include <TlHelp32.h>
31+
#include <sddl.h>
3132
#pragma comment(lib, "Advapi32")
3233
#else
3334
#include <fcntl.h>
@@ -57,36 +58,13 @@ namespace
5758

5859
static std::atomic_int32_t debug_id_counter{1000};
5960

61+
#if !defined(_WIN32)
6062
struct ChildStdinTracker
6163
{
6264
StringView input;
6365
std::size_t offset;
6466

65-
#if defined(_WIN32)
6667
// Write a hunk of data to `target`. If there is no more input to write, returns `true`.
67-
ExpectedL<bool> do_write(HANDLE target)
68-
{
69-
const auto this_write = input.size() - offset;
70-
if (this_write != 0)
71-
{
72-
const auto this_write_clamped = static_cast<DWORD>(this_write > MAXDWORD ? MAXDWORD : this_write);
73-
DWORD actually_written;
74-
if (!WriteFile(target,
75-
static_cast<const void*>(input.data() + offset),
76-
this_write_clamped,
77-
&actually_written,
78-
nullptr))
79-
{
80-
return format_system_error_message("WriteFile", GetLastError());
81-
}
82-
83-
offset += actually_written;
84-
}
85-
86-
return offset == input.size();
87-
}
88-
#else // ^^^ _WIN32 // !_WIN32 vvv
89-
// Write a hunk of data to `target`. If there is no more input to write, returns `true`.
9068
ExpectedL<bool> do_write(int target)
9169
{
9270
const auto this_write = input.size() - offset;
@@ -107,8 +85,8 @@ namespace
10785

10886
return offset == input.size();
10987
}
110-
#endif // ^^^ !_WIN32
11188
};
89+
#endif // ^^^ !_WIN32
11290
} // unnamed namespace
11391

11492
namespace vcpkg
@@ -896,6 +874,12 @@ namespace
896874
return windows_create_process(debug_id, cmd_line, wd, env, dwCreationFlags, startup_info_ex);
897875
}
898876

877+
struct OverlappedStatus : OVERLAPPED
878+
{
879+
DWORD expected_write;
880+
HANDLE* target;
881+
};
882+
899883
struct ProcessInfoAndPipes
900884
{
901885
ProcessInfo proc_info;
@@ -916,22 +900,6 @@ namespace
916900
close_handle_mark_invalid(child_handles[1]);
917901
}
918902

919-
void handle_child_read(ChildStdinTracker& stdin_tracker)
920-
{
921-
auto maybe_done = stdin_tracker.do_write(child_handles[0]);
922-
if (auto done = maybe_done.get())
923-
{
924-
if (*done)
925-
{
926-
close_handle_mark_invalid(child_handles[0]);
927-
}
928-
929-
return;
930-
}
931-
932-
vcpkg::Checks::unreachable(VCPKG_LINE_INFO);
933-
}
934-
935903
template<class Function>
936904
void handle_child_write(const Function& f, char* buf, DWORD buffer_size, Encoding encoding)
937905
{
@@ -968,34 +936,92 @@ namespace
968936
}
969937

970938
template<class Function>
971-
int wait_and_stream_output(StringView input, const Function& f, Encoding encoding)
939+
int wait_and_stream_output(const char* input, DWORD input_size, const Function& f, Encoding encoding)
972940
{
973-
ChildStdinTracker input_tracker{input, 0};
941+
static const auto stdin_completion_routine =
942+
[](DWORD dwErrorCode, DWORD dwNumberOfBytesTransferred, LPOVERLAPPED pOverlapped) {
943+
const auto status = static_cast<OverlappedStatus*>(pOverlapped);
944+
switch (dwErrorCode)
945+
{
946+
case 0:
947+
// OK, done
948+
Checks::check_exit(VCPKG_LINE_INFO, dwNumberOfBytesTransferred == status->expected_write);
949+
break;
950+
case ERROR_BROKEN_PIPE:
951+
case ERROR_OPERATION_ABORTED:
952+
// OK, child didn't want all the data
953+
break;
954+
default: Checks::unreachable(VCPKG_LINE_INFO, "stdin write completion");
955+
}
956+
957+
close_handle_mark_invalid(*status->target);
958+
};
959+
960+
OverlappedStatus stdin_write{};
961+
stdin_write.expected_write = input_size;
962+
stdin_write.target = &child_handles[0];
963+
if (input_size == 0)
964+
{
965+
close_handle_mark_invalid(child_handles[0]);
966+
}
967+
else
968+
{
969+
stdin_write.expected_write = input_size;
970+
if (WriteFileEx(child_handles[0], input, input_size, &stdin_write, stdin_completion_routine))
971+
{
972+
// write completed synchronously
973+
close_handle_mark_invalid(child_handles[0]);
974+
}
975+
else
976+
{
977+
DWORD last_error = GetLastError();
978+
if (last_error != ERROR_IO_PENDING)
979+
{
980+
vcpkg::Checks::unreachable(VCPKG_LINE_INFO,
981+
fmt::format("Writing stdin failed: {:x}", last_error));
982+
}
983+
}
984+
}
985+
974986
static constexpr DWORD buffer_size = 1024 * 32;
975987
char buf[buffer_size];
976-
while (child_handles[0] != INVALID_HANDLE_VALUE && child_handles[1] != INVALID_HANDLE_VALUE)
988+
while (child_handles[1] != INVALID_HANDLE_VALUE)
977989
{
978-
switch (WaitForMultipleObjects(2, child_handles, FALSE, INFINITE))
990+
switch (WaitForSingleObjectEx(child_handles[1], INFINITE, TRUE))
979991
{
980-
case WAIT_OBJECT_0: handle_child_read(input_tracker); break;
981-
case WAIT_OBJECT_0 + 1: handle_child_write(f, buf, buffer_size, encoding); break;
992+
case WAIT_OBJECT_0: handle_child_write(f, buf, buffer_size, encoding); break;
993+
case WAIT_IO_COMPLETION:
994+
// stdin might have completed, that's OK
995+
break;
996+
case WAIT_FAILED:
997+
vcpkg::Checks::unreachable(VCPKG_LINE_INFO,
998+
fmt::format("Waiting for stdout failed: {:x}", GetLastError()));
999+
break;
9821000
default: vcpkg::Checks::unreachable(VCPKG_LINE_INFO); break;
9831001
}
9841002
}
9851003

986-
while (child_handles[0] != INVALID_HANDLE_VALUE &&
987-
WaitForSingleObject(child_handles[0], INFINITE) == WAIT_OBJECT_0)
1004+
auto child_exit_code = proc_info.wait();
1005+
if (child_handles[0] != INVALID_HANDLE_VALUE)
9881006
{
989-
handle_child_read(input_tracker);
990-
}
1007+
if (!CancelIo(child_handles[0]))
1008+
{
1009+
vcpkg::Checks::unreachable(VCPKG_LINE_INFO,
1010+
fmt::format("Cancel stdin write failed: {:x}", GetLastError()));
1011+
}
9911012

992-
while (child_handles[1] != INVALID_HANDLE_VALUE &&
993-
WaitForSingleObject(child_handles[1], INFINITE) == WAIT_OBJECT_0)
994-
{
995-
handle_child_write(f, buf, buffer_size, encoding);
1013+
DWORD transferred;
1014+
if (GetOverlappedResult(child_handles[0], &stdin_write, &transferred, TRUE))
1015+
{
1016+
stdin_completion_routine(0, transferred, &stdin_write);
1017+
}
1018+
else
1019+
{
1020+
stdin_completion_routine(GetLastError(), transferred, &stdin_write);
1021+
}
9961022
}
9971023

998-
return proc_info.wait();
1024+
return child_exit_code;
9991025
}
10001026
};
10011027

@@ -1063,34 +1089,68 @@ namespace
10631089
}
10641090
};
10651091

1092+
struct CreatorOnlySecurityDescriptor
1093+
{
1094+
PSECURITY_DESCRIPTOR sd;
1095+
1096+
CreatorOnlySecurityDescriptor() : sd{}
1097+
{
1098+
// DACL:
1099+
// ACE 0: Allow; FILE_READ;;;OWNER_RIGHTS
1100+
Checks::check_exit(
1101+
VCPKG_LINE_INFO,
1102+
ConvertStringSecurityDescriptorToSecurityDescriptorW(L"D:(A;;FR;;;OW)", SDDL_REVISION_1, &sd, 0));
1103+
}
1104+
1105+
~CreatorOnlySecurityDescriptor() { LocalFree(sd); }
1106+
1107+
CreatorOnlySecurityDescriptor(const CreatorOnlySecurityDescriptor&) = delete;
1108+
CreatorOnlySecurityDescriptor& operator=(const CreatorOnlySecurityDescriptor&) = delete;
1109+
};
1110+
10661111
ExpectedL<ProcessInfoAndPipes> windows_create_process_redirect(std::int32_t debug_id,
10671112
StringView cmd_line,
10681113
const WorkingDirectory& wd,
10691114
const Environment& env,
10701115
DWORD dwCreationFlags) noexcept
10711116
{
1117+
static CreatorOnlySecurityDescriptor creator_owner_sd;
10721118
ProcessInfoAndPipes ret;
10731119
StartupInfoWithPipes startup_info_ex;
1074-
SECURITY_ATTRIBUTES saAttr{sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE};
1120+
SECURITY_ATTRIBUTES namedSa{sizeof(SECURITY_ATTRIBUTES), creator_owner_sd.sd, FALSE};
10751121

1076-
// Create a pipe for the child process's STDOUT.
1077-
if (!CreatePipe(&ret.child_handles[1], &startup_info_ex.StartupInfo.hStdOutput, &saAttr, 0))
1122+
// Create a pipe for the child process's STDIN.
1123+
std::wstring pipe_name{Strings::to_utf16(
1124+
fmt::format(R"(\\.\pipe\local\vcpkg-stdin-A8B4F218-4DB1-4A3E-8E5B-C41F1633F627-{})", debug_id))};
1125+
ret.child_handles[0] =
1126+
CreateNamedPipeW(pipe_name.c_str(),
1127+
PIPE_ACCESS_OUTBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED,
1128+
PIPE_TYPE_BYTE | PIPE_REJECT_REMOTE_CLIENTS,
1129+
1, // nMaxInstances
1130+
65535, // nOutBufferSize
1131+
0, // nInBufferSize (unused / PIPE_ACCESS_OUTBOUND)
1132+
0, // nDefaultTimeout (only for WaitPipe; unused)
1133+
&namedSa);
1134+
1135+
if (ret.child_handles[0] == INVALID_HANDLE_VALUE)
10781136
{
1079-
return format_system_error_message("CreatePipe stdout", GetLastError());
1137+
return format_system_error_message("CreateNamedPipeW stdin", GetLastError());
10801138
}
10811139

1082-
// Create a pipe for the child process's STDIN.
1083-
if (!CreatePipe(&startup_info_ex.StartupInfo.hStdInput, &ret.child_handles[0], &saAttr, 0))
1140+
SECURITY_ATTRIBUTES anonymousSa{sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE};
1141+
startup_info_ex.StartupInfo.hStdInput =
1142+
CreateFileW(pipe_name.c_str(), FILE_GENERIC_READ, 0, &anonymousSa, OPEN_EXISTING, 0, 0);
1143+
1144+
if (startup_info_ex.StartupInfo.hStdInput == INVALID_HANDLE_VALUE)
10841145
{
1085-
return format_system_error_message("CreatePipe stdin", GetLastError());
1146+
return format_system_error_message("CreateFileW stdin", GetLastError());
10861147
}
10871148

1088-
DWORD nonblocking_flags = PIPE_NOWAIT;
1089-
if (!SetNamedPipeHandleState(ret.child_handles[0], &nonblocking_flags, nullptr, nullptr))
1149+
// Create a pipe for the child process's STDOUT.
1150+
if (!CreatePipe(&ret.child_handles[1], &startup_info_ex.StartupInfo.hStdOutput, &anonymousSa, 0))
10901151
{
1091-
return format_system_error_message("SetNamedPipeHandleState", GetLastError());
1152+
return format_system_error_message("CreatePipe stdout", GetLastError());
10921153
}
1093-
10941154
startup_info_ex.StartupInfo.hStdError = startup_info_ex.StartupInfo.hStdOutput;
10951155

10961156
// Ensure that only the write handle to STDOUT and the read handle to STDIN are inherited.
@@ -1441,19 +1501,26 @@ namespace vcpkg
14411501
using vcpkg::g_ctrl_c_state;
14421502

14431503
g_ctrl_c_state.transition_to_spawn_process();
1504+
std::wstring as_utf16;
1505+
if (encoding == Encoding::Utf16)
1506+
{
1507+
as_utf16 = Strings::to_utf16(stdin_content);
1508+
stdin_content =
1509+
StringView{reinterpret_cast<const char*>(as_utf16.data()), as_utf16.size() * sizeof(wchar_t)};
1510+
}
1511+
1512+
auto stdin_content_size_raw = stdin_content.size();
1513+
if (stdin_content_size_raw > MAXDWORD)
1514+
{
1515+
return format_system_error_message("WriteFileEx", ERROR_INSUFFICIENT_BUFFER);
1516+
}
1517+
1518+
auto stdin_content_size = static_cast<DWORD>(stdin_content_size_raw);
1519+
14441520
ExpectedL<int> exit_code =
14451521
windows_create_process_redirect(debug_id, cmd_line.command_line(), wd, env, 0)
14461522
.map([&](ProcessInfoAndPipes&& output) {
1447-
if (encoding == Encoding::Utf16)
1448-
{
1449-
auto as_utf16 = Strings::to_utf16(stdin_content);
1450-
return output.wait_and_stream_output(StringView{reinterpret_cast<const char*>(as_utf16.data()),
1451-
as_utf16.size() * sizeof(wchar_t)},
1452-
data_cb,
1453-
encoding);
1454-
}
1455-
1456-
return output.wait_and_stream_output(stdin_content, data_cb, encoding);
1523+
return output.wait_and_stream_output(stdin_content.data(), stdin_content_size, data_cb, encoding);
14571524
});
14581525
g_ctrl_c_state.transition_from_spawn_process();
14591526
#else // ^^^ _WIN32 // !_WIN32 vvv

0 commit comments

Comments
 (0)