1
0

mptWine.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750
  1. /*
  2. * mptWine.cpp
  3. * -----------
  4. * Purpose: Wine stuff.
  5. * Notes : (currently none)
  6. * Authors: OpenMPT Devs
  7. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  8. */
  9. #include "stdafx.h"
  10. #include "mptWine.h"
  11. #include "mptOS.h"
  12. #include "../common/mptFileIO.h"
  13. #include <deque>
  14. #include <map>
  15. #if MPT_OS_WINDOWS
  16. #include <windows.h>
  17. #endif
  18. OPENMPT_NAMESPACE_BEGIN
  19. #if MPT_OS_WINDOWS
  20. namespace mpt
  21. {
  22. namespace Wine
  23. {
  24. Context::Context(mpt::OS::Wine::VersionContext versionContext)
  25. : m_VersionContext(versionContext)
  26. , wine_get_dos_file_name(nullptr)
  27. , wine_get_unix_file_name(nullptr)
  28. {
  29. if(!mpt::OS::Windows::IsWine())
  30. {
  31. throw mpt::Wine::Exception("Wine not detected.");
  32. }
  33. if(!m_VersionContext.Version().IsValid())
  34. {
  35. throw mpt::Wine::Exception("Unknown Wine version detected.");
  36. }
  37. m_Kernel32 = std::make_shared<std::optional<mpt::library>>(mpt::library::load({ mpt::library::path_search::system, mpt::library::path_prefix::none, MPT_PATH("kernel32.dll"), mpt::library::path_suffix::none }));
  38. if(!m_Kernel32->has_value())
  39. {
  40. throw mpt::Wine::Exception("Could not load Wine kernel32.dll.");
  41. }
  42. if(!(*m_Kernel32)->bind(wine_get_unix_file_name, "wine_get_unix_file_name"))
  43. {
  44. throw mpt::Wine::Exception("Could not bind Wine kernel32.dll:wine_get_unix_file_name.");
  45. }
  46. if(!(*m_Kernel32)->bind(wine_get_dos_file_name, "wine_get_dos_file_name"))
  47. {
  48. throw mpt::Wine::Exception("Could not bind Wine kernel32.dll:wine_get_dos_file_name.");
  49. }
  50. {
  51. std::string out;
  52. std::string err;
  53. try
  54. {
  55. if(ExecutePosixShellCommand("uname -m", out, err) != 0)
  56. {
  57. throw mpt::Wine::Exception("Wine 'uname -m' failed.");
  58. }
  59. if(!err.empty())
  60. {
  61. throw mpt::Wine::Exception("Wine 'uname -m' failed.");
  62. }
  63. out = mpt::trim(out, std::string("\r\n"));
  64. m_Uname_m = out;
  65. } catch(const std::exception &)
  66. {
  67. m_Uname_m = std::string();
  68. }
  69. }
  70. try
  71. {
  72. m_HOME = GetPosixEnvVar("HOME");
  73. } catch(const std::exception &)
  74. {
  75. m_HOME = std::string();
  76. }
  77. try
  78. {
  79. m_XDG_DATA_HOME = GetPosixEnvVar("XDG_DATA_HOME");
  80. if(m_XDG_DATA_HOME.empty())
  81. {
  82. m_XDG_DATA_HOME = m_HOME + "/.local/share";
  83. }
  84. } catch(const std::exception &)
  85. {
  86. m_XDG_DATA_HOME = std::string();
  87. }
  88. try
  89. {
  90. m_XDG_CACHE_HOME = GetPosixEnvVar("XDG_CACHE_HOME");
  91. if(m_XDG_CACHE_HOME.empty())
  92. {
  93. m_XDG_CACHE_HOME = m_HOME + "/.cache";
  94. }
  95. } catch(const std::exception &)
  96. {
  97. m_XDG_CACHE_HOME = std::string();
  98. }
  99. try
  100. {
  101. m_XDG_CONFIG_HOME = GetPosixEnvVar("XDG_CONFIG_HOME");
  102. if(m_XDG_CONFIG_HOME.empty())
  103. {
  104. m_XDG_CONFIG_HOME = m_HOME + "/.config";
  105. }
  106. } catch(const std::exception &)
  107. {
  108. m_XDG_CONFIG_HOME = std::string();
  109. }
  110. }
  111. std::string Context::PathToPosix(mpt::PathString windowsPath)
  112. {
  113. std::string result;
  114. if(windowsPath.empty())
  115. {
  116. return result;
  117. }
  118. if(windowsPath.Length() >= 32000)
  119. {
  120. throw mpt::Wine::Exception("Path too long.");
  121. }
  122. LPSTR tmp = nullptr;
  123. tmp = wine_get_unix_file_name(windowsPath.ToWide().c_str());
  124. if(!tmp)
  125. {
  126. throw mpt::Wine::Exception("Wine kernel32.dll:wine_get_unix_file_name failed.");
  127. }
  128. result = tmp;
  129. HeapFree(GetProcessHeap(), 0, tmp);
  130. tmp = nullptr;
  131. return result;
  132. }
  133. mpt::PathString Context::PathToWindows(std::string hostPath)
  134. {
  135. mpt::PathString result;
  136. if(hostPath.empty())
  137. {
  138. return result;
  139. }
  140. if(hostPath.length() >= 32000)
  141. {
  142. throw mpt::Wine::Exception("Path too long.");
  143. }
  144. LPWSTR tmp = nullptr;
  145. tmp = wine_get_dos_file_name(hostPath.c_str());
  146. if(!tmp)
  147. {
  148. throw mpt::Wine::Exception("Wine kernel32.dll:wine_get_dos_file_name failed.");
  149. }
  150. result = mpt::PathString::FromWide(tmp);
  151. HeapFree(GetProcessHeap(), 0, tmp);
  152. tmp = nullptr;
  153. return result;
  154. }
  155. std::string Context::PathToPosixCanonical(mpt::PathString windowsPath)
  156. {
  157. std::string result;
  158. std::string hostPath = PathToPosix(windowsPath);
  159. if(hostPath.empty())
  160. {
  161. return result;
  162. }
  163. std::string output;
  164. std::string error;
  165. int exitcode = ExecutePosixShellCommand(std::string() + "readlink -f " + EscapePosixShell(hostPath), output, error);
  166. if(!error.empty())
  167. {
  168. throw mpt::Wine::Exception("Wine readlink failed: " + error);
  169. }
  170. if(exitcode != 0 && exitcode != 1)
  171. {
  172. throw mpt::Wine::Exception("Wine readlink failed.");
  173. }
  174. std::string trimmedOutput = mpt::trim(output, std::string("\r\n"));
  175. result = trimmedOutput;
  176. return result;
  177. }
  178. static void ExecutePosixCommandProgressDefault(void * /*userdata*/ )
  179. {
  180. ::Sleep(10);
  181. return;
  182. }
  183. static ExecuteProgressResult ExecutePosixShellScriptProgressDefault(void * /*userdata*/ )
  184. {
  185. ::Sleep(10);
  186. return ExecuteProgressContinueWaiting;
  187. }
  188. std::string Context::EscapePosixShell(std::string line)
  189. {
  190. const char escape_chars [] = { '|', '&', ';', '<', '>', '(', ')', '$', '`', '"', '\'', ' ', '\t' };
  191. const char maybe_escape_chars [] = { '*', '?', '[', '#', '~', '=', '%' };
  192. line = mpt::replace(line, std::string("\\"), std::string("\\\\"));
  193. for(char c : escape_chars)
  194. {
  195. line = mpt::replace(line, std::string(1, c), std::string("\\") + std::string(1, c));
  196. }
  197. for(char c : maybe_escape_chars)
  198. {
  199. line = mpt::replace(line, std::string(1, c), std::string("\\") + std::string(1, c));
  200. }
  201. return line;
  202. }
  203. ExecResult Context::ExecutePosixShellScript(std::string script, FlagSet<ExecFlags> flags, std::map<std::string, std::vector<char> > filetree, std::string title, ExecutePosixCommandProgress progress, ExecutePosixShellScriptProgress progressCancel, void *userdata)
  204. {
  205. // Relevant documentation:
  206. // https://stackoverflow.com/questions/6004070/execute-shell-commands-from-program-running-in-wine
  207. // https://www.winehq.org/pipermail/wine-bugs/2014-January/374918.html
  208. // https://bugs.winehq.org/show_bug.cgi?id=34730
  209. if(!progress) progress = &ExecutePosixCommandProgressDefault;
  210. if(!progressCancel) progressCancel = &ExecutePosixShellScriptProgressDefault;
  211. if(flags[ExecFlagInteractive]) flags.reset(ExecFlagSilent);
  212. if(flags[ExecFlagSplitOutput]) flags.set(ExecFlagSilent);
  213. std::vector<mpt::PathString> tempfiles;
  214. progress(userdata);
  215. mpt::TempDirGuard dirWindowsTemp(mpt::CreateTempFileName());
  216. if(dirWindowsTemp.GetDirname().empty())
  217. {
  218. throw mpt::Wine::Exception("Creating temporary directoy failed.");
  219. }
  220. const std::string dirPosix = PathToPosix(dirWindowsTemp.GetDirname());
  221. if(dirPosix.empty())
  222. {
  223. throw mpt::Wine::Exception("mpt::Wine::ConvertWindowsPathToHost returned empty path.");
  224. }
  225. const std::string dirPosixEscape = EscapePosixShell(dirPosix);
  226. const mpt::PathString dirWindows = dirWindowsTemp.GetDirname();
  227. progress(userdata);
  228. // write the script to disk
  229. mpt::PathString scriptFilenameWindows = dirWindows + P_("script.sh");
  230. {
  231. mpt::ofstream tempfile(scriptFilenameWindows, std::ios::binary);
  232. tempfile << script;
  233. tempfile.flush();
  234. if(!tempfile)
  235. {
  236. throw mpt::Wine::Exception("Error writing script.sh.");
  237. }
  238. }
  239. const std::string scriptFilenamePosix = PathToPosix(scriptFilenameWindows);
  240. if(scriptFilenamePosix.empty())
  241. {
  242. throw mpt::Wine::Exception("Error converting script.sh path.");
  243. }
  244. const std::string scriptFilenamePosixEscape = EscapePosixShell(scriptFilenamePosix);
  245. progress(userdata);
  246. // create a wrapper that will call the script and gather result.
  247. mpt::PathString wrapperstarterFilenameWindows = dirWindows + P_("wrapperstarter.sh");
  248. {
  249. mpt::ofstream tempfile(wrapperstarterFilenameWindows, std::ios::binary);
  250. std::string wrapperstarterscript;
  251. wrapperstarterscript += std::string() + "#!/usr/bin/env sh" "\n";
  252. wrapperstarterscript += std::string() + "exec /usr/bin/env sh " + dirPosixEscape + "wrapper.sh" "\n";
  253. tempfile << wrapperstarterscript;
  254. tempfile.flush();
  255. if(!tempfile)
  256. {
  257. throw mpt::Wine::Exception("Error writing wrapper.sh.");
  258. }
  259. }
  260. mpt::PathString wrapperFilenameWindows = dirWindows + P_("wrapper.sh");
  261. std::string cleanupscript;
  262. {
  263. mpt::ofstream tempfile(wrapperFilenameWindows, std::ios::binary);
  264. std::string wrapperscript;
  265. if(!flags[ExecFlagSilent])
  266. {
  267. wrapperscript += "printf \"\\033]0;" + title + "\\a\"" "\n";
  268. }
  269. wrapperscript += "chmod u+x " + scriptFilenamePosixEscape + "\n";
  270. wrapperscript += "cd " + dirPosixEscape + "filetree" "\n";
  271. if(flags[ExecFlagInteractive])
  272. { // no stdout/stderr capturing for interactive scripts
  273. wrapperscript += scriptFilenamePosixEscape + "\n";
  274. wrapperscript += "MPT_RESULT=$?" "\n";
  275. wrapperscript += "echo ${MPT_RESULT} > " + dirPosixEscape + "exit" "\n";
  276. } else if(flags[ExecFlagSplitOutput])
  277. {
  278. wrapperscript += "(" + scriptFilenamePosixEscape + "; echo $? >&4) 4>" + dirPosixEscape + "exit 1>" + dirPosixEscape + "out 2>" + dirPosixEscape + "err" "\n";
  279. } else
  280. {
  281. wrapperscript += "(" + scriptFilenamePosixEscape + "; echo $? >&4) 2>&1 4>" + dirPosixEscape + "exit | tee " + dirPosixEscape + "out" "\n";
  282. }
  283. wrapperscript += "echo done > " + dirPosixEscape + "done" "\n";
  284. cleanupscript += "rm " + dirPosixEscape + "done" "\n";
  285. cleanupscript += "rm " + dirPosixEscape + "exit" "\n";
  286. if(flags[ExecFlagInteractive])
  287. {
  288. // nothing
  289. } else if(flags[ExecFlagSplitOutput])
  290. {
  291. cleanupscript += "rm " + dirPosixEscape + "out" "\n";
  292. cleanupscript += "rm " + dirPosixEscape + "err" "\n";
  293. } else
  294. {
  295. cleanupscript += "rm " + dirPosixEscape + "out" "\n";
  296. }
  297. cleanupscript += "rm -r " + dirPosixEscape + "filetree" "\n";
  298. cleanupscript += "rm " + dirPosixEscape + "script.sh" "\n";
  299. cleanupscript += "rm " + dirPosixEscape + "wrapper.sh" "\n";
  300. cleanupscript += "rm " + dirPosixEscape + "wrapperstarter.sh" "\n";
  301. cleanupscript += "rm " + dirPosixEscape + "terminal.sh" "\n";
  302. if(flags[ExecFlagAsync])
  303. {
  304. wrapperscript += cleanupscript;
  305. cleanupscript.clear();
  306. }
  307. tempfile << wrapperscript;
  308. tempfile.flush();
  309. if(!tempfile)
  310. {
  311. throw mpt::Wine::Exception("Error writing wrapper.sh.");
  312. }
  313. }
  314. progress(userdata);
  315. ::CreateDirectory((dirWindows + P_("filetree")).AsNative().c_str(), nullptr);
  316. for(const auto &file : filetree)
  317. {
  318. std::vector<mpt::ustring> path = mpt::String::Split<mpt::ustring>(mpt::ToUnicode(mpt::Charset::UTF8, file.first), U_("/"));
  319. mpt::PathString combinedPath = dirWindows + P_("filetree") + P_("\\");
  320. if(path.size() > 1)
  321. {
  322. for(std::size_t singlepath = 0; singlepath < path.size() - 1; ++singlepath)
  323. {
  324. if(path[singlepath].empty())
  325. {
  326. continue;
  327. }
  328. combinedPath += mpt::PathString::FromUnicode(path[singlepath]);
  329. if(!combinedPath.IsDirectory())
  330. {
  331. if(::CreateDirectory(combinedPath.AsNative().c_str(), nullptr) == 0)
  332. {
  333. throw mpt::Wine::Exception("Error writing filetree.");
  334. }
  335. }
  336. combinedPath += P_("\\");
  337. }
  338. }
  339. try
  340. {
  341. mpt::LazyFileRef out(dirWindows + P_("filetree") + P_("\\") + mpt::PathString::FromUTF8(mpt::replace(file.first, std::string("/"), std::string("\\"))));
  342. out = file.second;
  343. } catch(std::exception &)
  344. {
  345. throw mpt::Wine::Exception("Error writing filetree.");
  346. }
  347. }
  348. progress(userdata);
  349. // create a wrapper that will find a suitable terminal and run the wrapper script in the terminal window.
  350. mpt::PathString terminalWrapperFilenameWindows = dirWindows + P_("terminal.sh");
  351. {
  352. mpt::ofstream tempfile(terminalWrapperFilenameWindows, std::ios::binary);
  353. // NOTE:
  354. // Modern terminals detach themselves from the invoking shell if another instance is already present.
  355. // This means we cannot rely on terminal invocation being syncronous.
  356. static constexpr const char * terminals[] =
  357. {
  358. "x-terminal-emulator",
  359. "konsole",
  360. "mate-terminal",
  361. "xfce4-terminal",
  362. "gnome-terminal",
  363. "uxterm",
  364. "xterm",
  365. "rxvt",
  366. };
  367. std::string terminalscript = "\n";
  368. for(const std::string terminal : terminals)
  369. {
  370. // mate-terminal on Debian 8 cannot execute commands with arguments,
  371. // thus we use a separate script that requires no arguments to execute.
  372. terminalscript += "if command -v " + terminal + " 2>/dev/null 1>/dev/null ; then" "\n";
  373. terminalscript += " chmod u+x " + dirPosixEscape + "wrapperstarter.sh" "\n";
  374. terminalscript += " exec `command -v " + terminal + "` -e \"" + dirPosixEscape + "wrapperstarter.sh\"" "\n";
  375. terminalscript += "fi" "\n";
  376. }
  377. tempfile << terminalscript;
  378. tempfile.flush();
  379. if(!tempfile)
  380. {
  381. return ExecResult::Error();
  382. }
  383. }
  384. progress(userdata);
  385. // build unix command line
  386. std::string unixcommand;
  387. bool createProcessSuccess = false;
  388. if(!createProcessSuccess)
  389. {
  390. if(flags[ExecFlagSilent])
  391. {
  392. unixcommand = "/usr/bin/env sh \"" + dirPosixEscape + "wrapper.sh\"";
  393. } else
  394. {
  395. unixcommand = "/usr/bin/env sh \"" + dirPosixEscape + "terminal.sh\"";
  396. }
  397. progress(userdata);
  398. std::wstring unixcommandW = mpt::ToWide(mpt::Charset::UTF8, unixcommand);
  399. std::wstring titleW = mpt::ToWide(mpt::Charset::UTF8, title);
  400. STARTUPINFOW startupInfo = {};
  401. startupInfo.lpTitle = titleW.data();
  402. startupInfo.cb = sizeof(startupInfo);
  403. PROCESS_INFORMATION processInformation = {};
  404. progress(userdata);
  405. BOOL success = FALSE;
  406. if(flags[ExecFlagSilent])
  407. {
  408. success = CreateProcessW(NULL, unixcommandW.data(), NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &startupInfo, &processInformation);
  409. } else
  410. {
  411. success = CreateProcessW(NULL, unixcommandW.data(), NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &startupInfo, &processInformation);
  412. }
  413. progress(userdata);
  414. createProcessSuccess = (success != FALSE);
  415. progress(userdata);
  416. if(success)
  417. {
  418. if(!flags[ExecFlagAsync])
  419. {
  420. // note: execution is not syncronous with all Wine versions,
  421. // we additionally explicitly wait for "done" later
  422. while(WaitForSingleObject(processInformation.hProcess, 0) == WAIT_TIMEOUT)
  423. { // wait
  424. if(progressCancel(userdata) != ExecuteProgressContinueWaiting)
  425. {
  426. CloseHandle(processInformation.hThread);
  427. CloseHandle(processInformation.hProcess);
  428. throw mpt::Wine::Exception("Canceled.");
  429. }
  430. }
  431. }
  432. progress(userdata);
  433. CloseHandle(processInformation.hThread);
  434. CloseHandle(processInformation.hProcess);
  435. }
  436. }
  437. progress(userdata);
  438. // Work around Wine being unable to execute PIE binaries on Debian 9.
  439. // Luckily, /bin/bash is still non-PIE on Debian 9.
  440. if(!createProcessSuccess)
  441. {
  442. if(flags[ExecFlagSilent])
  443. {
  444. unixcommand = "/bin/bash \"" + dirPosixEscape + "wrapper.sh\"";
  445. } else
  446. {
  447. unixcommand = "/bin/bash \"" + dirPosixEscape + "terminal.sh\"";
  448. }
  449. progress(userdata);
  450. std::wstring unixcommandW = mpt::ToWide(mpt::Charset::UTF8, unixcommand);
  451. std::wstring titleW = mpt::ToWide(mpt::Charset::UTF8, title);
  452. STARTUPINFOW startupInfo = {};
  453. startupInfo.lpTitle = titleW.data();
  454. startupInfo.cb = sizeof(startupInfo);
  455. PROCESS_INFORMATION processInformation = {};
  456. progress(userdata);
  457. BOOL success = FALSE;
  458. if(flags[ExecFlagSilent])
  459. {
  460. success = CreateProcessW(NULL, unixcommandW.data(), NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &startupInfo, &processInformation);
  461. } else
  462. {
  463. success = CreateProcessW(NULL, unixcommandW.data(), NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &startupInfo, &processInformation);
  464. }
  465. progress(userdata);
  466. createProcessSuccess = (success != FALSE);
  467. progress(userdata);
  468. if(success)
  469. {
  470. if(!flags[ExecFlagAsync])
  471. {
  472. // note: execution is not syncronous with all Wine versions,
  473. // we additionally explicitly wait for "done" later
  474. while(WaitForSingleObject(processInformation.hProcess, 0) == WAIT_TIMEOUT)
  475. { // wait
  476. if(progressCancel(userdata) != ExecuteProgressContinueWaiting)
  477. {
  478. CloseHandle(processInformation.hThread);
  479. CloseHandle(processInformation.hProcess);
  480. throw mpt::Wine::Exception("Canceled.");
  481. }
  482. }
  483. }
  484. progress(userdata);
  485. CloseHandle(processInformation.hThread);
  486. CloseHandle(processInformation.hProcess);
  487. }
  488. }
  489. progress(userdata);
  490. if(!createProcessSuccess)
  491. {
  492. throw mpt::Wine::Exception("CreateProcess failed.");
  493. }
  494. progress(userdata);
  495. if(flags[ExecFlagAsync])
  496. {
  497. ExecResult result;
  498. result.exitcode = 0;
  499. return result;
  500. }
  501. while(!(dirWindows + P_("done")).IsFile())
  502. { // wait
  503. if(progressCancel(userdata) != ExecuteProgressContinueWaiting)
  504. {
  505. throw mpt::Wine::Exception("Canceled.");
  506. }
  507. }
  508. progress(userdata);
  509. int exitCode = 0;
  510. {
  511. mpt::ifstream exitFile(dirWindows + P_("exit"), std::ios::binary);
  512. if(!exitFile)
  513. {
  514. throw mpt::Wine::Exception("Script .exit file not found.");
  515. }
  516. std::string exitString;
  517. exitFile >> exitString;
  518. if(exitString.empty())
  519. {
  520. throw mpt::Wine::Exception("Script .exit file empty.");
  521. }
  522. exitCode = ConvertStrTo<int>(exitString);
  523. }
  524. progress(userdata);
  525. std::string outputString;
  526. if(!flags[ExecFlagInteractive])
  527. {
  528. mpt::ifstream outputFile(dirWindows + P_("out"), std::ios::binary);
  529. if(outputFile)
  530. {
  531. outputFile.seekg(0, std::ios::end);
  532. std::streampos outputFileSize = outputFile.tellg();
  533. outputFile.seekg(0, std::ios::beg);
  534. std::vector<char> outputFileBuf(mpt::saturate_cast<std::size_t>(static_cast<std::streamoff>(outputFileSize)));
  535. outputFile.read(&outputFileBuf[0], outputFileBuf.size());
  536. outputString = mpt::buffer_cast<std::string>(outputFileBuf);
  537. }
  538. }
  539. progress(userdata);
  540. std::string errorString;
  541. if(flags[ExecFlagSplitOutput])
  542. {
  543. mpt::ifstream errorFile(dirWindows + P_("err"), std::ios::binary);
  544. if(errorFile)
  545. {
  546. errorFile.seekg(0, std::ios::end);
  547. std::streampos errorFileSize = errorFile.tellg();
  548. errorFile.seekg(0, std::ios::beg);
  549. std::vector<char> errorFileBuf(mpt::saturate_cast<std::size_t>(static_cast<std::streamoff>(errorFileSize)));
  550. errorFile.read(&errorFileBuf[0], errorFileBuf.size());
  551. errorString = mpt::buffer_cast<std::string>(errorFileBuf);
  552. }
  553. }
  554. progress(userdata);
  555. ExecResult result;
  556. result.exitcode = exitCode;
  557. result.output = outputString;
  558. result.error = errorString;
  559. std::deque<mpt::PathString> paths;
  560. paths.push_back(dirWindows + P_("filetree"));
  561. mpt::PathString basePath = (dirWindows + P_("filetree")).EnsureTrailingSlash();
  562. while(!paths.empty())
  563. {
  564. mpt::PathString path = paths.front();
  565. paths.pop_front();
  566. path.EnsureTrailingSlash();
  567. HANDLE hFind = NULL;
  568. WIN32_FIND_DATA wfd = {};
  569. hFind = FindFirstFile((path + P_("*.*")).AsNative().c_str(), &wfd);
  570. if(hFind != NULL && hFind != INVALID_HANDLE_VALUE)
  571. {
  572. do
  573. {
  574. mpt::PathString filename = mpt::PathString::FromNative(wfd.cFileName);
  575. if(filename != P_(".") && filename != P_(".."))
  576. {
  577. filename = path + filename;
  578. filetree[filename.ToUTF8()] = std::vector<char>();
  579. if(filename.IsDirectory())
  580. {
  581. paths.push_back(filename);
  582. } else if(filename.IsFile())
  583. {
  584. try
  585. {
  586. mpt::LazyFileRef f(filename);
  587. std::vector<char> buf = f;
  588. mpt::PathString treeFilename = mpt::PathString::FromNative(filename.AsNative().substr(basePath.AsNative().length()));
  589. result.filetree[treeFilename.ToUTF8()] = buf;
  590. } catch (std::exception &)
  591. {
  592. // nothing?!
  593. }
  594. }
  595. }
  596. } while(FindNextFile(hFind, &wfd));
  597. FindClose(hFind);
  598. }
  599. }
  600. mpt::DeleteWholeDirectoryTree(dirWindows);
  601. return result;
  602. }
  603. int Context::ExecutePosixShellCommand(std::string command, std::string & output, std::string & error)
  604. {
  605. std::string script;
  606. script += "#!/usr/bin/env sh" "\n";
  607. script += "exec " + command + "\n";
  608. mpt::Wine::ExecResult execResult = ExecutePosixShellScript
  609. ( script
  610. , mpt::Wine::ExecFlagSilent | mpt::Wine::ExecFlagSplitOutput, std::map<std::string, std::vector<char> >()
  611. , std::string()
  612. , nullptr
  613. , nullptr
  614. , nullptr
  615. );
  616. output = execResult.output;
  617. error = execResult.error;
  618. return execResult.exitcode;
  619. }
  620. std::string Context::GetPosixEnvVar(std::string var, std::string def)
  621. {
  622. // We cannot use std::getenv here because Wine overrides SOME env vars,
  623. // in particular, HOME is unset in the Wine environment.
  624. // Instead, we just spawn a shell that will catch up a sane environment on
  625. // its own.
  626. std::string output;
  627. std::string error;
  628. int exitcode = ExecutePosixShellCommand(std::string() + "echo $" + var, output, error);
  629. if(!error.empty())
  630. {
  631. throw mpt::Wine::Exception("Wine echo $var failed: " + error);
  632. }
  633. if(exitcode != 0)
  634. {
  635. throw mpt::Wine::Exception("Wine echo $var failed.");
  636. }
  637. std::string result = mpt::trim_right(output, std::string("\r\n"));
  638. if(result.empty())
  639. {
  640. result = def;
  641. }
  642. return result;
  643. }
  644. } // namespace Wine
  645. } // namespace mpt
  646. #else // !MPT_OS_WINDOWS
  647. MPT_MSVC_WORKAROUND_LNK4221(mptWine)
  648. #endif // MPT_OS_WINDOWS
  649. OPENMPT_NAMESPACE_END