Setup.Helper.pas 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. unit Setup.Helper;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2025 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. Interface to 64-bit helper
  8. NOTE: These functions are NOT thread-safe. Do not call them from multiple
  9. threads simultaneously.
  10. }
  11. interface
  12. uses
  13. Windows, SysUtils, Shared.Struct;
  14. function GetHelperResourceName: String;
  15. function HelperGrantPermission(const AObjectType: DWORD;
  16. const AObjectName: String; const AEntries: TGrantPermissionEntry;
  17. const AEntryCount: Integer; const AInheritance: DWORD): DWORD;
  18. procedure HelperRegisterTypeLibrary(const AUnregister: Boolean;
  19. Filename: String);
  20. procedure SetHelperExeFilename(const Filename: String);
  21. procedure StopHelper(const DelayAfterStopping: Boolean);
  22. implementation
  23. {x$DEFINE HELPERDEBUG}
  24. uses
  25. Forms, Shared.CommonFunc.Vcl, Shared.CommonFunc, PathFunc, Setup.MainFunc, Setup.InstFunc,
  26. Setup.LoggingFunc, SetupLdrAndSetup.Messages, Shared.SetupMessageIDs;
  27. const
  28. HELPER_VERSION = 105;
  29. const
  30. REQUEST_PING = 1;
  31. REQUEST_GRANT_PERMISSION = 2;
  32. REQUEST_REGISTER_SERVER = 3; { no longer used }
  33. REQUEST_REGISTER_TYPE_LIBRARY = 4;
  34. type
  35. TRequestGrantPermissionData = record
  36. ObjectType: DWORD;
  37. EntryCount: DWORD;
  38. Inheritance: DWORD;
  39. ObjectName: array[0..4095] of WideChar;
  40. Entries: array[0..MaxGrantPermissionEntries-1] of TGrantPermissionEntry;
  41. end;
  42. TRequestRegisterServerData = record
  43. Unregister: BOOL;
  44. FailCriticalErrors: BOOL;
  45. Filename: array[0..4095] of WideChar;
  46. Directory: array[0..4095] of WideChar;
  47. end;
  48. TRequestRegisterTypeLibraryData = record
  49. Unregister: BOOL;
  50. Filename: array[0..4095] of WideChar;
  51. end;
  52. TRequestData = record
  53. SequenceNumber: DWORD;
  54. Command: DWORD;
  55. DataSize: DWORD;
  56. case Integer of
  57. 0: (Data: array[0..0] of Byte);
  58. 1: (GrantPermissionData: TRequestGrantPermissionData);
  59. 2: (RegisterServerData: TRequestRegisterServerData);
  60. 3: (RegisterTypeLibraryData: TRequestRegisterTypeLibraryData);
  61. end;
  62. TResponseData = record
  63. SequenceNumber: DWORD;
  64. StatusCode: DWORD;
  65. ErrorCode: DWORD;
  66. DataSize: DWORD;
  67. Data: array[0..0] of Byte; { currently, no data is ever returned }
  68. end;
  69. THelper = class
  70. private
  71. FRunning, FNeedsRestarting: Boolean;
  72. FProcessHandle, FPipe: THandle;
  73. FProcessID: DWORD;
  74. FCommandSequenceNumber: DWORD;
  75. FProcessMessagesProc: procedure of object;
  76. FRequest: TRequestData;
  77. FResponse: TResponseData;
  78. procedure Call(const ACommand, ADataSize: DWORD);
  79. procedure InternalCall(const ACommand, ADataSize: DWORD;
  80. const AllowProcessMessages: Boolean);
  81. procedure Start;
  82. public
  83. destructor Destroy; override;
  84. function GrantPermission(const AObjectType: DWORD;
  85. const AObjectName: String; const AEntries: TGrantPermissionEntry;
  86. const AEntryCount: Integer; const AInheritance: DWORD): DWORD;
  87. procedure RegisterTypeLibrary(const AUnregister: Boolean;
  88. Filename: String);
  89. procedure Stop(const DelayAfterStopping: Boolean);
  90. end;
  91. var
  92. HelperMainInstance: THelper;
  93. HelperExeFilename: String;
  94. HelperPipeNameSequence: LongWord;
  95. function GetHelperResourceName: String;
  96. begin
  97. {$R Setup.HelperEXEs.res}
  98. if paX64 in MachineTypesSupportedBySystem then
  99. Result := 'HELPER_EXE_AMD64'
  100. else
  101. Result := '';
  102. end;
  103. procedure SetHelperExeFilename(const Filename: String);
  104. begin
  105. HelperExeFilename := Filename;
  106. end;
  107. procedure StopHelper(const DelayAfterStopping: Boolean);
  108. begin
  109. HelperMainInstance.Stop(DelayAfterStopping);
  110. end;
  111. function HelperGrantPermission(const AObjectType: DWORD;
  112. const AObjectName: String; const AEntries: TGrantPermissionEntry;
  113. const AEntryCount: Integer; const AInheritance: DWORD): DWORD;
  114. begin
  115. Result := HelperMainInstance.GrantPermission(AObjectType, AObjectName,
  116. AEntries, AEntryCount, AInheritance);
  117. end;
  118. procedure HelperRegisterTypeLibrary(const AUnregister: Boolean;
  119. Filename: String);
  120. begin
  121. HelperMainInstance.RegisterTypeLibrary(AUnregister, Filename);
  122. end;
  123. procedure FillWideCharBuffer(var Buf: array of WideChar; const S: String);
  124. begin
  125. if High(Buf) <= 0 then
  126. InternalError('FillWideCharBuffer: Invalid Buf');
  127. if Length(S) > High(Buf) then
  128. InternalError('FillWideCharBuffer: String too long');
  129. StrPLCopy(Buf, S, High(Buf));
  130. end;
  131. { THelper }
  132. destructor THelper.Destroy;
  133. begin
  134. Stop(False);
  135. inherited;
  136. end;
  137. procedure THelper.Start;
  138. const
  139. FILE_FLAG_FIRST_PIPE_INSTANCE = $00080000;
  140. InheritableSecurity: TSecurityAttributes = (
  141. nLength: SizeOf(InheritableSecurity); lpSecurityDescriptor: nil;
  142. bInheritHandle: True);
  143. var
  144. PerformanceCount: Int64;
  145. PipeName: String;
  146. Pipe, RemotePipe: THandle;
  147. Mode: DWORD;
  148. StartupInfo: TStartupInfo;
  149. ProcessInfo: TProcessInformation;
  150. begin
  151. Log('Starting 64-bit helper process.');
  152. { We don't *have* to check IsWin64 here; the helper *should* run fine without
  153. the new APIs added in 2003 SP1. But let's be consistent and disable 64-bit
  154. functionality across the board when the user is running 64-bit Windows and
  155. IsWin64=False. }
  156. if not IsWin64 then
  157. InternalError('Cannot utilize 64-bit features on this version of Windows');
  158. if HelperExeFilename = '' then
  159. InternalError('64-bit helper EXE wasn''t extracted');
  160. repeat
  161. { Generate a very unique pipe name }
  162. Inc(HelperPipeNameSequence);
  163. FCommandSequenceNumber := GetTickCount;
  164. if not QueryPerformanceCounter(PerformanceCount) then
  165. GetSystemTimeAsFileTime(TFileTime(PerformanceCount));
  166. PipeName := Format('\\.\pipe\InnoSetup64BitHelper-%.8x-%.8x-%.8x-%.16x',
  167. [GetCurrentProcessId, HelperPipeNameSequence, FCommandSequenceNumber,
  168. PerformanceCount]);
  169. { Create the pipe }
  170. Pipe := CreateNamedPipe(PChar(PipeName),
  171. PIPE_ACCESS_DUPLEX or FILE_FLAG_OVERLAPPED or FILE_FLAG_FIRST_PIPE_INSTANCE,
  172. PIPE_TYPE_MESSAGE or PIPE_READMODE_MESSAGE or PIPE_WAIT,
  173. 1, 8192, 8192, 0, nil);
  174. if Pipe <> INVALID_HANDLE_VALUE then
  175. Break;
  176. { Loop if there's a name clash (ERROR_PIPE_BUSY), otherwise raise error }
  177. if GetLastError <> ERROR_PIPE_BUSY then
  178. Win32ErrorMsg('CreateNamedPipe');
  179. until False;
  180. try
  181. { Create an inheritable handle to the pipe for the helper to use }
  182. RemotePipe := CreateFile(PChar(PipeName), GENERIC_READ or GENERIC_WRITE,
  183. 0, @InheritableSecurity, OPEN_EXISTING, 0, 0);
  184. if RemotePipe = INVALID_HANDLE_VALUE then
  185. Win32ErrorMsg('CreateFile');
  186. try
  187. Mode := PIPE_READMODE_MESSAGE or PIPE_WAIT;
  188. if not SetNamedPipeHandleState(RemotePipe, Mode, nil, nil) then
  189. Win32ErrorMsg('SetNamedPipeHandleState');
  190. FillChar(StartupInfo, SizeOf(StartupInfo), 0);
  191. StartupInfo.cb := SizeOf(StartupInfo);
  192. if not CreateProcess(PChar(HelperExeFilename),
  193. PChar(Format('helper %d 0x%x', [HELPER_VERSION, RemotePipe])), nil,
  194. nil, True, CREATE_DEFAULT_ERROR_MODE or CREATE_NO_WINDOW, nil,
  195. PChar(GetSystemDir), StartupInfo, ProcessInfo) then
  196. Win32ErrorMsg('CreateProcess');
  197. FRunning := True;
  198. FNeedsRestarting := False;
  199. FProcessHandle := ProcessInfo.hProcess;
  200. FProcessID := ProcessInfo.dwProcessId;
  201. FPipe := Pipe;
  202. Pipe := 0; { ensure the 'except' section can't close it now }
  203. CloseHandle(ProcessInfo.hThread);
  204. LogFmt('Helper process PID: %u', [FProcessID]);
  205. finally
  206. { We don't need a handle to RemotePipe after creating the process }
  207. CloseHandle(RemotePipe);
  208. end;
  209. except
  210. if Pipe <> 0 then
  211. CloseHandle(Pipe);
  212. raise;
  213. end;
  214. end;
  215. procedure THelper.Stop(const DelayAfterStopping: Boolean);
  216. { Stops the helper process if it's running }
  217. var
  218. ExitCode: DWORD;
  219. begin
  220. if not FRunning then
  221. Exit;
  222. { Before attempting to stop anything, set FNeedsRestarting to ensure
  223. Call can never access a partially-stopped helper }
  224. FNeedsRestarting := True;
  225. LogFmt('Stopping 64-bit helper process. (PID: %u)', [FProcessID]);
  226. { Closing our handle to the pipe will cause the helper's blocking ReadFile
  227. call to return False, and the process to exit }
  228. CloseHandle(FPipe);
  229. FPipe := 0;
  230. while WaitForSingleObject(FProcessHandle, 10000) = WAIT_TIMEOUT do begin
  231. { It should never have to resort to terminating the process, but if the
  232. process for some unknown reason didn't exit in response to our closing
  233. the pipe, it should be safe to kill it since it most likely isn't doing
  234. anything other than waiting for a request. }
  235. Log('Helper isn''t responding; killing it.');
  236. TerminateProcess(FProcessHandle, 1);
  237. end;
  238. if GetExitCodeProcess(FProcessHandle, ExitCode) then begin
  239. if ExitCode = 0 then
  240. Log('Helper process exited.')
  241. else
  242. LogFmt('Helper process exited with failure code: 0x%x', [ExitCode]);
  243. end
  244. else
  245. Log('Helper process exited, but failed to get exit code.');
  246. CloseHandle(FProcessHandle);
  247. FProcessHandle := 0;
  248. FProcessID := 0;
  249. FRunning := False;
  250. { Give it extra time to fully terminate to ensure that the EXE isn't still
  251. locked on SMP systems when we try to delete it in DeinitSetup.
  252. (Note: I'm not 100% certain this is needed; I don't have an SMP AMD64
  253. system to test on. It didn't seem to be necessary on IA64, but I suspect
  254. that may be because it doesn't execute x86 code in true SMP fashion.)
  255. This also limits the rate at which new helper processes can be spawned,
  256. which is probably a good thing. }
  257. if DelayAfterStopping then
  258. Sleep(250);
  259. end;
  260. procedure THelper.InternalCall(const ACommand, ADataSize: DWORD;
  261. const AllowProcessMessages: Boolean);
  262. var
  263. RequestSize, BytesRead, LastError: DWORD;
  264. OverlappedEvent: THandle;
  265. Res: BOOL;
  266. Overlapped: TOverlapped;
  267. begin
  268. Inc(FCommandSequenceNumber);
  269. { On entry, only Request.Data needs to be filled }
  270. FRequest.SequenceNumber := FCommandSequenceNumber;
  271. FRequest.Command := ACommand;
  272. FRequest.DataSize := ADataSize;
  273. RequestSize := Cardinal(@TRequestData(nil^).Data) + ADataSize;
  274. try
  275. {$IFDEF HELPERDEBUG}
  276. LogFmt('Helper[%u]: Sending request (size: %u): Seq=%u, Command=%u, DataSize=%u',
  277. [FProcessID, RequestSize, FRequest.SequenceNumber, FRequest.Command,
  278. FRequest.DataSize]);
  279. {$ENDIF}
  280. { Create event object to use in our Overlapped structure. (Technically,
  281. I'm not sure we need the event object -- we could just wait on the pipe
  282. object instead, however the SDK docs discourage this.) }
  283. OverlappedEvent := CreateEvent(nil, True, False, nil);
  284. if OverlappedEvent = 0 then
  285. Win32ErrorMsg('CreateEvent');
  286. try
  287. FillChar(Overlapped, SizeOf(Overlapped), 0);
  288. Overlapped.hEvent := OverlappedEvent;
  289. if not TransactNamedPipe(FPipe, @FRequest, RequestSize, @FResponse,
  290. SizeOf(FResponse), BytesRead, @Overlapped) then begin
  291. if GetLastError <> ERROR_IO_PENDING then
  292. Win32ErrorMsg('TransactNamedPipe');
  293. { Operation is pending; wait for it to complete.
  294. (Note: Waiting is never optional. The system will modify Overlapped
  295. when the operation completes; if we were to return early for whatever
  296. reason, the stack would get corrupted.) }
  297. try
  298. if AllowProcessMessages and Assigned(FProcessMessagesProc) then begin
  299. repeat
  300. { Process any pending messages first because MsgWaitForMultipleObjects
  301. (called below) only returns when *new* messages arrive }
  302. FProcessMessagesProc;
  303. until MsgWaitForMultipleObjects(1, OverlappedEvent, False,
  304. INFINITE, QS_ALLINPUT) <> WAIT_OBJECT_0+1;
  305. end;
  306. finally
  307. { Call GetOverlappedResult with bWait=True, even if exception occurred }
  308. Res := GetOverlappedResult(FPipe, Overlapped, BytesRead, True);
  309. LastError := GetLastError;
  310. end;
  311. if not Res then
  312. Win32ErrorMsgEx('TransactNamedPipe/GetOverlappedResult', LastError);
  313. end;
  314. finally
  315. CloseHandle(OverlappedEvent);
  316. end;
  317. {$IFDEF HELPERDEBUG}
  318. LogFmt('Helper[%u]: Got response (size: %u): Seq=%u, StatusCode=%u, ErrorCode=%u, DataSize=%u',
  319. [FProcessID, BytesRead, FResponse.SequenceNumber, FResponse.StatusCode,
  320. FResponse.ErrorCode, FResponse.DataSize]);
  321. {$ENDIF}
  322. if (Cardinal(BytesRead) < Cardinal(@TResponseData(nil^).Data)) or
  323. (FResponse.DataSize <> Cardinal(BytesRead) - Cardinal(@TResponseData(nil^).Data)) then
  324. InternalError('Helper: Response message has wrong size');
  325. if FResponse.SequenceNumber <> FRequest.SequenceNumber then
  326. InternalError('Helper: Wrong sequence number');
  327. if FResponse.StatusCode = 0 then
  328. InternalError('Helper: Command did not execute');
  329. except
  330. { If an exception occurred, then the helper may have crashed or is in some
  331. weird state. Attempt to stop it now, and also set FNeedsRestarting
  332. to ensure it's restarted on the next call in case our stop attempt here
  333. fails for some reason. }
  334. FNeedsRestarting := True;
  335. Log('Exception while communicating with helper:' + SNewLine + GetExceptMessage);
  336. Stop(True);
  337. raise;
  338. end;
  339. end;
  340. procedure THelper.Call(const ACommand, ADataSize: DWORD);
  341. begin
  342. { Start/restart helper if needed }
  343. if not FRunning or FNeedsRestarting then begin
  344. Stop(True);
  345. Start;
  346. end
  347. else begin
  348. { It is running -- or so we think. Before sending the specified request,
  349. send a ping request to verify that it's still alive. It may have somehow
  350. died since we last talked to it (unlikely, though). }
  351. try
  352. InternalCall(REQUEST_PING, 0, False);
  353. except
  354. { Don't propagate any exception; just log it and restart the helper }
  355. Log('Ping failed; helper seems to have died.');
  356. Stop(True);
  357. Start;
  358. end;
  359. end;
  360. InternalCall(ACommand, ADataSize, True);
  361. end;
  362. { High-level interface functions }
  363. function THelper.GrantPermission(const AObjectType: DWORD;
  364. const AObjectName: String; const AEntries: TGrantPermissionEntry;
  365. const AEntryCount: Integer; const AInheritance: DWORD): DWORD;
  366. begin
  367. if (AEntryCount < 1) or
  368. (AEntryCount > SizeOf(FRequest.GrantPermissionData.Entries) div SizeOf(FRequest.GrantPermissionData.Entries[0])) then
  369. InternalError('HelperGrantPermission: Invalid entry count');
  370. FRequest.GrantPermissionData.ObjectType := AObjectType;
  371. FRequest.GrantPermissionData.EntryCount := AEntryCount;
  372. FRequest.GrantPermissionData.Inheritance := AInheritance;
  373. FillWideCharBuffer(FRequest.GrantPermissionData.ObjectName, AObjectName);
  374. Move(AEntries, FRequest.GrantPermissionData.Entries,
  375. AEntryCount * SizeOf(FRequest.GrantPermissionData.Entries[0]));
  376. Call(REQUEST_GRANT_PERMISSION, SizeOf(FRequest.GrantPermissionData));
  377. Result := FResponse.ErrorCode;
  378. end;
  379. procedure THelper.RegisterTypeLibrary(const AUnregister: Boolean;
  380. Filename: String);
  381. { Registers or unregisters the specified type library inside the helper.
  382. Raises an exception on failure. }
  383. begin
  384. Filename := PathExpand(Filename);
  385. FRequest.RegisterTypeLibraryData.Unregister := AUnregister;
  386. FillWideCharBuffer(FRequest.RegisterTypeLibraryData.Filename, Filename);
  387. { Stop the helper before and after the call to be 100% sure the state of the
  388. helper is clean prior to and after registering. Can't trust foreign code. }
  389. Stop(False);
  390. Call(REQUEST_REGISTER_TYPE_LIBRARY, SizeOf(FRequest.RegisterTypeLibraryData));
  391. Stop(False);
  392. case FResponse.StatusCode of
  393. 1: begin
  394. { The LoadTypeLib call failed }
  395. RaiseOleError('LoadTypeLib', FResponse.ErrorCode);
  396. end;
  397. 2: begin
  398. { The call to RegisterTypeLib was made; possibly succeeded }
  399. if (FResponse.ErrorCode <> S_OK) or AUnregister then
  400. RaiseOleError('RegisterTypeLib', FResponse.ErrorCode);
  401. end;
  402. 3: begin
  403. { The ITypeLib::GetLibAttr call failed }
  404. RaiseOleError('ITypeLib::GetLibAttr', FResponse.ErrorCode);
  405. end;
  406. 4: begin
  407. { The call to UnRegisterTypeLib was made; possibly succeeded }
  408. if (FResponse.ErrorCode <> S_OK) or not AUnregister then
  409. RaiseOleError('UnRegisterTypeLib', FResponse.ErrorCode);
  410. end;
  411. else
  412. InternalError('HelperRegisterTypeLibrary: StatusCode invalid');
  413. end;
  414. end;
  415. initialization
  416. HelperMainInstance := THelper.Create;
  417. finalization
  418. FreeAndNil(HelperMainInstance);
  419. end.