extract.h 5.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. // The pinned 7-Zip executable owns extraction; this pipe reader publishes its work percentage.
  2. #pragma once
  3. #include <string>
  4. #include <vector>
  5. #include "extract-progress.h"
  6. struct InstallerHandle {
  7. HANDLE value = nullptr;
  8. InstallerHandle() = default;
  9. explicit InstallerHandle(HANDLE handle) : value(handle) {}
  10. InstallerHandle(const InstallerHandle&) = delete;
  11. InstallerHandle& operator=(const InstallerHandle&) = delete;
  12. ~InstallerHandle() { Close(); }
  13. void Close() {
  14. if (value && value != INVALID_HANDLE_VALUE) CloseHandle(value);
  15. value = nullptr;
  16. }
  17. };
  18. struct InstallerAttributes {
  19. std::vector<unsigned char> storage;
  20. LPPROC_THREAD_ATTRIBUTE_LIST list = nullptr;
  21. ~InstallerAttributes() { if (list) DeleteProcThreadAttributeList(list); }
  22. bool Initialize() {
  23. SIZE_T size = 0;
  24. InitializeProcThreadAttributeList(nullptr, 2, 0, &size);
  25. storage.resize(size);
  26. auto* candidate = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(storage.data());
  27. if (!InitializeProcThreadAttributeList(candidate, 2, 0, &size)) return false;
  28. list = candidate;
  29. return true;
  30. }
  31. };
  32. static DWORD ExtractApplication(HWND parent, LPCWSTR tool, LPCWSTR archive, LPCWSTR destination, LPCWSTR log) {
  33. SECURITY_ATTRIBUTES security = {sizeof(security), nullptr, TRUE};
  34. InstallerHandle input, output;
  35. if (!CreatePipe(&input.value, &output.value, &security, 0)) return GetLastError();
  36. if (!SetHandleInformation(input.value, HANDLE_FLAG_INHERIT, 0)) return GetLastError();
  37. InstallerHandle errors(CreateFileW(log, GENERIC_WRITE, FILE_SHARE_READ, &security, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr));
  38. if (errors.value == INVALID_HANDLE_VALUE) return GetLastError();
  39. InstallerHandle nullInput(CreateFileW(L"NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, &security, OPEN_EXISTING, 0, nullptr));
  40. if (nullInput.value == INVALID_HANDLE_VALUE) return GetLastError();
  41. InstallerHandle job(CreateJobObjectW(nullptr, nullptr));
  42. if (!job.value) return GetLastError();
  43. JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits = {};
  44. limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
  45. if (!SetInformationJobObject(job.value, JobObjectExtendedLimitInformation, &limits, sizeof(limits))) return GetLastError();
  46. InstallerAttributes attributes;
  47. if (!attributes.Initialize()) return GetLastError();
  48. HANDLE inherited[] = {nullInput.value, output.value, errors.value};
  49. if (!UpdateProcThreadAttribute(attributes.list, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, inherited, sizeof(inherited), nullptr, nullptr) ||
  50. !UpdateProcThreadAttribute(attributes.list, 0, PROC_THREAD_ATTRIBUTE_JOB_LIST, &job.value, sizeof(job.value), nullptr, nullptr)) return GetLastError();
  51. STARTUPINFOEXW startup = {};
  52. startup.StartupInfo.cb = sizeof(startup);
  53. startup.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
  54. startup.StartupInfo.hStdInput = nullInput.value;
  55. startup.StartupInfo.hStdOutput = output.value;
  56. startup.StartupInfo.hStdError = errors.value;
  57. startup.lpAttributeList = attributes.list;
  58. // A final dot prevents a destination's trailing backslash from escaping its closing quote.
  59. std::wstring command = L"\"" + std::wstring(tool) + L"\" x -y -bso0 -bse2 -bsp1 -bb0 \"-o" + destination + L"\\.\" \"" + archive + L"\"";
  60. PROCESS_INFORMATION child = {};
  61. if (!CreateProcessW(tool, &command[0], nullptr, nullptr, TRUE,
  62. EXTENDED_STARTUPINFO_PRESENT | CREATE_NO_WINDOW, nullptr, nullptr, &startup.StartupInfo, &child)) return GetLastError();
  63. InstallerHandle process(child.hProcess), thread(child.hThread);
  64. output.Close();
  65. errors.Close();
  66. ExtractionProgress progress;
  67. char buffer[4096];
  68. DWORD count = 0;
  69. DWORD readError = ERROR_SUCCESS;
  70. while (true) {
  71. if (!ReadFile(input.value, buffer, sizeof(buffer), &count, nullptr)) { readError = GetLastError(); break; }
  72. if (!count) break;
  73. progress.Read(buffer, count);
  74. if (parent) SetPropW(parent, L"HarnessInstaller.ExtractProgress", reinterpret_cast<HANDLE>(static_cast<UINT_PTR>(progress.value)));
  75. }
  76. if (readError != ERROR_BROKEN_PIPE && readError != ERROR_SUCCESS) {
  77. TerminateJobObject(job.value, readError);
  78. WaitForSingleObject(process.value, INFINITE);
  79. return readError;
  80. }
  81. if (WaitForSingleObject(process.value, INFINITE) != WAIT_OBJECT_0) return GetLastError();
  82. DWORD result;
  83. if (!GetExitCodeProcess(process.value, &result)) return GetLastError();
  84. if (result == 0 && parent) SetPropW(parent, L"HarnessInstaller.ExtractProgress", reinterpret_cast<HANDLE>(100));
  85. return result;
  86. }
  87. // Runs on the NSIS worker; only an exit code of zero permits directory promotion.
  88. extern "C" __declspec(dllexport) DWORD __cdecl InstallerExtract(HWND parent, LPCWSTR tool, LPCWSTR archive, LPCWSTR destination, LPCWSTR log) {
  89. try { return ExtractApplication(parent, tool, archive, destination, log); }
  90. catch (const std::bad_alloc&) { return ERROR_NOT_ENOUGH_MEMORY; }
  91. }