2
0

SetupLdrAndSetup.InstFunc.pas 16 KB

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