|
2 | 2 |
|
3 | 3 | #include <arpa/inet.h>
|
4 | 4 | #include <assert.h>
|
| 5 | +#include <ctype.h> |
5 | 6 | #include <fcntl.h>
|
6 | 7 | #include <getopt.h>
|
7 | 8 | #include <locale.h>
|
|
25 | 26 | #include <atomic>
|
26 | 27 | #include <memory>
|
27 | 28 | #include <mutex>
|
| 29 | +#include <sstream> |
28 | 30 | #include <string>
|
29 | 31 | #include <thread>
|
30 | 32 | #include <utility>
|
@@ -128,9 +130,12 @@ static std::wstring mbsToWcs(const std::string &s) {
|
128 | 130 | return ret;
|
129 | 131 | }
|
130 | 132 |
|
131 |
| -static std::string wcsToMbs(const std::wstring &s) { |
| 133 | +static std::string wcsToMbs(const std::wstring &s, bool emptyOnError=false) { |
132 | 134 | const size_t len = wcstombs(nullptr, s.c_str(), 0);
|
133 | 135 | if (len == static_cast<size_t>(-1)) {
|
| 136 | + if (emptyOnError) { |
| 137 | + return {}; |
| 138 | + } |
134 | 139 | fatal("error: wcsToMbs: invalid string\n");
|
135 | 140 | }
|
136 | 141 | std::string ret;
|
@@ -748,13 +753,202 @@ static std::string formatErrorMessage(DWORD err) {
|
748 | 753 | return ret;
|
749 | 754 | }
|
750 | 755 |
|
| 756 | +struct PipeHandles { |
| 757 | + HANDLE rh; |
| 758 | + HANDLE wh; |
| 759 | +}; |
| 760 | + |
| 761 | +static PipeHandles createPipe() { |
| 762 | + SECURITY_ATTRIBUTES sa {}; |
| 763 | + sa.nLength = sizeof(sa); |
| 764 | + sa.bInheritHandle = TRUE; |
| 765 | + PipeHandles ret {}; |
| 766 | + const BOOL success = CreatePipe(&ret.rh, &ret.wh, &sa, 0); |
| 767 | + assert(success && "CreatePipe failed"); |
| 768 | + return ret; |
| 769 | +} |
| 770 | + |
| 771 | +class StartupInfoAttributeList { |
| 772 | +public: |
| 773 | + StartupInfoAttributeList(PPROC_THREAD_ATTRIBUTE_LIST &attrList, int count) { |
| 774 | + SIZE_T size {}; |
| 775 | + InitializeProcThreadAttributeList(nullptr, count, 0, &size); |
| 776 | + assert(size > 0 && "InitializeProcThreadAttributeList failed"); |
| 777 | + buffer_ = std::unique_ptr<char[]>(new char[size]); |
| 778 | + const BOOL success = InitializeProcThreadAttributeList(get(), count, 0, &size); |
| 779 | + assert(success && "InitializeProcThreadAttributeList failed"); |
| 780 | + attrList = get(); |
| 781 | + } |
| 782 | + StartupInfoAttributeList(const StartupInfoAttributeList &) = delete; |
| 783 | + StartupInfoAttributeList &operator=(const StartupInfoAttributeList &) = delete; |
| 784 | + ~StartupInfoAttributeList() { |
| 785 | + DeleteProcThreadAttributeList(get()); |
| 786 | + } |
| 787 | +private: |
| 788 | + PPROC_THREAD_ATTRIBUTE_LIST get() { |
| 789 | + return reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(buffer_.get()); |
| 790 | + } |
| 791 | + std::unique_ptr<char[]> buffer_; |
| 792 | +}; |
| 793 | + |
| 794 | +class StartupInfoInheritList { |
| 795 | +public: |
| 796 | + StartupInfoInheritList(PPROC_THREAD_ATTRIBUTE_LIST attrList, |
| 797 | + std::vector<HANDLE> &&inheritList) : |
| 798 | + inheritList_(std::move(inheritList)) { |
| 799 | + const BOOL success = UpdateProcThreadAttribute( |
| 800 | + attrList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, |
| 801 | + inheritList_.data(), inheritList_.size() * sizeof(HANDLE), |
| 802 | + nullptr, nullptr); |
| 803 | + assert(success && "UpdateProcThreadAttribute failed"); |
| 804 | + } |
| 805 | + StartupInfoInheritList(const StartupInfoInheritList &) = delete; |
| 806 | + StartupInfoInheritList &operator=(const StartupInfoInheritList &) = delete; |
| 807 | + ~StartupInfoInheritList() {} |
| 808 | +private: |
| 809 | + std::vector<HANDLE> inheritList_; |
| 810 | +}; |
| 811 | + |
| 812 | +// WSL bash will print an error if the user tries to run elevated and |
| 813 | +// non-elevated instances simultaneously, and maybe other situations. We'd |
| 814 | +// like to detect this situation and report the error back to the user. |
| 815 | +// |
| 816 | +// Two complications: |
| 817 | +// - WSL bash will print the error to stdout/stderr, but if the file is a |
| 818 | +// pipe, then WSL bash doesn't print it until it exits (presumably due to |
| 819 | +// block buffering). |
| 820 | +// - WSL bash puts up a prompt, "Press any key to continue", and it reads |
| 821 | +// that key from the attached console, not from stdin. |
| 822 | +// |
| 823 | +// This function spawns the frontend again and instructs it to attach to the |
| 824 | +// new WSL bash console and send it a return keypress. |
| 825 | +// |
| 826 | +// The HANDLE must be inheritable. |
| 827 | +static void spawnPressReturnProcess(HANDLE bashProcess) { |
| 828 | + const auto exePath = getModuleFileName(getCurrentModule()); |
| 829 | + std::wstring cmdline; |
| 830 | + cmdline.append(L"\""); |
| 831 | + cmdline.append(exePath); |
| 832 | + cmdline.append(L"\" --press-return "); |
| 833 | + cmdline.append(std::to_wstring(reinterpret_cast<uintptr_t>(bashProcess))); |
| 834 | + STARTUPINFOEXW sui {}; |
| 835 | + sui.StartupInfo.cb = sizeof(sui); |
| 836 | + StartupInfoAttributeList attrList { sui.lpAttributeList, 1 }; |
| 837 | + StartupInfoInheritList inheritList { sui.lpAttributeList, { bashProcess } }; |
| 838 | + PROCESS_INFORMATION pi {}; |
| 839 | + const BOOL success = CreateProcessW(exePath.c_str(), &cmdline[0], nullptr, nullptr, |
| 840 | + true, 0, nullptr, nullptr, &sui.StartupInfo, &pi); |
| 841 | + if (!success) { |
| 842 | + fprintf(stderr, "wslbridge warning: could not spawn: %s\n", wcsToMbs(cmdline).c_str()); |
| 843 | + } |
| 844 | + if (WaitForSingleObject(pi.hProcess, 10000) != WAIT_OBJECT_0) { |
| 845 | + fprintf(stderr, "wslbridge warning: process didn't exit after 10 seconds: %ls\n", |
| 846 | + cmdline.c_str()); |
| 847 | + } else { |
| 848 | + DWORD code {}; |
| 849 | + BOOL success = GetExitCodeProcess(pi.hProcess, &code); |
| 850 | + if (!success) { |
| 851 | + fprintf(stderr, "wslbridge warning: GetExitCodeProcess failed\n"); |
| 852 | + } else if (code != 0) { |
| 853 | + fprintf(stderr, "wslbridge warning: process failed: %ls\n", cmdline.c_str()); |
| 854 | + } |
| 855 | + } |
| 856 | + CloseHandle(pi.hProcess); |
| 857 | + CloseHandle(pi.hThread); |
| 858 | +} |
| 859 | + |
| 860 | +static int handlePressReturn(const char *pidStr) { |
| 861 | + // AttachConsole replaces STD_INPUT_HANDLE with a new console input |
| 862 | + // handle. See https://github.com/rprichard/win32-console-docs. The |
| 863 | + // bash.exe process has already started, but console creation and |
| 864 | + // process creation don't happen atomically, so poll for the console's |
| 865 | + // existence. |
| 866 | + auto str2handle = [](const char *str) { |
| 867 | + std::stringstream ss(str); |
| 868 | + uintptr_t n {}; |
| 869 | + ss >> n; |
| 870 | + return reinterpret_cast<HANDLE>(n); |
| 871 | + }; |
| 872 | + const HANDLE bashProcess = str2handle(pidStr); |
| 873 | + const DWORD bashPid = GetProcessId(bashProcess); |
| 874 | + FreeConsole(); |
| 875 | + for (int i = 0; i < 400; ++i) { |
| 876 | + if (WaitForSingleObject(bashProcess, 0) == WAIT_OBJECT_0) { |
| 877 | + // bash.exe has exited, give up immediately. |
| 878 | + return 0; |
| 879 | + } else if (AttachConsole(bashPid)) { |
| 880 | + std::array<INPUT_RECORD, 2> ir {}; |
| 881 | + ir[0].EventType = KEY_EVENT; |
| 882 | + ir[0].Event.KeyEvent.bKeyDown = TRUE; |
| 883 | + ir[0].Event.KeyEvent.wRepeatCount = 1; |
| 884 | + ir[0].Event.KeyEvent.wVirtualKeyCode = VK_RETURN; |
| 885 | + ir[0].Event.KeyEvent.wVirtualScanCode = MapVirtualKey(VK_RETURN, MAPVK_VK_TO_VSC); |
| 886 | + ir[0].Event.KeyEvent.uChar.UnicodeChar = '\r'; |
| 887 | + ir[1] = ir[0]; |
| 888 | + ir[1].Event.KeyEvent.bKeyDown = FALSE; |
| 889 | + DWORD actual {}; |
| 890 | + WriteConsoleInputW( |
| 891 | + GetStdHandle(STD_INPUT_HANDLE), |
| 892 | + ir.data(), ir.size(), &actual); |
| 893 | + return 0; |
| 894 | + } |
| 895 | + Sleep(25); |
| 896 | + } |
| 897 | + return 1; |
| 898 | +} |
| 899 | + |
| 900 | +static std::vector<char> readAllFromHandle(HANDLE h) { |
| 901 | + std::vector<char> ret; |
| 902 | + char buf[1024]; |
| 903 | + DWORD actual {}; |
| 904 | + while (ReadFile(h, buf, sizeof(buf), &actual, nullptr) && actual > 0) { |
| 905 | + ret.insert(ret.end(), buf, buf + actual); |
| 906 | + } |
| 907 | + return ret; |
| 908 | +} |
| 909 | + |
| 910 | +static std::tuple<DWORD, DWORD, DWORD> windowsVersion() { |
| 911 | + OSVERSIONINFO info {}; |
| 912 | + info.dwOSVersionInfoSize = sizeof(info); |
| 913 | + const BOOL success = GetVersionEx(&info); |
| 914 | + assert(success && "GetVersionEx failed"); |
| 915 | + if (info.dwMajorVersion == 6 && info.dwMinorVersion == 2) { |
| 916 | + // We want to distinguish between Windows 10.0.14393 and 10.0.15063, |
| 917 | + // but if the EXE doesn't have an appropriate manifest, then |
| 918 | + // GetVersionEx will report the lesser of 6.2 and the true version. |
| 919 | + fprintf(stderr, "wslbridge warning: GetVersionEx reports version 6.2 -- " |
| 920 | + "is wslbridge.exe properly manifested?\n"); |
| 921 | + } |
| 922 | + return std::make_tuple(info.dwMajorVersion, info.dwMinorVersion, info.dwBuildNumber); |
| 923 | +} |
| 924 | + |
| 925 | +static std::string replaceAll(std::string str, const std::string &from, const std::string &to) { |
| 926 | + size_t pos {}; |
| 927 | + while ((pos = str.find(from, pos)) != std::string::npos) { |
| 928 | + str = str.replace(pos, from.size(), to); |
| 929 | + pos += to.size(); |
| 930 | + } |
| 931 | + return str; |
| 932 | +} |
| 933 | + |
| 934 | +static std::string stripTrailing(std::string str) { |
| 935 | + while (!str.empty() && isspace(str.back())) { |
| 936 | + str.pop_back(); |
| 937 | + } |
| 938 | + return str; |
| 939 | +} |
| 940 | + |
751 | 941 | } // namespace
|
752 | 942 |
|
753 | 943 | int main(int argc, char *argv[]) {
|
754 | 944 | setlocale(LC_ALL, "");
|
755 | 945 | cygwin_internal(CW_SYNC_WINENV);
|
756 | 946 | g_wakeupFd = new WakeupFd();
|
757 | 947 |
|
| 948 | + if (argc == 3 && !strcmp(argv[1], "--press-return")) { |
| 949 | + return handlePressReturn(argv[2]); |
| 950 | + } |
| 951 | + |
758 | 952 | Environment env;
|
759 | 953 | std::string spawnCwd;
|
760 | 954 | enum class TtyRequest { Auto, Yes, No, Force } ttyRequest = TtyRequest::Auto;
|
@@ -916,40 +1110,85 @@ int main(int argc, char *argv[]) {
|
916 | 1110 | cmdLine.append(L"\" -c ");
|
917 | 1111 | appendBashArg(cmdLine, bashCmdLine);
|
918 | 1112 |
|
919 |
| - STARTUPINFOW sui = {}; |
920 |
| - sui.cb = sizeof(sui); |
| 1113 | + const auto outputPipe = createPipe(); |
| 1114 | + const auto errorPipe = createPipe(); |
| 1115 | + STARTUPINFOEXW sui {}; |
| 1116 | + sui.StartupInfo.cb = sizeof(sui); |
| 1117 | + StartupInfoAttributeList attrList { sui.lpAttributeList, 1 }; |
| 1118 | + StartupInfoInheritList inheritList { sui.lpAttributeList, |
| 1119 | + { outputPipe.wh, errorPipe.wh } |
| 1120 | + }; |
| 1121 | + |
| 1122 | + if (windowsVersion() >= std::make_tuple(10u, 0u, 15063u)) { |
| 1123 | + // WSL was first officially shipped in 14393, but in that version, |
| 1124 | + // bash.exe did not allow redirecting stdout/stderr to a pipe. |
| 1125 | + // Redirection is allowed starting with 15063, and we'd like to use it |
| 1126 | + // to help report errors. |
| 1127 | + sui.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; |
| 1128 | + sui.StartupInfo.hStdOutput = outputPipe.wh; |
| 1129 | + sui.StartupInfo.hStdError = errorPipe.wh; |
| 1130 | + } |
| 1131 | + |
921 | 1132 | PROCESS_INFORMATION pi = {};
|
922 | 1133 | BOOL success = CreateProcessW(bashPath.c_str(), &cmdLine[0], nullptr, nullptr,
|
923 |
| - false, |
| 1134 | + true, |
924 | 1135 | debugFork ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW,
|
925 |
| - nullptr, nullptr, &sui, &pi); |
| 1136 | + nullptr, nullptr, &sui.StartupInfo, &pi); |
926 | 1137 | if (!success) {
|
927 | 1138 | fatal("error starting bash.exe adapter: %s\n",
|
928 | 1139 | formatErrorMessage(GetLastError()).c_str());
|
929 | 1140 | }
|
930 | 1141 |
|
| 1142 | + CloseHandle(outputPipe.wh); |
| 1143 | + CloseHandle(errorPipe.wh); |
| 1144 | + success = SetHandleInformation(pi.hProcess, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); |
| 1145 | + assert(success && "SetHandleInformation failed"); |
| 1146 | + spawnPressReturnProcess(pi.hProcess); |
| 1147 | + |
931 | 1148 | std::atomic<bool> backendStarted = { false };
|
932 | 1149 |
|
933 | 1150 | // If the backend process exits before the frontend, then something has
|
934 | 1151 | // gone wrong.
|
935 | 1152 | const auto watchdog = std::thread([&]() {
|
936 | 1153 | WaitForSingleObject(pi.hProcess, INFINITE);
|
| 1154 | + |
| 1155 | + // Because bash.exe has exited, we know that the write ends of the |
| 1156 | + // output pipes are closed. Finish reading anything bash.exe wrote. |
| 1157 | + // bash.exe writes at least one error via stdout in UTF-16; |
| 1158 | + // wslbridge-backend could write to stderr in UTF-8. |
| 1159 | + auto outVec = readAllFromHandle(outputPipe.rh); |
| 1160 | + auto errVec = readAllFromHandle(errorPipe.rh); |
| 1161 | + std::wstring outWide(outVec.size() / sizeof(wchar_t), L'\0'); |
| 1162 | + memcpy(&outWide[0], outVec.data(), outWide.size() * sizeof(wchar_t)); |
| 1163 | + std::string out { wcsToMbs(outWide, true) }; |
| 1164 | + std::string err { errVec.begin(), errVec.end() }; |
| 1165 | + out = stripTrailing(replaceAll(out, "Press any key to continue...", "")); |
| 1166 | + err = stripTrailing(err); |
| 1167 | + |
| 1168 | + std::string msg; |
937 | 1169 | if (backendStarted) {
|
938 |
| - g_terminalState.fatal("\nwslbridge error: backend process died\n"); |
939 |
| - } |
940 |
| - std::string msg = "wslbridge error: failed to start backend process\n"; |
941 |
| - msg.append("note: backend program is at '"); |
942 |
| - msg.append(wcsToMbs(backendPathWin)); |
943 |
| - msg.append("'\n"); |
944 |
| - if (access(wcsToMbs(backendPathWin).c_str(), X_OK) == -1 && errno == EACCES) { |
945 |
| - msg.append("note: the backend file is not executable " |
946 |
| - "(use 'chmod +x' on it?)\n"); |
947 |
| - } |
948 |
| - if (fsname != L"NTFS") { |
949 |
| - msg.append("note: backend is on a volume of type '"); |
950 |
| - msg.append(wcsToMbs(fsname)); |
951 |
| - msg.append("', expected 'NTFS'\n" |
952 |
| - "note: WSL only supports local NTFS volumes\n"); |
| 1170 | + msg = "\nwslbridge error: backend process died\n"; |
| 1171 | + } else { |
| 1172 | + msg = "wslbridge error: failed to start backend process\n"; |
| 1173 | + if (fsname != L"NTFS") { |
| 1174 | + msg.append("note: backend program is at '"); |
| 1175 | + msg.append(wcsToMbs(backendPathWin)); |
| 1176 | + msg.append("'\n"); |
| 1177 | + msg.append("note: backend is on a volume of type '"); |
| 1178 | + msg.append(wcsToMbs(fsname)); |
| 1179 | + msg.append("', expected 'NTFS'\n" |
| 1180 | + "note: WSL only supports local NTFS volumes\n"); |
| 1181 | + } |
| 1182 | + } |
| 1183 | + if (!out.empty()) { |
| 1184 | + msg.append("note: bash.exe output: "); |
| 1185 | + msg.append(out); |
| 1186 | + msg.push_back('\n'); |
| 1187 | + } |
| 1188 | + if (!err.empty()) { |
| 1189 | + msg.append("note: backend error output: "); |
| 1190 | + msg.append(err); |
| 1191 | + msg.push_back('\n'); |
953 | 1192 | }
|
954 | 1193 | g_terminalState.fatal("%s", msg.c_str());
|
955 | 1194 | });
|
|
0 commit comments