SetupLdrAndSetup.InstFunc.pas 14 KB

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