diff --git a/installer/exec-with-capture.inc.iss b/installer/exec-with-capture.inc.iss deleted file mode 100644 index 2f38d6075a..0000000000 --- a/installer/exec-with-capture.inc.iss +++ /dev/null @@ -1,211 +0,0 @@ -[Code] - -type - HANDLE = LongInt; - SECURITY_ATTRIBUTES = record - nLength:DWORD; - lpSecurityDescriptor:LongInt; - bInheritHandle:BOOL; - end; - -function CreatePipe(var hReadPipe,hWritePipe:HANDLE;var lpPipeAttributes:SECURITY_ATTRIBUTES;nSize:DWORD):BOOL; -external 'CreatePipe@kernel32.dll stdcall'; - -const - HANDLE_FLAG_INHERIT=$00000001; - -function SetHandleInformation(hObject:HANDLE;dwMask,dwFlags:DWORD):integer; -external 'SetHandleInformation@kernel32.dll stdcall'; - -type - PROCESS_INFORMATION = record - hProcess:HANDLE; - hThread:HANDLE; - dwProcessId:DWORD; - dwThreadId:DWORD; - end; - LPSTR = LongInt; - LPBYTE = LongInt; - STARTUPINFO = record - cb:DWORD; - lpReserved:LPSTR; - lpDesktop:LPSTR; - lpTitle:LPSTR; - dwX:DWORD; - dwY:DWORD; - dwXSize:DWORD; - dwYSize:DWORD; - dwXCountChars:DWORD; - dwYCountChars:DWORD; - dwFillAttribute:DWORD; - dwFlags:DWORD; - wShowWindow:WORD; - cbReserved2:WORD; - lpReserved2:LPBYTE; - hStdInput:HANDLE; - hStdOutput:HANDLE; - hStdError:HANDLE; - end; - -const - STARTF_USESTDHANDLES=$00000100; - STARTF_USESHOWWINDOW=$00000001; - NORMAL_PRIORITY_CLASS=$00000020; - -function CreateProcess(lpApplicationName:LongInt;lpCommandLine:AnsiString;lpProcessAttributes,lpThreadAttributes:LongInt;bInheritHandles:LongInt;dwCreationFlags:DWORD;lpEnvironment,lpCurrentDirectory:LongInt;var lpStartupInfo:STARTUPINFO;var lpProcessInformation:PROCESS_INFORMATION):BOOL; -external 'CreateProcessA@kernel32.dll stdcall'; - -function ReadFile(hFile:HANDLE;lpBuffer:AnsiString;nNumberOfBytesToRead:DWORD;var lpNumberOfBytesRead:DWORD;lpOverlapped:LongInt):BOOL; -external 'ReadFile@kernel32.dll stdcall'; - -const - WAIT_OBJECT_0=$00000000; - -type - TMsg = record - hwnd:HWND; - message:UINT; - wParam:LongInt; - lParam:LongInt; - time:DWORD; - pt:TPoint; - end; - -const - PM_REMOVE=1; - -function PeekMessage(var lpMsg:TMsg;hWnd:HWND;wMsgFilterMin,wMsgFilterMax,wRemoveMsg:UINT):BOOL; -external 'PeekMessageA@user32.dll stdcall'; - -function TranslateMessage(const lpMsg:TMsg):BOOL; -external 'TranslateMessage@user32.dll stdcall'; - -function DispatchMessage(const lpMsg:TMsg):LongInt; -external 'DispatchMessageA@user32.dll stdcall'; - -function PeekNamedPipe(hNamedPipe:HANDLE;lpBuffer:LongInt;nBufferSize:DWORD;lpBytesRead:LongInt;var lpTotalBytesAvail:DWORD;lpBytesLeftThisMessage:LongInt):BOOL; -external 'PeekNamedPipe@kernel32.dll stdcall'; - -const - STILL_ACTIVE=$00000103; - -function GetExitCodeProcess(hProcess:HANDLE;var lpExitCode:DWORD):BOOL; -external 'GetExitCodeProcess@kernel32.dll stdcall'; - -const - ERROR_BROKEN_PIPE=109; - -function GetLastError():DWORD; -external 'GetLastError@kernel32.dll stdcall'; - -function CaptureToString(var Handle:HANDLE;var Buffer:AnsiString;Length:DWORD;var Output:String):Boolean; -var - Available:DWORD; -begin - Result:=True; - if (Handle=INVALID_HANDLE_VALUE) then - Exit; - - if not PeekNamedPipe(Handle,0,0,0,Available,0) then begin - if (GetLastError()<>ERROR_BROKEN_PIPE) then - Result:=False; - CloseHandle(Handle); - Handle:=INVALID_HANDLE_VALUE; - Exit; - end; - - if (Available>0) then begin - if not ReadFile(Handle,Buffer,Length,Length,0) then begin - Result:=False; - CloseHandle(Handle); - Handle:=INVALID_HANDLE_VALUE; - Exit; - end; - if (Length>0) then - Output:=Output+Copy(Buffer,0,Length) - else begin - // EOF - CloseHandle(Handle); - Handle:=INVALID_HANDLE_VALUE; - end; - end; -end; - -function ExecWithCapture(CommandLine:String;var StdOut,StdErr:String;var ExitCode:DWORD):Boolean; -var - SecurityAttributes:SECURITY_ATTRIBUTES; - StartupInfo:STARTUPINFO; - ProcessInformation:PROCESS_INFORMATION; - StdOutReadHandle,StdOutWriteHandle,StdErrReadHandle,StdErrWriteHandle:HANDLE; - Buffer:AnsiString; - BufferLength:DWORD; - Msg:TMsg; -begin - Result:=False; - ExitCode:=-1; - - SecurityAttributes.nLength:=sizeof(SecurityAttributes); - SecurityAttributes.bInheritHandle:=True; - - if not CreatePipe(StdOutReadHandle,StdOutWriteHandle,SecurityAttributes,0) then - Exit; - SetHandleInformation(StdOutReadHandle,HANDLE_FLAG_INHERIT,0); - if not CreatePipe(StdErrReadHandle,StdErrWriteHandle,SecurityAttributes,0) then - Exit; - SetHandleInformation(StdErrReadHandle,HANDLE_FLAG_INHERIT,0); - - StartupInfo.cb:=sizeof(StartupInfo); - StartupInfo.dwFlags:=STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW; - StartupInfo.wShowWindow:=SW_HIDE; - StartupInfo.hStdOutput:=StdOutWriteHandle; - StartupInfo.hStdError:=StdErrWriteHandle; - - if not CreateProcess(0,CommandLine,0,0,1,NORMAL_PRIORITY_CLASS,0,0,StartupInfo,ProcessInformation) then begin - CloseHandle(StdOutReadHandle); - CloseHandle(StdErrReadHandle); - CloseHandle(StdOutWriteHandle); - CloseHandle(StdErrWriteHandle); - Exit; - end; - CloseHandle(ProcessInformation.hThread); - - // unblock read pipes - CloseHandle(StdOutWriteHandle); - CloseHandle(StdErrWriteHandle); - - BufferLength:=16384; - Buffer:=StringOfChar('c',BufferLength); - while (WaitForSingleObject(ProcessInformation.hProcess,50)=WAIT_TIMEOUT) do begin - // pump messages - if Assigned(WizardForm) then begin - while PeekMessage(Msg,WizardForm.Handle,0,0,PM_REMOVE) do begin - TranslateMessage(Msg); - DispatchMessage(Msg); - end; - - // allow the window to be rendered - WizardForm.Refresh(); - end; - - // capture stdout/stderr (non-blocking) - if not CaptureToString(StdOutReadHandle,Buffer,BufferLength,StdOut) then - Exit; - if not CaptureToString(StdErrReadHandle,Buffer,BufferLength,StdErr) then - Exit; - end; - - // drain stdout/stderr - while (StdOutReadHandle<>INVALID_HANDLE_VALUE) do - if not CaptureToString(StdOutReadHandle,Buffer,BufferLength,StdOut) then - Exit; - while (StdErrReadHandle<>INVALID_HANDLE_VALUE) do - if not CaptureToString(StdErrReadHandle,Buffer,BufferLength,StdErr) then - Exit; - - if (WaitForSingleObject(ProcessInformation.hProcess,50)=WAIT_OBJECT_0) then - if GetExitCodeProcess(ProcessInformation.hProcess,ExitCode) then - if (ExitCode<>STILL_ACTIVE) then - Result:=True; - - CloseHandle(ProcessInformation.hProcess); -end; \ No newline at end of file diff --git a/installer/helpers.inc.iss b/installer/helpers.inc.iss index 3e3788d2b1..c42cfe5596 100644 --- a/installer/helpers.inc.iss +++ b/installer/helpers.inc.iss @@ -54,6 +54,25 @@ begin Result[i]:=#0; end; +// Search for the last occurence of a substring in another string or zero if not found. +// RPos exists as a preprocessor function in innosetup, but not as a scripting function. +function RPos(SubStr, S: AnyString): Integer; +var + Len,i:Longint; +begin + Len:=Length(SubStr); + + i:=Length(S)-Len+1; + while i>0 do begin + If (SameStr(SubStr,Copy(S,i,Len))) then begin + Result:=i; + Exit; + end; + end; + + Result:=0; +end; + function AppendToArray(var AnArray:TArrayOfString;Str:String):Integer; begin Result:=GetArrayLength(AnArray)+1; diff --git a/installer/install.iss b/installer/install.iss index b3bcdeb17b..fa381c2020 100644 --- a/installer/install.iss +++ b/installer/install.iss @@ -54,7 +54,7 @@ SolidCompression=yes #endif SourceDir={#SOURCE_DIR} #if BITNESS=='64' || INSTALLER_FILENAME_SUFFIX=='arm64' -ArchitecturesInstallIn64BitMode=x64 arm64 +ArchitecturesInstallIn64BitMode=x64compatible arm64 #endif #ifdef SIGNTOOL SignTool=signtool @@ -291,7 +291,6 @@ Type: files; Name: {localappdata}\Microsoft\Windows Terminal\Fragments\Git\git-b #include "environment.inc.iss" #include "putty.inc.iss" #include "modules.inc.iss" -#include "exec-with-capture.inc.iss" function ParamIsSet(Key:String):Boolean; begin @@ -605,9 +604,10 @@ var function ShutdownFSMonitorDaemons():Boolean; var FindRec:TFindRec; - ExitCode:DWORD; - Path,Str:String; + ExitCode:Integer; + Path,StdOut:String; Len,i:Integer; + Output:TExecOutput; begin Result:=False; #ifdef WITH_EXPERIMENTAL_BUILTIN_FSMONITOR @@ -627,22 +627,22 @@ begin // Find out which form to use. if (BuiltinFSMonitorStopOption='') then begin BuiltinFSMonitorStopOption:='(huh?)'; - if not ExecWithCapture('"'+AppDir+'\cmd\git.exe" fsmonitor--daemon -h',Str,Str,ExitCode) or (ExitCode<>129) then begin + if not ExecAndCaptureOutput('"'+AppDir+'\cmd\git.exe"', 'fsmonitor--daemon -h', '', SW_SHOWNORMAL, ewWaitUntilTerminated, ExitCode, Output) or (ExitCode<>129) then begin if (i<>1) and (i<>127) then // Suppress message if `git.exe` was not found, or if it does not know about the built-in FSMonitor - LogError('Could not get FSMonitor help (exit code '+IntToStr(ExitCode)+'):'+#13+Str); + LogError('Could not get FSMonitor help (exit code '+IntToStr(ExitCode)+'):'+#13+StringJoin(#13,Output.StdOut)+#13+StringJoin(#13,Output.StdErr)); Exit; end else begin - i:=Pos('stop'+#10,Str); + StdOut:=StringJoin(#13, Output.StdOut); + i:=Pos('stop'+#10,StdOut); if (i=0) then begin - LogError('Could not determine stop option from:'+#13+Str); + LogError('Could not determine stop option from:'+#13+StdOut); Exit; end; - if (i>2) and (Str[i-1]='-') and (Str[i-2]='-') then + if (i>2) and (StdOut[i-1]='-') and (StdOut[i-2]='-') then BuiltinFSMonitorStopOption:='--stop' else BuiltinFSMonitorStopOption:='stop'; end; - Str:=''; end; // The colon was replaced with an underscore by the FSMonitor daemon @@ -774,21 +774,21 @@ end; function GitSystemConfigSet(Key,Value:String):Boolean; var - ExitCode:DWORD; - StdOut,StdErr:String; + ExitCode:Integer; + Output:TExecOutput; begin if (Value=#0) then begin - if ExecWithCapture('"'+AppDir+'\{#MINGW_BITNESS}\bin\git.exe" config --system --unset-all '+Key,StdOut,StdErr,ExitCode) And ((ExitCode=0) Or (ExitCode=5)) then + if ExecAndCaptureOutput('"'+AppDir+'\{#MINGW_BITNESS}\bin\git.exe"', 'config --system --unset-all '+Key, '', SW_SHOWNORMAL, ewWaitUntilTerminated, ExitCode, Output) And ((ExitCode=0) Or (ExitCode=5)) then // exit code 5 means it was already unset, so that's okay Result:=True else begin - LogError('Unable to unset system config "'+Key+'": exit code '+IntToStr(ExitCode)+#13+#10+StdOut+#13+#10+'stderr:'+#13+#10+StdErr); + LogError('Unable to unset system config "'+Key+'": exit code '+IntToStr(ExitCode)+#13+#10+StringJoin(#13+#10,Output.StdOut)+#13+#10+'stderr:'+#13+#10+StringJoin(#13+#10,Output.StdErr)); Result:=False end - end else if ExecWithCapture('"'+AppDir+'\{#MINGW_BITNESS}\bin\git.exe" config --system --replace-all '+ShellQuote(Key)+' '+ShellQuote(Value),StdOut,StdErr,ExitCode) And (ExitCode=0) then + end else if ExecAndCaptureOutput('"'+AppDir+'\{#MINGW_BITNESS}\bin\git.exe"', 'config --system --replace-all '+ShellQuote(Key)+' '+ShellQuote(Value), '', SW_SHOWNORMAL, ewWaitUntilTerminated, ExitCode, Output) And (ExitCode=0) then Result:=True else begin - LogError('Unable to set system config "'+Key+'":="'+Value+'": exit code '+IntToStr(ExitCode)+#13+#10+StdOut+#13+#10+'stderr:'+#13+#10+StdErr); + LogError('Unable to set system config "'+Key+'":="'+Value+'": exit code '+IntToStr(ExitCode)+#13+#10+StringJoin(#13+#10,Output.StdOut)+#13+#10+'stderr:'+#13+#10+StringJoin(#13+#10,Output.StdErr)); Result:=False; end; end; @@ -815,10 +815,10 @@ end; function GetDefaultsFromGitConfig(WhichOne:String):Boolean; var - ExtraOptions,StdOut,StdErr,Key,Value:String; - ExitCode:DWORD; + ExtraOptions,Key,Value:String; Values:TArrayOfString; - c,i,j,k:Integer; + ExitCode,c,i,j,k:Integer; + Output:TExecOutput; begin if AppDir='' then begin // No previous installation detected, therefore we cannot execute `git config` @@ -837,22 +837,32 @@ begin end end; - if not ExecWithCapture('"'+AppDir+'\{#MINGW_BITNESS}\bin\git.exe" config -l -z '+ExtraOptions,StdOut,StdErr,ExitCode) then begin + if not ExecAndCaptureOutput('"'+AppDir+'\{#MINGW_BITNESS}\bin\git.exe"', 'config -l -z '+ExtraOptions, '', SW_SHOWNORMAL, ewWaitUntilTerminated, ExitCode, Output) then begin if FileExists(AppDir+'\{#MINGW_BITNESS}\bin\git.exe') then - LogError('Unable to get system config (exit code '+IntToStr(ExitCode)+'):'+#13+#10+StdErr); - end; - - // Split NUL-delimited key/value pairs, extract LF that denotes end of key - Value:=StdOut; - i:=1; j:=i; k:=i; - while (j<=Length(StdOut)) do begin - c:=Ord(StdOut[j]); - if (c=10) then - k:=j - else if (c=0) then begin - if (i<>k) then begin // Ignore keys without values - Key:=Copy(StdOut,i,k-i); - Value:=Copy(StdOut,k+1,j-k-1); + LogError('Unable to get system config (exit code '+IntToStr(ExitCode)+'):'+#13+#10+StringJoin(#13+#10,Output.StdErr)); + end; + + // git config -l -z outputs NUL-delimited key/value pairs, with a LF that denotes end of key + // ExecAndCaptureOutput splits the Output by lines. So each String in Output.StdOut could + // contain up to one Value followed by zero or more Keys, separated by NUL bytes. + Value:=''; + j:=0; + while (j1) then + if (Value='') then + Value:=Copy(Output.StdOut[j], 1, i) + else + Value:=Value+#10+Copy(Output.StdOut[j], 1, i); + if (Value<>'') then begin // Ignore keys without values case Key of 'http.sslbackend': case Value of @@ -903,9 +913,8 @@ begin RecordInferredDefault('Default Branch Option', Value) end; end; - i:=j+1; - j:=i; - k:=i; + Key:=Copy(Output.StdOut[j],c+1,k-c-1); + Value:=''; end; j:=j+1; end; @@ -1052,15 +1061,16 @@ var function GetPreviousGitVersion():String; var - Path,StdOut,StdErr:String; - ExitCode:DWORD; + Path:String; + ExitCode:Integer; + Output:TExecOutput; begin if not PreviousGitVersionInitialized then begin PreviousGitVersionInitialized:=True; if (RegQueryStringValue(HKEY_LOCAL_MACHINE,'Software\GitForWindows','InstallPath',Path)) - and (ExecWithCapture('"'+Path+'\cmd\git.exe" version',StdOut,StdErr,ExitCode)) + and (ExecAndCaptureOutput('"'+Path+'\cmd\git.exe"', 'version', '', SW_SHOWNORMAL, ewWaitUntilTerminated, ExitCode, Output)) and (ExitCode=0) then begin - PreviousGitVersion:=Trim(StdOut); + PreviousGitVersion:=Trim(Output.StdOut[0]); end; end; Result:=PreviousGitVersion; @@ -1191,7 +1201,7 @@ begin UpdateInfFilenames; #if BITNESS=='32' Result:=True; - if not IsX64 then + if not IsX64Compatible then SuppressibleMsgBox('Git for Windows (32-bit) is nearing its end of support.'+#13+'More information at https://gitforwindows.org/32-bit.html',mbError,MB_OK or MB_DEFBUTTON1,IDOK) else if not ParamIsSet('ALLOWINSTALLING32ON64') and (SuppressibleMsgBox('Git for Windows (32-bit) is nearing its end of support. It is recommended to install the 64-bit variant of Git for Windows instead.'+#13+'More information at https://gitforwindows.org/32-bit.html'+#13+'Continue to install the 32-bit variant?',mbError,MB_YESNO or MB_DEFBUTTON2,IDNO)=IDNO) then Result:=False; @@ -2574,8 +2584,9 @@ end; function ShouldSkipPage(PageID:Integer):Boolean; var - Msg,Cmd,StdOut,StdErr:String; - Res:DWORD; + Msg,Cmd:String; + Res:Integer; + Output:TExecOutput; begin if (ProcessesPage<>NIL) and (PageID=ProcessesPage.ID) then begin // This page is only reached forward (by pressing "Next", never by pressing "Back"). @@ -2584,9 +2595,9 @@ begin if DirExists(AppDir) then begin if not FileExists(ExpandConstant('{tmp}\blocked-file-util.exe')) then ExtractTemporaryFile('blocked-file-util.exe'); - Cmd:='"'+ExpandConstant('{tmp}\blocked-file-util.exe')+'" blocking-pids "'+AppDir+'"'; - if not ExecWithCapture(Cmd,StdOut,StdErr,Res) or (Res<>0) then begin - Msg:='Skipping installation because '+AppDir+' is still in use:'+#13+#10+StdErr; + Cmd:='"'+ExpandConstant('{tmp}\blocked-file-util.exe')+'"'; + if not ExecAndCaptureOutput(Cmd, 'blocking-pids "'+AppDir+'"', '', SW_SHOWNORMAL, ewWaitUntilTerminated, Res, Output) or (Res<>0) then begin + Msg:='Skipping installation because '+AppDir+' is still in use:'+#13+#10+StringJoin(#13+#10,Output.StdErr); if ParamIsSet('SKIPIFINUSE') or (ExpandConstant('{log}')='') then LogError(Msg) else @@ -2816,8 +2827,8 @@ end; procedure CleanupWhenUpgrading; var - StdOut,StdErr:String; - ErrorCode:DWORD; + ErrorCode:Integer; + Output:TExecOutput; begin if UninstallAppPath<>'' then begin // Save a copy of the system config so that we can copy it back later @@ -2832,8 +2843,8 @@ begin if UninstallString<>'' then begin WizardForm.StatusLabel.Caption:='Removing previous Git version ('+PreviousGitForWindowsVersion+')'; - if not ExecWithCapture(UninstallString+' /VERYSILENT /SILENT /NORESTART /SUPPRESSMSGBOXES',StdOut,StdErr,ErrorCode) then - LogError('Could not uninstall previous version (stderr: '+StdErr+'). Trying to continue anyway.'); + if not ExecAndCaptureOutput(UninstallString,'/VERYSILENT /SILENT /NORESTART /SUPPRESSMSGBOXES', '', SW_SHOWNORMAL, ewWaitUntilTerminated, ErrorCode, Output) then + LogError('Could not uninstall previous version (stderr: '+StringJoin(#13+#10,Output.StdErr)+'). Trying to continue anyway.'); end; end; @@ -3044,10 +3055,11 @@ end; function UpgradeFromDotNetBasedScalar:Boolean; var - RegKey,UninstallScalar,ScalarExe,Cmd,StdOut,StdErr:String; - Res:DWORD; + RegKey,UninstallScalar,ScalarExe,Cmd:String; + Res:Integer; Enlistments:TArrayOfString; i:Integer; + Output:TExecOutput; begin Result:=True; @@ -3082,9 +3094,8 @@ begin // (leaving C:\ProgramData\Scalar in place, in case // the user needs to downgrade again to get unblocked) WizardForm.StatusLabel.Caption:='Uninstalling .NET-based Scalar'; - Cmd:=UninstallScalar+'/VERYSILENT /SILENT /NORESTART /SUPPRESSMSGBOXES /LOG'; - if (not ExecWithCapture(Cmd,StdOut,StdErr,Res)) or (Res<>0) then - LogError('Could not uninstall Scalar (stderr: '+StdErr+'). Trying to continue anyway.'); + if (not ExecAndCaptureOutput(UninstallScalar, '/VERYSILENT /SILENT /NORESTART /SUPPRESSMSGBOXES /LOG', '', SW_SHOWNORMAL, ewWaitUntilTerminated, Res, Output)) or (Res<>0) then + LogError('Could not uninstall Scalar (stderr: '+StringJoin(#13+#10,Output.StdErr)+'). Trying to continue anyway.'); end; procedure CurStepChanged(CurStep:TSetupStep);