SetupLdrAndSetup.InstFunc.pas 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. unit SetupLdrAndSetup.InstFunc;
  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. Misc. installation functions. Used only by the Setup and SetupLdr projects.
  8. }
  9. interface
  10. uses
  11. Windows, SysUtils, Shared.Struct, Shared.CommonFunc;
  12. type
  13. TDetermineDefaultLanguageResult = (ddNoMatch, ddMatch, ddMatchLangParameter);
  14. TGetLanguageEntryProc = function(Index: Integer; var Entry: PSetupLanguageEntry): Boolean;
  15. function CreateTempDir(const Extension: String;
  16. const LimitCurrentUserSidAccess: Boolean; var Protected: Boolean): String; overload;
  17. function CreateTempDir(const Extension: String;
  18. const LimitCurrentUserSidAccess: Boolean): String; overload;
  19. procedure DelayDeleteFile({$IFDEF SETUPPROJ}const DisableFsRedir: Boolean;{$ENDIF} const Filename: String;
  20. const MaxTries: Integer; const FirstRetryDelayMS, SubsequentRetryDelayMS: Cardinal);
  21. function DetermineDefaultLanguage(const GetLanguageEntryProc: TGetLanguageEntryProc;
  22. const Method: TSetupLanguageDetectionMethod; const LangParameter: String;
  23. var ResultIndex: Integer): TDetermineDefaultLanguageResult;
  24. function GetFinalCurrentDir: String;
  25. function GetFinalFileName(const Filename: String): String;
  26. procedure RaiseFunctionFailedError(const FunctionName: String);
  27. function RestartComputer: Boolean;
  28. procedure SplitNewParamStr(const Index: Integer; var AName, AValue: String);
  29. {$IFDEF SETUPPROJ}
  30. { The following are not called by other SetupLdr units: they are only called by the
  31. code below and by other Setup units - so the implementations exist below but they
  32. are not included here in the interface, for clarity }
  33. function CreateSafeDirectory(const LimitCurrentUserSidAccess: Boolean; Path: String;
  34. var ErrorCode: DWORD; out Protected: Boolean): Boolean; overload;
  35. function CreateSafeDirectory(const LimitCurrentUserSidAccess: Boolean; Path: String;
  36. var ErrorCode: DWORD): Boolean; overload;
  37. function UIntToBase36Str(AValue: UInt32; const ADigits: Integer): String;
  38. function GenerateUniqueName({$IFDEF SETUPPROJ}const DisableFsRedir: Boolean;{$ENDIF} Path: String;
  39. const Extension: String): String;
  40. {$ENDIF}
  41. implementation
  42. uses
  43. PathFunc, SetupLdrAndSetup.Messages, Shared.SetupMessageIDs{$IFDEF SETUPPROJ}, Setup.RedirFunc{$ENDIF};
  44. function ConvertStringSecurityDescriptorToSecurityDescriptorW(
  45. StringSecurityDescriptor: PWideChar;
  46. StringSDRevision: DWORD; var ppSecurityDescriptor: Pointer;
  47. dummy: Pointer): BOOL; stdcall; external advapi32;
  48. function CreateSafeDirectory(const LimitCurrentUserSidAccess: Boolean; Path: String;
  49. var ErrorCode: DWORD; out Protected: Boolean): Boolean; overload;
  50. { Creates a protected directory if
  51. -permissions are supported
  52. -it's a subdirectory of c:\WINDOWS\TEMP, or
  53. -it's on a local drive and LimitCurrentUserSidAccess is True (latter is true atm if elevated and not debugging)
  54. otherwise creates a normal directory. }
  55. const
  56. SDDL_REVISION_1 = 1;
  57. begin
  58. Path := PathExpand(Path);
  59. var Drive := PathExtractDrive(Path);
  60. var FileSystemFlags: DWORD;
  61. if GetVolumeInformation(PChar(AddBackslash(Drive)), nil, 0, nil, DWORD(nil^), FileSystemFlags, nil, 0) and
  62. ((FileSystemFlags and FILE_PERSISTENT_ACLS) <> 0) then begin
  63. var IsUnderWindowsTemp := Pos(PathLowercase(AddBackslash(GetSystemWinDir) + 'TEMP\'),
  64. PathLowercase(Path)) = 1;
  65. var IsLocalTempToProtect := LimitCurrentUserSidAccess and (Drive <> '') and
  66. not PathCharIsSlash(Drive[1]) and
  67. (GetDriveType(PChar(AddBackslash(Drive))) <> DRIVE_REMOTE);
  68. Protected := IsUnderWindowsTemp or IsLocalTempToProtect;
  69. end else
  70. Protected := False;
  71. if Protected then begin
  72. var StringSecurityDescriptor :=
  73. // D: adds a Discretionary ACL ("DACL", i.e. access control via SIDs)
  74. // P: prevents DACL from being modified by inheritable ACEs
  75. // AI: says automatic propagation of inheritable ACEs to child objects
  76. // is supported; always supposed to be set on Windows 2000+ ACLs
  77. 'D:PAI';
  78. var CurrentUserSid := GetCurrentUserSid;
  79. if CurrentUserSid = '' then
  80. CurrentUserSid := 'OW'; // OW: owner rights
  81. { Omit the CurrentUserSid ACE if the current user is SYSTEM, because
  82. there's already a fixed Full Control ACE for SYSTEM below }
  83. if not SameText(CurrentUserSid, 'S-1-5-18') then begin
  84. // A: "allow"
  85. // OICI: "object and container inherit",
  86. // i.e. files and directories created within the new directory
  87. // inherit these permissions
  88. var AccessRights := 'FA'; // FILE_ALL_ACCESS (Full Control)
  89. if LimitCurrentUserSidAccess then
  90. AccessRights := 'FRFX'; // FILE_GENERIC_READ | FILE_GENERIC_EXECUTE
  91. StringSecurityDescriptor := StringSecurityDescriptor +
  92. '(A;OICI;' + AccessRights + ';;;' + CurrentUserSid + ')'; // current user
  93. end;
  94. StringSecurityDescriptor := StringSecurityDescriptor +
  95. '(A;OICI;FA;;;BA)' + // BA: built-in Administrators group
  96. '(A;OICI;FA;;;SY)'; // SY: local SYSTEM account
  97. var pSecurityDescriptor: Pointer;
  98. if not ConvertStringSecurityDescriptorToSecurityDescriptorW(
  99. PWideChar(StringSecurityDescriptor), SDDL_REVISION_1, pSecurityDescriptor, nil
  100. ) then begin
  101. ErrorCode := GetLastError;
  102. Result := False;
  103. Exit;
  104. end;
  105. var SecurityAttr: TSecurityAttributes;
  106. SecurityAttr.nLength := SizeOf(SecurityAttr);
  107. SecurityAttr.bInheritHandle := False;
  108. SecurityAttr.lpSecurityDescriptor := pSecurityDescriptor;
  109. Result := CreateDirectory(PChar(Path), @SecurityAttr);
  110. if not Result then
  111. ErrorCode := GetLastError;
  112. LocalFree(pSecurityDescriptor);
  113. end else begin
  114. Result := CreateDirectory(PChar(Path), nil);
  115. if not Result then
  116. ErrorCode := GetLastError;
  117. end;
  118. end;
  119. function CreateSafeDirectory(const LimitCurrentUserSidAccess: Boolean; Path: String;
  120. var ErrorCode: DWORD): Boolean; overload;
  121. begin
  122. var Protected: Boolean;
  123. Result := CreateSafeDirectory(LimitCurrentUserSidAccess, Path, ErrorCode, Protected);
  124. end;
  125. function UIntToBase36Str(AValue: UInt32; const ADigits: Integer): String;
  126. begin
  127. Result := StringOfChar('0', ADigits);
  128. for var I := High(Result) downto Low(Result) do begin
  129. var Digit := AValue mod 36;
  130. if Digit < 10 then
  131. Inc(Digit, Ord('0'))
  132. else
  133. Inc(Digit, Ord('A') - 10);
  134. Result[I] := Chr(Digit);
  135. AValue := AValue div 36;
  136. end;
  137. end;
  138. function GenerateUniqueName({$IFDEF SETUPPROJ}const DisableFsRedir: Boolean;{$ENDIF} Path: String;
  139. const Extension: String): String;
  140. const
  141. FiveDigitsRange = 36 * 36 * 36 * 36 * 36;
  142. begin
  143. Path := AddBackslash(Path);
  144. var Filename: String;
  145. var AttemptNumber := 0;
  146. repeat
  147. { If 50 attempts were made and every generated name was found to exist
  148. already, then stop trying, because something really strange is going
  149. on -- like the file system is claiming everything exists regardless of
  150. name. }
  151. Inc(AttemptNumber);
  152. if AttemptNumber > 50 then
  153. raise Exception.Create(FmtSetupMessage1(msgErrorTooManyFilesInDir,
  154. RemoveBackslashUnlessRoot(Path)));
  155. Filename := Path + 'is-' +
  156. UIntToBase36Str(TStrongRandom.GenerateUInt32Range(FiveDigitsRange), 5) +
  157. UIntToBase36Str(TStrongRandom.GenerateUInt32Range(FiveDigitsRange), 5) +
  158. Extension;
  159. until not {$IFDEF SETUPPROJ}FileOrDirExistsRedir(DisableFsRedir, Filename){$ELSE}FileOrDirExists(Filename){$ENDIF};
  160. Result := Filename;
  161. end;
  162. function CreateTempDir(const Extension: String;
  163. const LimitCurrentUserSidAccess: Boolean; var Protected: Boolean): String;
  164. { This is called by SetupLdr, Setup, and Uninstall. }
  165. var
  166. Dir: String;
  167. ErrorCode: DWORD;
  168. begin
  169. while True do begin
  170. Dir := GenerateUniqueName({$IFDEF SETUPPROJ}False,{$ENDIF} GetTempDir, Extension);
  171. if CreateSafeDirectory(LimitCurrentUserSidAccess, Dir, ErrorCode, Protected) then
  172. Break;
  173. if ErrorCode <> ERROR_ALREADY_EXISTS then
  174. raise Exception.Create(FmtSetupMessage(msgLastErrorMessage,
  175. [FmtSetupMessage1(msgErrorCreatingDir, Dir), IntToStr(ErrorCode),
  176. Win32ErrorString(ErrorCode)]));
  177. end;
  178. Result := Dir;
  179. end;
  180. function CreateTempDir(const Extension: String;
  181. const LimitCurrentUserSidAccess: Boolean): String;
  182. begin
  183. var Protected: Boolean;
  184. Result := CreateTempDir(Extension, LimitCurrentUserSidAccess, Protected);
  185. end;
  186. { Work around problem in D2's declaration of the function }
  187. function NewAdjustTokenPrivileges(TokenHandle: THandle; DisableAllPrivileges: BOOL;
  188. const NewState: TTokenPrivileges; BufferLength: DWORD;
  189. PreviousState: PTokenPrivileges; ReturnLength: PDWORD): BOOL; stdcall;
  190. external advapi32 name 'AdjustTokenPrivileges';
  191. function RestartComputer: Boolean;
  192. { Restarts the computer. }
  193. var
  194. Token: THandle;
  195. TokenPriv: TTokenPrivileges;
  196. const
  197. SE_SHUTDOWN_NAME = 'SeShutdownPrivilege'; { don't localize }
  198. begin
  199. if not OpenProcessToken(GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY,
  200. Token) then begin
  201. Result := False;
  202. Exit;
  203. end;
  204. LookupPrivilegeValue(nil, SE_SHUTDOWN_NAME, TokenPriv.Privileges[0].Luid);
  205. TokenPriv.PrivilegeCount := 1;
  206. TokenPriv.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
  207. NewAdjustTokenPrivileges(Token, False, TokenPriv, 0, nil, nil);
  208. { Cannot test the return value of AdjustTokenPrivileges. }
  209. if GetLastError <> ERROR_SUCCESS then begin
  210. Result := False;
  211. Exit;
  212. end;
  213. Result := ExitWindowsEx(EWX_REBOOT, 0);
  214. { ExitWindowsEx returns True immediately. The system then asynchronously
  215. sends WM_QUERYENDSESSION messages to all processes, including the current
  216. process. The current process is not killed until it has received
  217. WM_QUERYENDSESSION and WM_ENDSESSION messages. }
  218. end;
  219. procedure DelayDeleteFile({$IFDEF SETUPPROJ}const DisableFsRedir: Boolean;{$ENDIF} const Filename: String;
  220. const MaxTries: Integer; const FirstRetryDelayMS, SubsequentRetryDelayMS: Cardinal);
  221. { Attempts to delete Filename up to MaxTries times, retrying if the file is
  222. in use. It sleeps FirstRetryDelayMS msec after the first try, and
  223. SubsequentRetryDelayMS msec after subsequent tries. }
  224. begin
  225. for var I := 0 to MaxTries-1 do begin
  226. if I = 1 then
  227. Sleep(FirstRetryDelayMS)
  228. else if I > 1 then
  229. Sleep(SubsequentRetryDelayMS);
  230. if {$IFDEF SETUPPROJ}DeleteFileRedir(DisableFsRedir, Filename){$ELSE}Windows.DeleteFile(PChar(Filename)){$ENDIF} or
  231. (GetLastError = ERROR_FILE_NOT_FOUND) or
  232. (GetLastError = ERROR_PATH_NOT_FOUND) then
  233. Break;
  234. end;
  235. end;
  236. function DetermineDefaultLanguage(const GetLanguageEntryProc: TGetLanguageEntryProc;
  237. const Method: TSetupLanguageDetectionMethod; const LangParameter: String;
  238. var ResultIndex: Integer): TDetermineDefaultLanguageResult;
  239. { Finds the index of the language entry that most closely matches the user's
  240. UI language / locale. If no match is found, ResultIndex is set to 0. }
  241. function GetCodePageFromLangID(const ALangID: LANGID): Integer;
  242. const
  243. LOCALE_RETURN_NUMBER = $20000000;
  244. var
  245. CodePage: DWORD;
  246. begin
  247. if GetLocaleInfo(ALangID, LOCALE_IDEFAULTANSICODEPAGE or LOCALE_RETURN_NUMBER,
  248. PChar(@CodePage), SizeOf(CodePage) div SizeOf(Char)) > 0 then
  249. Result := Integer(CodePage)
  250. else
  251. Result := -1;
  252. end;
  253. var
  254. I: Integer;
  255. LangEntry: PSetupLanguageEntry;
  256. UILang: LANGID;
  257. begin
  258. ResultIndex := 0;
  259. Result := ddNoMatch;
  260. if LangParameter <> '' then begin
  261. { Use the language specified on the command line, if available }
  262. I := 0;
  263. while GetLanguageEntryProc(I, LangEntry) do begin
  264. if CompareText(LangParameter, LangEntry.Name) = 0 then begin
  265. ResultIndex := I;
  266. Result := ddMatchLangParameter;
  267. Exit;
  268. end;
  269. Inc(I);
  270. end;
  271. end;
  272. case Method of
  273. ldUILanguage: UILang := GetUILanguage;
  274. ldLocale: UILang := GetUserDefaultLangID;
  275. else
  276. { ldNone }
  277. UILang := 0;
  278. end;
  279. if UILang <> 0 then begin
  280. { Look for a primary + sub language ID match }
  281. I := 0;
  282. while GetLanguageEntryProc(I, LangEntry) do begin
  283. if LangEntry.LanguageID = UILang then begin
  284. ResultIndex := I;
  285. Result := ddMatch;
  286. Exit;
  287. end;
  288. Inc(I);
  289. end;
  290. { Look for just a primary language ID match }
  291. I := 0;
  292. while GetLanguageEntryProc(I, LangEntry) do begin
  293. if (LangEntry.LanguageID and $3FF) = (UILang and $3FF) then begin
  294. { On Unicode, there is no LanguageCodePage filter, so we have to check
  295. the language IDs to ensure we don't return Simplified Chinese on a
  296. Traditional Chinese system, or vice versa.
  297. If the default ANSI code pages associated with the language IDs are
  298. equal, then there is no Simplified/Traditional discrepancy.
  299. Simplified Chinese LANGIDs ($0804, $1004) use CP 936
  300. Traditional Chinese LANGIDs ($0404, $0C04, $1404) use CP 950 }
  301. if ((UILang and $3FF) <> LANG_CHINESE) or
  302. (GetCodePageFromLangID(LangEntry.LanguageID) = GetCodePageFromLangID(UILang)) then
  303. begin
  304. ResultIndex := I;
  305. Result := ddMatch;
  306. Exit;
  307. end;
  308. end;
  309. Inc(I);
  310. end;
  311. end;
  312. end;
  313. procedure SplitNewParamStr(const Index: Integer; var AName, AValue: String);
  314. { Reads a command line parameter. If it is in the form "/PARAM=VALUE" then
  315. AName is set to "/PARAM=" and AValue is set to "VALUE". Otherwise, the full
  316. parameter is stored in AName, and AValue is set to an empty string. }
  317. var
  318. S: String;
  319. P: Integer;
  320. begin
  321. S := NewParamStr(Index);
  322. if (S <> '') and (S[1] = '/') then begin
  323. P := PathPos('=', S);
  324. if P <> 0 then begin
  325. AName := Copy(S, 1, P);
  326. AValue := Copy(S, P+1, Maxint);
  327. Exit;
  328. end;
  329. end;
  330. AName := S;
  331. AValue := '';
  332. end;
  333. procedure RaiseFunctionFailedError(const FunctionName: String);
  334. begin
  335. raise Exception.Create(FmtSetupMessage1(msgErrorFunctionFailedNoCode,
  336. FunctionName));
  337. end;
  338. function GetFinalFileName(const Filename: String): String;
  339. { Calls GetFinalPathNameByHandle to expand any SUBST'ed drives, network drives,
  340. and symbolic links in Filename. This is needed for elevation to succeed when
  341. Setup is started from a SUBST'ed drive letter. }
  342. function ConvertToNormalPath(P: PChar): String;
  343. begin
  344. Result := P;
  345. if StrLComp(P, '\\?\', 4) = 0 then begin
  346. Inc(P, 4);
  347. if (PathStrNextChar(P) = P + 1) and (P[1] = ':') and PathCharIsSlash(P[2]) then
  348. Result := P
  349. else if StrLIComp(P, 'UNC\', 4) = 0 then begin
  350. Inc(P, 4);
  351. Result := '\\' + P;
  352. end;
  353. end;
  354. end;
  355. const
  356. FILE_SHARE_DELETE = $00000004;
  357. var
  358. GetFinalPathNameByHandleFunc: function(hFile: THandle; lpszFilePath: PWideChar;
  359. cchFilePath: DWORD; dwFlags: DWORD): DWORD; stdcall;
  360. Attr, FlagsAndAttributes: DWORD;
  361. H: THandle;
  362. Buf: array[0..4095] of Char;
  363. begin
  364. GetFinalPathNameByHandleFunc := GetProcAddress(GetModuleHandle(kernel32),
  365. 'GetFinalPathNameByHandleW');
  366. if Assigned(GetFinalPathNameByHandleFunc) then begin
  367. Attr := GetFileAttributes(PChar(Filename));
  368. if Attr <> INVALID_FILE_ATTRIBUTES then begin
  369. { Backup semantics must be requested in order to open a directory }
  370. if Attr and FILE_ATTRIBUTE_DIRECTORY <> 0 then
  371. FlagsAndAttributes := FILE_FLAG_BACKUP_SEMANTICS
  372. else
  373. FlagsAndAttributes := 0;
  374. { Use zero access mask and liberal sharing mode to ensure success }
  375. H := CreateFile(PChar(Filename), 0, FILE_SHARE_READ or FILE_SHARE_WRITE or
  376. FILE_SHARE_DELETE, nil, OPEN_EXISTING, FlagsAndAttributes, 0);
  377. if H <> INVALID_HANDLE_VALUE then begin
  378. const Res = GetFinalPathNameByHandleFunc(H, Buf, SizeOf(Buf) div SizeOf(Buf[0]), 0);
  379. CloseHandle(H);
  380. if (Res > 0) and (Res < (SizeOf(Buf) div SizeOf(Buf[0])) - 16) then begin
  381. { ShellExecuteEx fails with error 3 on \\?\UNC\ paths, so try to
  382. convert the returned path from \\?\ form }
  383. Result := ConvertToNormalPath(Buf);
  384. Exit;
  385. end;
  386. end;
  387. end;
  388. end;
  389. Result := Filename;
  390. end;
  391. function GetFinalCurrentDir: String;
  392. var
  393. Res: Integer;
  394. Buf: array[0..MAX_PATH-1] of Char;
  395. begin
  396. DWORD(Res) := GetCurrentDirectory(SizeOf(Buf) div SizeOf(Buf[0]), Buf);
  397. if (Res > 0) and (Res < SizeOf(Buf) div SizeOf(Buf[0])) then
  398. Result := GetFinalFileName(Buf)
  399. else begin
  400. RaiseFunctionFailedError('GetCurrentDirectory');
  401. Result := '';
  402. end;
  403. end;
  404. end.