SetupLdrAndSetup.InstFunc.pas 12 KB

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