Helper.pas 16 KB

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