{ This file is a part of the Open Source Synopse mORMot framework 2, licensed under a MPL/GPL/LGPL three license - see LICENSE.md Windows API calls for FPC/Delphi, as used by mormot.core.os.pas } { ****************** Unicode, Time, File process } const /// Windows file APIs have hardcoded MAX_PATH = 260 :( // - but more than 260 chars are possible with the \\?\..... prefix // or by disabling the limitation in registry since Windows 10, version 1607 // https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation // - extended-length path allows up to 32,767 widechars // - but 2047 chars seems big enough in practice e.g. due to NTFS - POSIX uses 4096 W32_MAX = 2047; type /// 4KB stack buffer for no heap allocation during UTF-16 encoding or // switch to extended-length path TW32Temp = array[0..W32_MAX] of WideChar; // W32() gets a PWideChar buffer from a TFileName using TW32Temp static buffer // W32Copy() forces a temporary copy - as used by GetFileVersionInfo() {$ifdef UNICODE} function W32Copy(const FileName: TFileName; var Temp: TW32Temp): PWideChar; var len: PtrInt; begin len := length(FileName) * 2 + 2; // +2 to include ending #0 if len > SizeOf(Temp) then Temp[0] := #0 // avoid buffer overflow else MoveFast(pointer(FileName)^, Temp, len); result := @Temp; end; // UnicodeString may need to be converted to extended-length path function W32(const FileName: TFileName; var Temp: TW32Temp): PWideChar; var U: pointer; // hold a UnicodeString but with no try..finally begin if (length(FileName) > MAX_PATH) and (ord(FileName[1]) in [ord('A')..ord('Z'), ord('a')..ord('z')]) and (FileName[2] = ':') then begin // convert to extended-length path U := nil; TFileName(U) := '\\?\' + FileName; // move to TW32Temp stack buffer result := W32Copy(TFileName(U), Temp); // release temp memory and return the generated static buffer TFileName(U) := ''; end else // no conversion nor allocation needed result := pointer(FileName); end; {$else} // AnsiString is converted to UTF-16, potentially with extended-length path procedure W32Convert(const FileName: TFileName; var Temp: TW32Temp); var U: SynUnicode; len: PtrInt; begin // identical to FPC RTL, which converts to UnicodeString before Wide API call U := SynUnicode(FileName); // let the RTL + OS do the conversion // switch to extended-length path if needed, allowing up to 32,767 widechars len := length(U); if (len > MAX_PATH) and (FileName[1] in ['A'..'Z', 'a'..'z']) and (FileName[2] = ':') then U := '\\?\' + U; // move to TW32Temp stack buffer len := length(U) * 2 + 2; // +2 to include ending #0 if len > SizeOf(Temp) then Temp[0] := #0 // avoid buffer overflow (rejected by Windows anyway) else MoveFast(pointer(U)^, Temp, len); end; function W32(const FileName: TFileName; var Temp: TW32Temp): PWideChar; var i, len: PtrInt; begin len := length(FileName); if len = 0 then result := nil else begin if (len < MAX_PATH) and IsAnsiCompatible(pointer(FileName), len) then // most common cases do not need any Unicode conversion for i := 0 to len do // include trailing #0 PWordArray(@Temp)[i] := PByteArray(FileName)[i] else // use a temporary SynUnicode variable for complex UTF-16 conversion // or if MAX_PATH is reached and \\?\ prefix is needed for extended length W32Convert(FileName, Temp); result := @Temp; end; end; function W32Copy(const FileName: TFileName; var Temp: TW32Temp): PWideChar; begin result := W32(FileName, Temp); // from AnsiString: name is always copied end; {$endif UNICODE} function _fmt(const Fmt: string; const Args: array of const): RawUtf8; overload; begin result := RawUtf8(format(Fmt, Args)); // good enough (seldom called) end; procedure _fmt(const Fmt: string; const Args: array of const; var result: RawUtf8); overload; begin result := RawUtf8(format(Fmt, Args)); // good enough (seldom called) end; procedure DoWin32PWideCharToUtf8(P: PWideChar; Len: PtrInt; var res: RawUtf8); var tmp: TSynTempBuffer; begin tmp.Init(Len * 3); Len := UnicodeToUtf8(tmp.Buf, Len * 3, P, Len); // use RTL if complex if Len > 0 then dec(Len); // UnicodeToUtf8() result includes the null terminator FastSetString(res, tmp.buf, Len); tmp.Done; end; // local RTL wrapper functions to avoid linking mormot.core.unicode.pas procedure Win32PWideCharToUtf8(P: PWideChar; Len: PtrInt; out res: RawUtf8); var i: PtrInt; begin if Len > 0 then if IsAnsiCompatibleW(P, Len) then begin FastSetString(res, Len); for i := 0 to Len - 1 do PByteArray(res)[i] := PWordArray(P)[i]; // fast direct conversion end else DoWin32PWideCharToUtf8(P, Len, res); end; procedure Win32PWideCharToUtf8(P: PWideChar; out res: RawUtf8); begin if P <> nil then Win32PWideCharToUtf8(P, StrLenW(P), res); end; function Utf8ToWin32PWideChar(const Text: RawUtf8; var dest: TSynTempBuffer): PWideChar; var TextLen, i: PtrInt; begin result := nil; TextLen := length(Text); dest.Init(TextLen * 2); if dest.len = 0 then exit; result := dest.buf; if IsAnsiCompatible(PAnsiChar(pointer(Text)), TextLen) then begin dest.len := TextLen; for i := 0 to TextLen do // include trailing #0 PWordArray(result)[i] := PByteArray(Text)[i]; end else begin dest.len := Utf8ToUnicode(result, dest.Len + 16, pointer(Text), TextLen); if dest.len <= 0 then dest.len := 0 else begin dec(dest.len); // Utf8ToUnicode() returned length includes trailing #0 result[dest.len] := #0; // missing on FPC end; end; end; const DefaultCharVar: AnsiChar = '?'; function Unicode_AnsiToWide(A: PAnsiChar; W: PWideChar; LA, LW, CodePage: PtrInt): integer; begin result := MultiByteToWideChar(CodePage, MB_PRECOMPOSED, A, LA, W, LW); end; function Unicode_WideToAnsi(W: PWideChar; A: PAnsiChar; LW, LA, CodePage: PtrInt): integer; begin result := WideCharToMultiByte(CodePage, 0, W, LW, A, LA, @DefaultCharVar, nil); end; function LibraryOpen(const LibraryName: TFileName): TLibHandle; var tmp: TW32Temp; err: DWord; {$ifdef CPUX86} x87cw: word; {$endif CPUX86} begin // note: GetErrorMode() is not available on XP err := SetErrorMode(SEM_NOOPENFILEERRORBOX or SEM_FAILCRITICALERRORS); {$ifdef CPUX86} asm fnstcw x87cw // save x87 flags end; {$endif CPUX86} result := Windows.LoadLibraryW(W32(LibraryName, tmp)); {$ifdef CPUX86} asm fnclex // clear pending x87 exceptions fldcw x87cw // restore flags (Visual C++ librairies usually change them) end; {$endif CPUX86} SetErrorMode(err); end; procedure LibraryClose(Lib: TLibHandle); begin if pointer(Lib) <> nil then Windows.FreeLibrary(Lib); end; // Delphi Unicode has an ambiguous GetProcAddress() overload with PWideChar function LibraryResolve(Lib: TLibHandle; ProcName: PAnsiChar): pointer; external kernel32 name 'GetProcAddress'; // this is an Ansi-only API function LibraryError: string; begin result := IntToStr(GetLastError); // enough for basic troubleshouting end; procedure FileTimeToInt64(const FT: TFileTime; out I64: Int64); {$ifdef HASINLINE} inline; {$endif} begin PInt64Rec(@I64)^.Lo := FT.dwLowDateTime; // Delphi 2007 bug with PInt64() PInt64Rec(@I64)^.Hi := FT.dwHighDateTime; end; const SecsPerFileTime = 10000000; MilliSecsPerFileTime = 10000; procedure UnixTimeToFileTime(I64: TUnixTime; out FT: TFileTime); begin I64 := (I64 * SecsPerFileTime) + UnixFileTimeDelta; FT.dwLowDateTime := PInt64Rec(@I64)^.Lo; // Delphi 2007 bug with PInt64() FT.dwHighDateTime := PInt64Rec(@I64)^.Hi; end; procedure UnixMSTimeToFileTime(I64: TUnixMSTime; out FT: TFileTime); begin I64 := (I64 * MilliSecsPerFileTime) + UnixFileTimeDelta; FT.dwLowDateTime := PInt64Rec(@I64)^.Lo; // Delphi 2007 bug with PInt64() FT.dwHighDateTime := PInt64Rec(@I64)^.Hi; end; procedure UnixTimeToLocalTime(I64: TUnixTime; out Local: TSystemTime); var ft, lt: TFileTime; begin UnixTimeToFileTime(I64, ft); FileTimeToLocalFileTime(ft, lt); FileTimeToSystemTime(lt, Local); end; function FileTimeToUnixTime(const FT: TFileTime): TUnixTime; {$ifdef CPU64} var nano100: Int64; // TFileTime is in 100 ns unit {$endif CPU64} begin if PInt64(@FT)^ = 0 then begin result := 0; exit; end; {$ifdef CPU64} FileTimeToInt64(ft, nano100); result := (nano100 - UnixFileTimeDelta) div SecsPerFileTime; {$else} // use PInt64 to avoid URW699 with Delphi 6 / Kylix result := (PInt64(@ft)^ - UnixFileTimeDelta) div SecsPerFileTime; {$endif CPU64} end; function FileTimeToUnixMSTime(const FT: TFileTime): TUnixMSTime; {$ifdef CPU64} var nano100: Int64; // TFileTime is in 100 ns unit {$endif CPU64} begin if PInt64(@FT)^ = 0 then begin result := 0; exit; end; {$ifdef CPU64} FileTimeToInt64(ft, nano100); result := (nano100 - UnixFileTimeDelta) div MilliSecsPerFileTime; {$else} result := (PInt64(@ft)^ - UnixFileTimeDelta) div MilliSecsPerFileTime; {$endif CPU64} end; function FileTimeToDateTime(const FT: TFileTime): TDateTime; begin if PInt64(@FT)^ = 0 then result := 0 else // inlined UnixTimeToDateTime() result := FileTimeToUnixMSTime(FT) / MSecsPerDay + UnixDateDelta; end; procedure DateTimeToFileTime(dt: TDateTime; out FT: TFileTime); begin if dt = 0 then PInt64(@FT)^ := 0 else // inlined DateTimeToUnixTime() UnixTimeToFileTime(Round((dt - UnixDateDelta) * SecsPerDay), FT); end; function UnixTimeUtc: TUnixTime; var ft: TFileTime; begin GetSystemTimeAsFileTime(ft); // fast (HW resolution is < TUnixTime second) result := FileTimeToUnixTime(ft); end; var // redirect to a slower but more accurate API available since Windows 8 // - points to GetSystemTimeAsFileTime() before Windows 8 GetSystemTimePreciseAsFileTime: procedure(var ft: TFILETIME); stdcall; function UnixMSTimeUtc: TUnixMSTime; var ft: TFileTime; begin GetSystemTimePreciseAsFileTime(ft); // slower, but try to achieve ms resolution result := FileTimeToUnixMSTime(ft); end; function UnixMSTimeUtcFast: TUnixMSTime; var ft: TFileTime; begin GetSystemTimeAsFileTime(ft); // faster, but with HW interupt resolution result := FileTimeToUnixMSTime(ft); end; procedure GetSystemTime; external kernel32; procedure GetLocalTime; external kernel32; procedure InitializeCriticalSection; external kernel32; procedure EnterCriticalSection; external kernel32; procedure LeaveCriticalSection; external kernel32; procedure DeleteCriticalSection; external kernel32; function TryEnterCriticalSection; external kernel32; function CloseHandle; external kernel32; procedure FileClose; external kernel32 name 'CloseHandle'; function GetCurrentThreadId; external kernel32; procedure SwitchToThread; external kernel32; function GetCurrentProcessId; external kernel32; function GetCurrentProcess; external kernel32; function WaitForSingleObject; external kernel32; function GetEnvironmentStringsW; external kernel32; function FreeEnvironmentStringsW; external kernel32; function RtlCaptureStackBackTrace; external kernel32; function IsDebuggerPresent; external kernel32; procedure SetEndOfFile; external kernel32; procedure FlushFileBuffers; external kernel32; function GetLastError; external kernel32; procedure SetLastError; external kernel32; function IocpCreate; external kernel32 name 'CreateIoCompletionPort'; function IocpGetQueuedStatus; external kernel32 name 'GetQueuedCompletionStatus'; function IocpPostQueuedStatus; external kernel32 name 'PostQueuedCompletionStatus'; function GetDesktopWindow; external user32; function Unicode_InPlaceUpper; external user32 name 'CharUpperBuffW'; function Unicode_InPlaceLower; external user32 name 'CharLowerBuffW'; function HasConsole: boolean; begin if StdOut = 0 then StdOut := GetStdHandle(STD_OUTPUT_HANDLE); result := (StdOut <> 0) and (StdOut <> INVALID_HANDLE_VALUE); end; procedure AllocConsole; begin Windows.AllocConsole; if (StdOut = 0) or (StdOut = INVALID_HANDLE_VALUE) then // force setup StdOut global variable StdOut := GetStdHandle(STD_OUTPUT_HANDLE); end; {$I-} procedure DisplayFatalError(const title, msg: RawUtf8); begin // better than a MessageBox() especially for services AllocConsole; // will create one black window console if none if title <> '' then begin TextColor(ccWhite); writeln(#13#10, title); writeln(StringOfChar('-', length(title) + 1), #13#10); TextColor(ccLightRed); writeln(msg); TextColor(ccLightGray); end else writeln(msg); ioresult; end; {$I+} function IsSharedViolation(ErrorCode: integer): boolean; begin if ErrorCode = 0 then ErrorCode := GetLastError; result := ErrorCode in [ERROR_SHARING_VIOLATION, ERROR_LOCK_VIOLATION]; end; function GetModuleHandle(lpModuleName: PChar): HMODULE; begin result := Windows.GetModuleHandle(lpModuleName); // call either A or W API end; function SetSystemTime(const utctime: TSystemTime): boolean; var privileges: TSynWindowsPrivileges; begin try privileges.Init; try privileges.Enable(wspSystemTime); // ensure has SE_SYSTEMTIME_NAME result := Windows.SetSystemTime(PSystemTime(@utctime)^); finally privileges.Done; end; if result then PostMessage(HWND_BROADCAST, WM_TIMECHANGE, 0, 0); // notify the apps except result := false; end; end; const // https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes ERROR_STANDARD1: array[0..39] of PUtf8Char = ( 'SUCCESS', 'INVALID_FUNCTION', 'FILE_NOT_FOUND', 'PATH_NOT_FOUND', 'TOO_MANY_OPEN_FILES', 'ACCESS_DENIED', 'INVALID_HANDLE', 'ARENA_TRASHED', 'NOT_ENOUGH_MEMORY', 'INVALID_BLOCK', 'BAD_ENVIRONMENT', 'BAD_FORMAT', 'INVALID_ACCESS', 'INVALID_DATA', 'OUTOFMEMORY', 'INVALID_DRIVE', 'CURRENT_DIRECTORY', 'NOT_SAME_DEVICE', 'NO_MORE_FILES', 'WRITE_PROTECT', 'BAD_UNIT', 'NOT_READY', 'BAD_COMMAND', 'CRC', 'BAD_LENGTH', 'SEEK', 'NOT_DOS_DISK', 'SECTOR_NOT_FOUND', 'OUT_OF_PAPER', 'WRITE_FAULT', 'READ_FAULT', 'GEN_FAILURE', 'SHARING_VIOLATION', 'LOCK_VIOLATION', 'WRONG_DISK', '35', 'SHARING_BUFFER_EXCEEDED', '37', 'HANDLE_EOF', 'HANDLE_DISK_FULL'); ERROR_STANDARD2: array[50..55] of PUtf8Char = ( 'NOT_SUPPORTED', 'REM_NOT_LIST', 'DUP_NAME', 'BAD_NETPATH', 'NETWORK_BUSY', 'DEV_NOT_EXIST'); ERROR_STANDARD3: array[80..89] of PUtf8Char = ( 'FILE_EXISTS', '81', 'CANNOT_MAKE', 'FAIL_I24', 'OUT_OF_STRUCTURES', 'ALREADY_ASSIGNED', 'INVALID_PASSWORD', 'INVALID_PARAMETER', 'NET_WRITE_FAULT', 'NO_PROC_SLOTS'); ERROR_STANDARD4: array[108..129] of PUtf8Char = ( 'DRIVE_LOCKED', 'BROKEN_PIPE', 'OPEN_FAILED', 'BUFFER_OVERFLOW', 'DISK_FULL', 'NO_MORE_SEARCH_HANDLES', 'INVALID_TARGET_HANDLE', '115', '116', 'INVALID_CATEGORY', 'INVALID_VERIFY_SWITCH', 'BAD_DRIVER_LEVEL', 'CALL_NOT_IMPLEMENTED', 'SEM_TIMEOUT', 'INSUFFICIENT_BUFFER', 'INVALID_NAME', 'INVALID_LEVEL', 'NO_VOLUME_LABEL', 'MOD_NOT_FOUND', 'PROC_NOT_FOUND', 'WAIT_NO_CHILDREN', 'CHILD_NOT_COMPLETE'); ERROR_STANDARD5: array[995..1013] of PUtf8Char = ( 'OPERATION_ABORTED', 'IO_INCOMPLETE', 'IO_PENDING', 'NOACCESS', 'SWAPERROR', '1000', 'STACK_OVERFLOW', 'INVALID_MESSAGE', 'CAN_NOT_COMPLETE', 'INVALID_FLAGS', 'UNRECOGNIZED_VOLUME', 'FILE_INVALID', 'FULLSCREEN_MODE', 'NO_TOKEN', 'BADDB', 'BADKEY', 'CANTOPEN', 'CANTREAD', 'CANTWRITE'); ERROR_STANDARD6: array[1051..1079] of PUtf8Char = ( 'DEPENDENT_SERVICES_RUNNING', 'INVALID_SERVICE_CONTROL', 'SERVICE_REQUEST_TIMEOUT', 'SERVICE_NO_THREAD', 'SERVICE_DATABASE_LOCKED', 'SERVICE_ALREADY_RUNNING', 'INVALID_SERVICE_ACCOUNT', 'SERVICE_DISABLED', 'CIRCULAR_DEPENDENCY', 'SERVICE_DOES_NOT_EXIST', 'SERVICE_CANNOT_ACCEPT_CTRL', 'SERVICE_NOT_ACTIVE', 'FAILED_SERVICE_CONTROLLER_CONNECT', 'EXCEPTION_IN_SERVICE', 'DATABASE_DOES_NOT_EXIST', 'SERVICE_SPECIFIC_ERROR', 'PROCESS_ABORTED', 'SERVICE_DEPENDENCY_FAIL', 'SERVICE_LOGON_FAILED', 'SERVICE_START_HANG', 'INVALID_SERVICE_LOCK', 'SERVICE_MARKED_FOR_DELETE', 'SERVICE_EXISTS', 'ALREADY_RUNNING_LKG', 'SERVICE_DEPENDENCY_DELETED', 'BOOT_ALREADY_ACCEPTED', 'SERVICE_NEVER_STARTED', 'DUPLICATE_SERVICE_NAME', 'DIFFERENT_SERVICE_ACCOUNT'); ERROR_STANDARD7: array[1200..1246] of PUtf8Char = ( 'BAD_DEVICE', 'CONNECTION_UNAVAIL', 'DEVICE_ALREADY_REMEMBERED', 'NO_NET_OR_BAD_PATH', 'BAD_PROVIDER', 'CANNOT_OPEN_PROFILE', 'BAD_PROFILE', 'NOT_CONTAINER', 'EXTENDED_ERROR', 'INVALID_GROUPNAME', 'INVALID_COMPUTERNAME', 'INVALID_EVENTNAME', 'INVALID_DOMAINNAME', 'INVALID_SERVICENAME', 'INVALID_NETNAME', 'INVALID_SHARENAME', 'INVALID_PASSWORDNAME', 'INVALID_MESSAGENAME', 'INVALID_MESSAGEDEST', 'SESSION_CREDENTIAL_CONFLICT', 'REMOTE_SESSION_LIMIT_EXCEEDED', 'DUP_DOMAINNAME', 'NO_NETWORK', 'CANCELLED', 'USER_MAPPED_FILE', 'CONNECTION_REFUSED', 'GRACEFUL_DISCONNECT', 'ADDRESS_ALREADY_ASSOCIATED', 'ADDRESS_NOT_ASSOCIATED', 'CONNECTION_INVALID', 'CONNECTION_ACTIVE', 'NETWORK_UNREACHABLE', 'HOST_UNREACHABLE', 'PROTOCOL_UNREACHABLE', 'PORT_UNREACHABLE', 'REQUEST_ABORTED', 'CONNECTION_ABORTED', 'RETRY', 'CONNECTION_COUNT_LIMIT', 'LOGIN_TIME_RESTRICTION', 'LOGIN_WKSTA_RESTRICTION', 'INCORRECT_ADDRESS', 'ALREADY_REGISTERED', 'SERVICE_NOT_FOUND', 'NOT_AUTHENTICATED', 'NOT_LOGGED_ON', 'CONTINUE'); function WinErrorConstant(Code: cardinal): PUtf8Char; begin if Code <= high(ERROR_STANDARD1) then result := ERROR_STANDARD1[Code] else if Code in [low(ERROR_STANDARD2)..high(ERROR_STANDARD2)] then result := ERROR_STANDARD2[Code] else if Code in [low(ERROR_STANDARD3)..high(ERROR_STANDARD3)] then result := ERROR_STANDARD3[Code] else if Code in [low(ERROR_STANDARD4)..high(ERROR_STANDARD4)] then result := ERROR_STANDARD4[Code] else if (Code >= low(ERROR_STANDARD5)) and (Code <= high(ERROR_STANDARD5)) then result := ERROR_STANDARD5[Code] else if (Code >= low(ERROR_STANDARD6)) and (Code <= high(ERROR_STANDARD6)) then result := ERROR_STANDARD6[Code] else if (Code >= low(ERROR_STANDARD7)) and (Code <= high(ERROR_STANDARD7)) then result := ERROR_STANDARD7[Code] else case Code of ERROR_ALREADY_EXISTS: result := 'ALREADY_EXISTS'; ERROR_MORE_DATA: result := 'MORE_DATA'; ERROR_NO_SYSTEM_RESOURCES: result := 'NO_SYSTEM_RESOURCES'; ERROR_WINHTTP_CANNOT_CONNECT: result := 'WINHTTP_CANNOT_CONNECT'; ERROR_WINHTTP_TIMEOUT: result := 'WINHTTP_TIMEOUT'; ERROR_WINHTTP_INVALID_SERVER_RESPONSE: result := 'WINHTTP_INVALID_SERVER_RESPONSE'; 10014: result := 'WSAEFAULT'; 10022: result := 'WSAEINVAL'; 10024: result := 'WSAEMFILE'; 10035: result := 'WSAEWOULDBLOCK'; 10038: result := 'WSAENOTSOCK'; 10053: result := 'WSAECONNABORTED'; 10054: result := 'WSAECONNRESET'; 10055: result := 'WSAENOBUFS'; 10060: result := 'WSAETIMEDOUT'; 10061: result := 'WSAECONNREFUSED'; 11003: result := 'WSATRY_AGAIN'; else result := nil; end; end; function WinErrorText(Code: cardinal; ModuleName: PChar): RawUtf8; var bak: integer; flags, len: PtrUInt; src: pointer; cod: PUtf8Char; tmp: array[0..511] of WideChar; begin bak := GetLastError; src := nil; flags := FORMAT_MESSAGE_FROM_SYSTEM; if ModuleName = nil then begin // system error codes cod := WinErrorConstant(Code); if cod <> nil then begin // we can return directly the standard system error code constant _fmt('ERROR_%s', [cod], result); exit; end; end else begin // module specific error codes src := pointer(GetModuleHandle(ModuleName)); if src <> nil then flags := FORMAT_MESSAGE_FROM_HMODULE; end; // first try if there is an English message version of this error code len := FormatMessageW(flags, src, Code, ENGLISH_LANGID, @tmp, SizeOf(tmp), nil); if len = 0 then // typically ERROR_RESOURCE_LANG_NOT_FOUND or ERROR_MUI_FILE_NOT_FOUND len := FormatMessageW(flags, src, Code, 0, @tmp, SizeOf(tmp), nil); if (len = 0) and (src <> nil) then begin // fallback to the system error message if this module as no such code SetLastError(bak); result := WinErrorText(Code, nil); exit; end; while (len > 0) and (ord(tmp[len - 1]) in [0..32, ord('.')]) do dec(len); // trim right Win32PWideCharToUtf8(@tmp, len, result); SetLastError(bak); end; function GetErrorText(error: integer): RawUtf8; begin result := WinErrorText(error, nil); end; procedure RaiseLastModuleError(ModuleName: PChar; ModuleException: ExceptClass); var code: integer; begin code := GetLastError; raise ModuleException.CreateFmt('%s error %x (%s)', [ModuleName, code, string(WinErrorText(code, ModuleName))]); end; procedure RaiseLastError(const Context: shortstring; RaisedException: ExceptClass); var code: integer; begin code := GetLastError; if RaisedException = nil then RaisedException := EOSException; raise RaisedException.CreateFmt('%s error %x (%s)', [Context, code, string(WinErrorText(code, nil))]) end; function PostMessage(hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): BOOL; begin result := Windows.PostMessage(hWnd, Msg, wParam, lParam); // call either A or W API end; function ExpandEnvVars(const aStr: string): string; // adapted from http://delphidabbler.com/articles?article=6 var size: integer; begin // Get required buffer size size := ExpandEnvironmentStrings(pointer(aStr), nil, 0); if size > 0 then begin // Read expanded string into result string SetString(result, nil, size - 1); ExpandEnvironmentStrings(pointer(aStr), pointer(result), size); end else result := aStr; // return the original file name end; function IsInitializedCriticalSection(var cs: TRTLCriticalSection): boolean; begin result := not IsZero(@cs, SizeOf(cs)); end; var // value is documented as stable after boot, so we get it at startup _QueryPerformanceFrequency: QWord; // from HyperV or if HPET disabled e.g. -> direct division _QueryPerformanceFrequencyPer10: boolean; procedure QueryPerformanceMicroSeconds(out Value: Int64); var v: Int64; // for proper alignment on some old Delphi revisions + Win32 begin QueryPerformanceCounter(v); if _QueryPerformanceFrequencyPer10 then Value := QWord(v) div 10 // faster div by a constant (especially on FPC_64) else Value := QWord((QWord(v) * 1000000) div _QueryPerformanceFrequency); end; var shlwapiDll: THandle; // lazy loading (only by TNetClientProtocolFile) PathCreateFromUrl: function(pszUrl, pszPath: PChar; var pcchPath: cardinal; dwFlags: cardinal): HRESULT; stdcall; function GetFileNameFromUrl(const Uri: string): TFileName; var len: DWORD; tmp: array[0..MAX_PATH] of char; begin result := ''; len := MAX_PATH; if DelayedProc(PathCreateFromUrl, shlwapiDll, 'shlwapi.dll', 'PathCreateFromUrl' + _AW) and (PathCreateFromUrl(pointer(Uri), @tmp, len, 0) = S_OK) then result := tmp; end; const faInvalidFile = faDirectory + faVolumeID{%H-} + faSysFile{%H-} + faHidden{%H-}; faDirectoryMask = faDirectory + faHidden{%H-}; function FileDateToDateTime(const FileDate: TFileAge): TDateTime; begin result := WindowsFileTimeToDateTime(FileDate); end; // some definitions missing on oldest Delphi const FILE_ATTRIBUTE_REPARSE_POINT = $0000400; function FindFirstFileExW(lpfilename: PWideChar; fInfoLevelId: FINDEX_INFO_LEVELS; lpFindFileData: pointer; fSearchOp: FINDEX_SEARCH_OPS; lpSearchFilter: pointer = nil; dwAdditionalFlags: cardinal = 0): THandle; stdcall; external kernel32; // an alternative to GetFileAttributesExW() with fallback to FindFirstFileEx API function GetFileAttributesRaw(fn: PWideChar; out Attr: WIN32_FILE_ATTRIBUTE_DATA): boolean; var h: THandle; fd: TWin32FindDataW; begin // this API is much faster than CreateFile/GetFileTime/GetFileSize/CloseHandle result := GetFileAttributesExW(fn, GetFileExInfoStandard, @Attr); if result or (GetLastError in [ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND, ERROR_INVALID_NAME, ERROR_INVALID_DRIVE, ERROR_NOT_READY, ERROR_INVALID_PARAMETER, ERROR_BAD_PATHNAME, ERROR_BAD_NETPATH, ERROR_BAD_NET_NAME]) then exit; // access denied, or locked file: fallback to slower but regular API h := FindFirstFileExW(fn, FindExInfoStandard, @fd, FindExSearchNameMatch); if not ValidHandle(h) then exit; windows.FindClose(h); Attr.dwFileAttributes := fd.dwFileAttributes; Attr.ftCreationTime := fd.ftCreationTime; Attr.ftLastAccessTime := fd.ftLastAccessTime; Attr.ftLastWriteTime := fd.ftLastWriteTime; Attr.nFileSizeHigh := fd.nFileSizeHigh; Attr.nFileSizeLow := fd.nFileSizeLow; result := true; end; function GetFileAttributesInternal(const FileName: TFileName; out Attr: WIN32_FILE_ATTRIBUTE_DATA; FollowLink: boolean = true): boolean; var fn: PWideChar; h: THandle; f: cardinal; lp: TByHandleFileInformation; tmp: TW32Temp; begin result := false; if FileName = '' then exit; fn := W32(FileName, tmp); result := GetFileAttributesRaw(fn, Attr); if result and FollowLink and (Attr.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT <> 0) then begin // we need to follow a symbolic link f := 0; if Attr.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY <> 0 then f := FILE_FLAG_BACKUP_SEMANTICS; // to access folder handle FillCharFast(Attr, SizeOf(Attr), 0); // enough for FileExists() // raw file access seems better than FileGetSymLinkTarget() in our case // and it will be consistent on both FPC and Delphi (including pre-Unicode) // - if we require file information, it is likely we would like to access it // - note that FPC and Delphi RTL seems overcomplicated and non-consistent // about symbolic links: mORMot will share this function everywhere h := CreateFileW(fn, GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, f, 0); if ValidHandle(h) then begin if GetFileInformationByHandle(h, lp) then begin Attr.dwFileAttributes := lp.dwFileAttributes; Attr.ftCreationTime := lp.ftCreationTime; Attr.ftLastAccessTime := lp.ftLastAccessTime; Attr.ftLastWriteTime := lp.ftLastWriteTime; Attr.nFileSizeHigh := lp.nFileSizeHigh; Attr.nFileSizeLow := lp.nFileSizeLow; end else result := false; CloseHandle(h); end else result := IsSharedViolation; end; end; function FileAgeToDateTime(const FileName: TFileName): TDateTime; var FA: WIN32_FILE_ATTRIBUTE_DATA; ST, LT: TSystemTime; begin if (FileName <> '') and GetFileAttributesInternal(FileName, FA) and FileTimeToSystemTime({%H-}FA.ftLastWriteTime, ST) and SystemTimeToTzSpecificLocalTime(nil, ST, LT) then result := SystemTimeToDateTime(LT) else result := 0; end; function FileAgeToUnixTimeUtc(const FileName: TFileName; AllowDir: boolean): TUnixTime; var FA: WIN32_FILE_ATTRIBUTE_DATA; begin if (FileName <> '') and GetFileAttributesInternal(FileName, FA) and (AllowDir or ({%H-}FA.dwFileAttributes and faDirectory = 0)) then result := FileTimeToUnixTime(FA.ftLastWriteTime) // no local time conversion else result := 0; end; function FileAgeToWindowsTime(const FileName: TFileName): integer; var FA: WIN32_FILE_ATTRIBUTE_DATA; ft: TFileTime; begin result := 0; if (FileName <> '') and GetFileAttributesInternal(FileName, FA) and ({%H-}FA.dwFileAttributes and faDirectory = 0) and FileTimeToLocalFileTime(FA.ftLastWriteTime, ft) and not FileTimeToDosDateTime(ft, LongRec(result).Hi, LongRec(result).Lo) then result := 0; end; function FileSetDateFromWindowsTime(const Dest: TFileName; WinTime: integer): boolean; begin result := FileSetDate(Dest, WinTime) = 0; // we already are on Windows end; function FileSetDateFromUnixUtc(const Dest: TFileName; Time: TUnixTime): boolean; var D: THandle; ft: TFileTime; begin result := false; if (Dest = '') or (Time = 0) then exit; D := FileOpen(Dest, fmOpenWrite); if not ValidHandle(D) then exit; UnixTimeToFileTime(Time, ft); result := SetFileTime(D, nil, nil, @ft); FileClose(D); end; function SearchRecToWindowsTime(const F: TSearchRec): integer; begin result := F.Time; // already in the expected legacy format end; function SearchRecToUnixTimeUtc(const F: TSearchRec): TUnixTime; begin // return the search record timestamp with no local time conversion result := FileTimeToUnixTime(F.FindData.ftLastWriteTime); end; function FileInfoByHandle(aFileHandle: THandle; FileId, FileSize: PInt64; LastWriteAccess, FileCreateDateTime: PUnixMSTime): boolean; var mtime, atime, ctime: Int64; lp: TByHandleFileInformation; begin result := GetFileInformationByHandle(aFileHandle, lp); if not result then exit; if FileId <> nil then begin PInt64Rec(FileId)^.lo := lp.nFileIndexLow; PInt64Rec(FileId)^.hi := lp.nFileIndexHigh; end; if FileSize <> nil then begin PInt64Rec(FileSize)^.lo := lp.nFileSizeLow; PInt64Rec(FileSize)^.hi := lp.nFileSizeHigh; end; if (LastWriteAccess = nil) and (FileCreateDateTime = nil) then exit; mtime := FileTimeToUnixMSTime(lp.ftLastWriteTime); if LastWriteAccess <> nil then LastWriteAccess^ := mtime; if FileCreateDateTime = nil then exit; atime := FileTimeToUnixMSTime(lp.ftLastAccessTime); ctime := FileTimeToUnixMSTime(lp.ftCreationTime); if mtime <> 0 then if (ctime = 0) or (ctime > mtime) then ctime := mtime; if atime <> 0 then if (ctime = 0) or (ctime > atime) then ctime := atime; FileCreateDateTime^ := ctime; end; function FileIsExecutable(const FileName: TFileName): boolean; var h: THandle; header: word; begin result := false; h := FileOpen(FileName, fmOpenReadShared); if not ValidHandle(h) then exit; result := (FileRead(h, header, 2) = 2) and (header = $5A4D); // DOS Magic Number FileClose(h); end; function GetModuleHandleExA(dwFlags: cardinal; lpModuleName: pointer; var phModule: HMODULE): BOOL; stdcall; external kernel32; const GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = $00000002; GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = $00000004; function GetExecutableName(aAddress: pointer): TFileName; var tmp: array[byte] of WideChar; hm: HMODULE; begin result := ''; FillcharFast(tmp, SizeOf(tmp), 0); hm := 0; if not GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT or GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, aAddress, hm) then exit; GetModuleFileNameW(hm, tmp, SizeOf(tmp)); result := string(SynUnicode(tmp)); end; function FileIsWritable(const FileName: TFileName): boolean; var FA: WIN32_FILE_ATTRIBUTE_DATA; begin result := (FileName <> '') and GetFileAttributesInternal(FileName, FA) and (FA.dwFileAttributes and faReadOnly = 0); end; function FileExists(const FileName: TFileName; FollowLink, CheckAsDir: boolean): boolean; var FA: WIN32_FILE_ATTRIBUTE_DATA; begin result := (FileName <> '') and GetFileAttributesInternal(FileName, FA, FollowLink) and ((FA.dwFileAttributes and faDirectory <> 0) = CheckAsDir); end; function DirectoryExists(const FileName: TFileName; FollowLink: boolean): boolean; var L: integer; begin L := length(FileName); if L = 0 then result := false else if (L = 1) and (FileName[1] = '.') then result := true else if FileName[L] <> '\' then result := FileExists(FileName, FollowLink, {checkasdir=}true) else result := FileExists(copy(FileName, 1, L - 1), FollowLink, true); end; function FileSize(const FileName: TFileName): Int64; var FA: WIN32_FILE_ATTRIBUTE_DATA; begin if (FileName <> '') and GetFileAttributesInternal(FileName, FA) and (FA.dwFileAttributes and faDirectory = 0) then result := Qword(FA.nFileSizeHigh) shl 32 + FA.nFileSizeLow else result := 0; end; function FileInfoByName(const FileName: TFileName; out FileSize: Int64; out FileTimestampUtc: TUnixMSTime): boolean; var FA: WIN32_FILE_ATTRIBUTE_DATA; begin result := (FileName <> '') and GetFileAttributesInternal(FileName, FA); if not result then exit; PInt64Rec(@FileSize)^.Lo := FA.nFileSizeLow; PInt64Rec(@FileSize)^.Hi := FA.nFileSizeHigh; FileTimestampUtc := FileTimeToUnixMSTime(FA.ftLastWriteTime) // no local time end; function GetFileSizeEx(hFile: THandle; var FileSize: Int64): BOOL; stdcall; external kernel32; function FileSize(F: THandle): Int64; begin if (PtrInt(F) <= 0) or not GetFileSizeEx(F, result) then result := 0; end; function FileSeek64(Handle: THandle; const Offset: Int64; Origin: cardinal): Int64; var r: TQWordRec; begin r.V := Offset; r.L := SetFilePointer(Handle, r.L, @r.H, Origin); if (r.Li = -1) and (GetLastError <> 0) then result := -1 else result := r.V; end; function DeleteFile(const aFileName: TFileName): boolean; var tmp: TW32Temp; begin if aFileName = '' then result := false else result := DeleteFileW(W32(aFileName, tmp)); end; function FileShare(aMode: integer): DWord; begin case (aMode and $f0) of fmShareRead: // = fmShareDenyWrite result := FILE_SHARE_READ; fmShareWrite: // = fmShareDenyRead result := FILE_SHARE_WRITE; fmShareReadWrite: // = fmShareDenyNone result := FILE_SHARE_READ or FILE_SHARE_WRITE; else result := 0; end; end; function FileCreate(const aFileName: TFileName; aMode, aRights: integer): THandle; var tmp: TW32Temp; begin // aRights parameter is just ignored on Windows if aFileName = '' then result := 0 else result := CreateFileW(W32(aFileName, tmp), GENERIC_READ or GENERIC_WRITE, FileShare(aMode), nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); end; const FILE_WRITE_ATTRIBUTES = $0100; // not defined on oldest Delphi // W32() will support length > MAX_PATH even if aFileName is UnicodeString function FileOpen(const aFileName: TFileName; aMode: integer): THandle; const ACCESS: array[0..2] of DWord = ( GENERIC_READ, // fmOpenRead = $0000 GENERIC_WRITE, // fmOpenWrite = $0001 GENERIC_READ or GENERIC_WRITE or FILE_WRITE_ATTRIBUTES); // fmOpenReadWrite var tmp: TW32Temp; begin if aFileName = '' then result := 0 else result := CreateFileW(W32(aFileName, tmp), ACCESS[aMode and 3], FileShare(aMode), nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); end; function FileSetDateFrom(const Dest: TFileName; SourceHandle: THandle): boolean; var FileTime: TFileTime; D: THandle; begin if (Dest = '') or not ValidHandle(SourceHandle) then result := false else begin D := FileOpen(Dest, fmOpenWrite); if ValidHandle(D) then begin result := GetFileTime(SourceHandle, nil, nil, @FileTime) and SetFileTime(D, nil, nil, @FileTime); FileClose(D); end else result := false; end; end; function FileSetDateFrom(const Dest, Source: TFileName): boolean; var S: THandle; begin result := false; if (Dest = '') or (Source = '') then exit; S := FileOpen(Source, fmOpenReadShared); if not ValidHandle(S) then exit; result := FileSetDateFrom(Dest, S); FileClose(S); end; procedure FileSetAttr(const FileName: TFileName; Attr: integer); var tmp: TW32Temp; begin if FileName <> '' then SetFileAttributesW(W32(FileName, tmp), Attr); end; procedure FileSetHidden(const FileName: TFileName; ReadOnly: boolean); const FLAGS: array[boolean] of integer = ( FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_HIDDEN or FILE_ATTRIBUTE_READONLY); begin FileSetAttr(FileName, FLAGS[ReadOnly]); end; procedure FileSetSticky(const FileName: TFileName); begin FileSetAttr(FileName, FILE_ATTRIBUTE_HIDDEN or FILE_ATTRIBUTE_SYSTEM); end; function RenameFile(const OldName, NewName: TFileName): boolean; var o, n: TW32Temp; begin if (OldName = '') or (NewName = '') then result := false else result := MoveFileW(W32(OldName, o), W32(NewName, n)); end; function FileSetTime(const FileName: TFileName; const Created, Accessed, Written: Int64): boolean; var tmp: TW32Temp; h: THandle; pct, pat, pwt: pointer; begin result := false; h := CreateFileW(W32(FileName, tmp), FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, nil, OPEN_ALWAYS, 0, 0); if ValidHandle(h) then try // some input code may not set all properties: use what we got if Created <> 0 then pct := @Created else if Written <> 0 then pct := @Written else if Accessed <> 0 then pct := @Accessed else exit; if Accessed <> 0 then pat := @Accessed else if Written <> 0 then pat := @Written else pat := @Created; if Written <> 0 then pwt := @Written else if Created <> 0 then pwt := @Created else pwt := @Accessed; result := SetFileTime(h, pct, pat, pwt); finally CloseHandle(h); end; end; function CopyFile(const Source, Target: TFileName; FailIfExists: boolean): boolean; var s, t: TW32Temp; begin if (Source = '') or (Target = '') then result := false else result := Windows.CopyFileW(W32(Source, s), W32(Target, t), FailIfExists); end; function ValidHandle(Handle: THandle): boolean; begin result := PtrInt(Handle) > 0; end; function FileOpenSequentialRead(const FileName: TFileName): integer; var tmp: TW32Temp; begin result := CreateFileW(W32(FileName, tmp), GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0); end; function FileIsReadable(const aFileName: TFileName): boolean; var tmp: TW32Temp; h: THandle; begin h := CreateFileW(W32(aFileName, tmp), GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0); result := ValidHandle(h); if result then CloseHandle(h); end; threadvar // mandatory: GetTickCount seems per-thread on XP :( LastTickXP: TQWordRec; function GetTickCount64ForXP: Int64; stdcall; var t32: cardinal; p: PQWordRec; begin // warning: GetSystemTimeAsFileTime() is fast, but not monotonic! t32 := Windows.GetTickCount; // we only have the 32-bit counter on XP p := @LastTickXP; inc(p^.H, ord(t32 < p^.L)); // wrap-up overflow after 49 days p^.L := t32; result := p^.V; end; // warning: FPC's GetTickCount64 doesn't handle 49 days wrap on XP :( procedure InitializeSRWLockForXP(var P: TOSLightMutex); stdcall; begin TLightLock(P).Init; // TLightLock is good enough on XP end; procedure AcquireSRWLockExclusiveForXP(var P: TOSLightMutex); stdcall; begin TLightLock(P).Lock; end; procedure ReleaseSRWLockExclusiveForXP(var P: TOSLightMutex); stdcall; begin TLightLock(P).UnLock; end; function GetUptimeSec: cardinal; begin result := GetTickCount64 div MSecsPerSec; // good enough end; procedure SleepHiRes(ms: cardinal); begin if ms <> 0 then Windows.Sleep(ms) // follow the HW timer: typically up to 16ms on Windows else SwitchToThread; end; { TOSLightLock } procedure TOSLightLock.Init; begin fMutex := nil; InitializeSRWLock(fMutex); // fallback to TLightLock on XP end; procedure TOSLightLock.Done; begin // nothing needed end; procedure TOSLightLock.Lock; begin AcquireSRWLockExclusive(fMutex); end; procedure TOSLightLock.UnLock; begin ReleaseSRWLockExclusive(fMutex); end; { TSynEvent } constructor TSynEvent.Create; begin fHandle := pointer(CreateEvent(nil, false, false, nil)); end; destructor TSynEvent.Destroy; begin CloseHandle(THandle(fHandle)); inherited Destroy; end; procedure TSynEvent.ResetEvent; begin Windows.ResetEvent(THandle(fHandle)); end; procedure TSynEvent.SetEvent; begin Windows.SetEvent(THandle(fHandle)); end; procedure TSynEvent.WaitFor(TimeoutMS: integer); begin WaitForSingleObject(THandle(fHandle), TimeoutMS); end; procedure TSynEvent.WaitForEver; begin WaitForSingleObject(THandle(fHandle), INFINITE); end; {$ifdef FPC} {$define NOSETTHREADNAME} // only tested and supported on Delphi {$endif FPC} const // see http://msdn.microsoft.com/en-us/library/xcb2z8hs cSetThreadNameException = $406D1388; {$ifdef NOSETTHREADNAME} procedure RawSetThreadName(ThreadID: TThreadID; const Name: RawUtf8); begin end; {$else} procedure RawSetThreadName(ThreadID: TThreadID; const Name: RawUtf8); var s: AnsiString; {$ifndef ISDELPHIXE2} info: record FType: LongWord; // must be 0x1000 FName: PAnsiChar; // pointer to name (in user address space) FThreadID: LongWord; // thread ID (-1 indicates caller thread) FFlags: LongWord; // reserved for future use, must be zero end; {$endif ISDELPHIXE2} begin if not IsDebuggerPresent then exit; s := AnsiString(Name); {$ifdef ISDELPHIXE2} TThread.NameThreadForDebugging(s, ThreadID); // use {$else} info.FType := $1000; info.FName := pointer(s); info.FThreadID := ThreadID; info.FFlags := 0; try RaiseException( cSetThreadNameException, 0, SizeOf(info) div SizeOf(LongWord), @info); except {ignore} end; {$endif ISDELPHIXE2} end; {$endif NOSETTHREADNAME} function RawKillThread(Thread: TThread): boolean; begin result := (Thread <> nil) and Windows.TerminateThread(Thread.Handle, 777); end; procedure ResetCpuSet(out CpuSet: TCpuSet); begin CpuSet := 0; end; function SetThreadMaskAffinity(Thread: TThread; const Mask: TCpuSet): boolean; begin result := (Thread <> nil) and (Windows.SetThreadAffinityMask(Thread.Handle, Mask) <> 0); end; function GetProcessAffinityMask(hProcess: THandle; var lpProcessAffinityMask, lpSystemAffinityMask: TCpuSet): BOOL; stdcall; external kernel32; // redefined for Delphi 7 compatibility function GetMaskAffinity(out CpuSet: TCpuSet): boolean; var process, thread: TCpuSet; begin result := GetProcessAffinityMask(GetCurrentProcess, process, thread); if result then CpuSet := process; end; type // avoid linking of ComObj.pas just for EOleSysError EOleSysError = class(Exception) public ErrorCode: cardinal; end; {$ifndef NOEXCEPTIONINTERCEPT} const // https://docs.microsoft.com/en-us/archive/blogs/yizhang/interpreting-hresults-returned-from-netclr-0x8013xxxx // see also https://referencesource.microsoft.com/#mscorlib/system/__hresults.cs DOTNET_EXCEPTIONNAME: array[0..91] of PUtf8Char = ( 'Access', // $8013151A 'AmbiguousMatch', // $8000211D 'appdomainUnloaded', // $80131015 'Application', // $80131600 'Argument', // $80070057 'ArgumentNull', // $80004003 'ArgumentOutOfRange', // $80131502 'Arithmetic', // $80070216 'ArrayTypeMismatch', // $80131503 'BadImageFormat', // $8007000B 'CannotUnloadappdomain', // $80131015 'ContextMarshal', // $80090020 'Cryptographic', // $80004001 'CryptographicUnexpectedOperation', // $80131431 'CustomAttributeFormat', // $80131537 'DirectoryNotFound', // $80070003 'DirectoryNotFound', // $80030003 'DivideByZero', // $80020012 'DllNotFound', // $80131524 'DuplicateWaitObject', // $80131529 'EndOfStream', // $00801338 'EntryPointNotFound', // $80131522 '', // $80131500 - name is plain Exception 'ExecutionEngine', // $80131506 'External', // $80004005 'FieldAccess', // $80131507 'FileLoad', // $80131621 'FileLoad', // $80131018 'FileNotFound', // $80070002 'Format', // $80131537 'IndexOutOfRange', // $80131508 'InvalidCast', // $80004002 'InvalidComObject', // $80131527 'InvalidFilterCriteria', // $80131601 'InvalidOleVariantType', // $80131531 'InvalidOperation', // $80131509 'InvalidProgram', // $8013153A 'IO', // $80131620 'IsolatedStorage', // $80131450 'MarshalDirective', // $80131535 'MethodAccess', // $80131510 'MissingField', // $80131511 'MissingManifestResource', // $80131532 'MissingMember', // $80131512 'MissingMethod', // $80131513 'MulticastNotSupported', // $80131514 'NotFiniteNumber', // $80131528 'NotImplemented', // $80004001 'NotSupported', // $80131515 'NullReference', // $80004003 'OutOfMemory', // $8007000E 'Overflow', // $80131516 'PlatformNotSupported', // $80131539 'Policy', // $80131416 'Rank', // $80131517 'ReflectionTypeLoad', // $80131602 'Remoting', // $8013150B 'RemotingTimeout', // $8013150B 'SafeArrayTypeMismatch', // $80131533 'SafeArrayRankMismatch', // $80131538 'Security', // $8013150A 'SEH', // $80004005 'Serialization', // $8013150C 'Server', // $8013150E 'StackOverflow', // $800703E9 'SUDSGenerator', // $80131500 'SUDSParser', // $80131500 'SynchronizationLock', // $80131518 'System', // $80131501 'Target', // $80131603 'TargetInvocation', // $80131604 'TargetParameterCount', // $80138002 'ThreadAbort', // $80131530 'ThreadInterrupted', // $80131519 'ThreadState', // $80131520 'ThreadStop', // $80131521 'TypeInitialization', // $80131534 'TypeLoad', // $80131522 'TypeUnloaded', // $80131013 'UnauthorizedAccess', // $80070005 'InClassConstructor', // $80131543 'KeyNotFound', // $80131577 'InsufficientStack', // $80131578 'InsufficientMemory', // $8013153D 'Verification', // $8013150D 'HostProtection', // $80131640 'MinGrantFailed', // $80131417 'Crypto', // $80131430 'CryptoUnexOper', // $80131431 'Overflow', // $8002000a 'InvalidName', // $80131047 'TypeMismatch'); // $80028ca0 DOTNET_EXCEPTIONHRESULT: array[0..91] of cardinal = ( $8013151A, $8000211D, $80131015, $80131600, $80070057, $80004003, $80131502, $80070216, $80131503, $8007000B, $80131015, $80090020, $80004001, $80131431, $80131537, $80070003, $80030003, $80020012, $80131524, $80131529, $00801338, $80131522, $80131500, $80131506, $80004005, $80131507, $80131621, $80131018, $80070002, $80131537, $80131508, $80004002, $80131527, $80131601, $80131531, $80131509, $8013153A, $80131620, $80131450, $80131535, $80131510, $80131511, $80131532, $80131512, $80131513, $80131514, $80131528, $80004001, $80131515, $80004003, $8007000E, $80131516, $80131539, $80131416, $80131517, $80131602, $8013150B, $8013150B, $80131533, $80131538, $8013150A, $80004005, $8013150C, $8013150E, $800703E9, $80131500, $80131500, $80131518, $80131501, $80131603, $80131604, $80138002, $80131530, $80131519, $80131520, $80131521, $80131534, $80131522, $80131013, $80070005, $80131543, $80131577, $80131578, $8013153D, $8013150D, $80131640, $80131417, $80131430, $80131431, $8002000a, $80131047, $80028ca0); function ExceptionInheritsFrom(E: TClass; const Name: ShortString): boolean; begin result := true; while (E <> nil) and (E <> Exception) do if PropNameEquals(PPointer(PtrInt(E) + vmtClassName)^, @Name) then exit else E := GetClassParent(E); result := false; end; function TSynLogExceptionContext.AdditionalInfo( out ExceptionNames: TPUtf8CharDynArray): cardinal; var i: PtrInt; begin if ExceptionInheritsFrom(EClass, 'EOleSysError') then begin result := EOleSysError(EInstance).ErrorCode; if result > $80000000 then for i := 0 to high(DOTNET_EXCEPTIONHRESULT) do // manual loop: the same error code can appear several times if DOTNET_EXCEPTIONHRESULT[i] = result then PtrArrayAdd(ExceptionNames, DOTNET_EXCEPTIONNAME[i]); end else result := 0; end; var _RawLogException: TOnRawLogException; {$ifdef FPC} {$ifdef WIN64} {$define WITH_VECTOREXCEPT} // use AddVectoredExceptionHandler Win64 API {$else} {$ifdef FPC_USE_WIN32_SEH} {$define WITH_VECTOREXCEPT} // new since FPC 3.2 {$else} // Win32, Linux: intercept via the RaiseProc global variable {$define WITH_RAISEPROC} // RaiseProc is set in main mormot.core.os.pas {$endif FPC_USE_WIN32_SEH} {$endif WIN64} {$else} {$ifdef CPU64} {$define WITH_VECTOREXCEPT} {$else} {$define WITH_RTLUNWINDPROC} // use x86_64 asm -> Win32 only {$endif CPU64} {$endif FPC} {$ifndef WITH_RAISEPROC} type PExceptionRecord = ^TExceptionRecord; TExceptionRecord = record ExceptionCode: DWord; ExceptionFlags: DWord; OuterException: PExceptionRecord; ExceptionAddress: PtrUInt; NumberParameters: integer; case {IsOsException:} boolean of true: (ExceptionInformation: array[0..14] of PtrUInt); false: (ExceptAddr: PtrUInt; ExceptObject: Exception); end; GetExceptionClass = function(const P: TExceptionRecord): ExceptClass; const {$ifdef FPC} cRtlException = $E0465043; // $E0 F P C {$else} cRtlException = $0EEDFADE; // Delphi exception {$endif FPC} procedure LogExcept(stack: PPtrUInt; const Exc: TExceptionRecord); var ctxt: TSynLogExceptionContext; backuplasterror: DWord; backuphandler: TOnRawLogException; begin if Exc.ExceptionCode = cSetThreadNameException then exit; backuplasterror := GetLastError; backuphandler := _RawLogException; if Assigned(backuphandler) then // paranoid check (tested before calling) try _RawLogException := nil; // disable nested exception ctxt.ECode := Exc.ExceptionCode; if (Exc.ExceptionCode = cRtlException) and (Exc.ExceptObject <> nil) then begin if Exc.ExceptObject.InheritsFrom(Exception) then ctxt.EClass := PPointer(Exc.ExceptObject)^ else ctxt.EClass := EExternalException; ctxt.EInstance := Exc.ExceptObject; ctxt.ELevel := sllException; ctxt.EAddr := Exc.ExceptAddr; end else begin if Assigned(ExceptClsProc) then ctxt.EClass := GetExceptionClass(ExceptClsProc)(Exc) else ctxt.EClass := EExternal; ctxt.EInstance := nil; ctxt.ELevel := sllExceptionOS; ctxt.EAddr := Exc.ExceptionAddress; end; ctxt.EStack := pointer(stack); ctxt.EStackCount := 0; ctxt.ETimestamp := UnixTimeUtc; // fast API call backuphandler(ctxt); except { ignore any nested exception } end; _RawLogException := backuphandler; SetLastError(backuplasterror); // code above could have changed this end; {$ifdef WITH_VECTOREXCEPT} type PExceptionInfo = ^TExceptionInfo; TExceptionInfo = packed record ExceptionRecord: PExceptionRecord; ContextRecord: pointer; end; var AddVectoredExceptionHandler: function(FirstHandler: cardinal; VectoredHandler: pointer): PtrInt; stdcall; function SynLogVectoredHandler(ExceptionInfo: PExceptionInfo): PtrInt; stdcall; const EXCEPTION_CONTINUE_SEARCH = 0; begin if Assigned(_RawLogException) then LogExcept(nil, ExceptionInfo^.ExceptionRecord^); result := EXCEPTION_CONTINUE_SEARCH; end; {$endif WITH_VECTOREXCEPT} {$ifdef WITH_RTLUNWINDPROC} var OldUnWindProc: pointer; procedure SynRtlUnwind(TargetFrame, TargetIp: pointer; ExceptionRecord: PExceptionRecord; ReturnValue: Pointer); stdcall; asm cmp dword ptr _RawLogException, 0 jz @old pushad mov eax, TargetFrame mov edx, ExceptionRecord call LogExcept popad @old: pop ebp // hidden push ebp at asm level jmp OldUnWindProc end; {$endif WITH_RTLUNWINDPROC} {$endif WITH_RAISEPROC} {$endif NOEXCEPTIONINTERCEPT} { TMemoryMap } function TMemoryMap.DoMap(aCustomOffset: Int64): boolean; begin with PInt64Rec(@fFileSize)^ do fMap := CreateFileMapping(fFile, nil, PAGE_READONLY, Hi, Lo, nil); if fMap = 0 then RaiseLastError('TMemoryMap.Map: CreateFileMapping'); with PInt64Rec(@aCustomOffset)^ do fBuf := MapViewOfFile(fMap, FILE_MAP_READ, Hi, Lo, fBufSize); if fBuf = nil then begin // Windows failed to find a contiguous VA space -> fall back on direct read CloseHandle(fMap); fMap := 0; end; result := fMap <> 0; end; procedure TMemoryMap.DoUnMap; begin if fMap <> 0 then begin UnmapViewOfFile(fBuf); CloseHandle(fMap); fMap := 0; end; end; const STUB_SIZE = 65536; // 16*4 KB (4 KB = memory granularity) type TProcessMemoryCounters = record cb: DWord; PageFaultCount: DWord; PeakWorkingSetSize: PtrUInt; WorkingSetSize: PtrUInt; QuotaPeakPagedPoolUsage: PtrUInt; QuotaPagedPoolUsage: PtrUInt; QuotaPeakNonPagedPoolUsage: PtrUInt; QuotaNonPagedPoolUsage: PtrUInt; PagefileUsage: PtrUInt; PeakPagefileUsage: PtrUInt; end; const PROCESS_QUERY_LIMITED_INFORMATION = $1000; var // PROCESS_QUERY_INFORMATION (XP) / PROCESS_QUERY_LIMITED_INFORMATION (Vista+) OpenProcessAccess: DWord; // late-binding of Windows API entries not available on XP GetSystemTimes: function( var lpIdleTime, lpKernelTime, lpUserTime: TFileTime): BOOL; stdcall; GetProcessTimes: function(hProcess: THandle; var lpCreationTime, lpExitTime, lpKernelTime, lpUserTime: TFileTime): BOOL; stdcall; // Vista+/WS2008+ (use GetModuleFileNameEx on XP) QueryFullProcessImageNameW: function(hProcess: THandle; dwFlags: DWord; lpExeName: PWideChar; lpdwSize: PDWord): BOOL; stdcall; // PSAPI API late-binding via DelayedProc() GetProcessMemoryInfo: function(Process: THandle; var ppsmemCounters: TProcessMemoryCounters; cb: DWord): BOOL; stdcall; EnumProcesses: function(lpidProcess: PDWord; cb: DWord; var cbNeeded: DWord): BOOL; stdcall; GetModuleFileNameExW: function(hProcess: THandle; hModule: HMODULE; lpBaseName: PWideChar; nSize: DWord): DWord; stdcall; function DelayedProc(var api; var lib: THandle; libname: PChar; procname: PAnsiChar): boolean; var proc: pointer; begin if pointer(api) = nil then begin proc := nil; GlobalLock; // avoid race condition if lib = 0 then lib := Windows.LoadLibrary(libname); if lib >= 32 then proc := Windows.GetProcAddress(lib, procname) else lib := 1; // try to load the library once if proc = nil then proc := pointer(1); // mark non available pointer(api) := proc; // set it last GlobalUnLock; end; result := pointer(api) <> pointer(1); end; function GetNextItem(var P: PAnsiChar): RawUtf8; var S: PAnsiChar; begin result := ''; while P^ <= ' ' do if P^ = #0 then exit else inc(P); S := P; repeat inc(P); until P^ <= ' '; FastSetString(result, S, P - S); end; const PAGE_GUARD = $0100; PAGE_VALID = $00e6; // PAGE_READONLY or PAGE_READWRITE or PAGE_EXECUTE or // PAGE_EXECUTE_READ or PAGE_EXECUTE_READWRITE or PAGE_EXECUTE_WRITECOPY var LastMemInfo: TMemoryBasicInformation; // simple cache function SeemsRealPointer(p: pointer): boolean; var meminfo: TMemoryBasicInformation; begin result := false; if PtrUInt(p) <= 65535 then exit; // first 64KB is not a valid pointer by definition if (LastMemInfo.State <> 0) and (PtrUInt(p) - PtrUInt(LastMemInfo.BaseAddress) <= PtrUInt(LastMemInfo.RegionSize)) then result := true // reuse last memory region information if we can else begin // VirtualQuery API is slow but better than raising an exception // see https://stackoverflow.com/a/37547837/458259 FillCharFast(meminfo, SizeOf(meminfo), 0); result := (VirtualQuery(p, meminfo, SizeOf(meminfo)) = SizeOf(meminfo)) and (meminfo.RegionSize >= SizeOf(pointer)) and (meminfo.State = MEM_COMMIT) and (meminfo.Protect and PAGE_VALID <> 0) and (meminfo.Protect and PAGE_GUARD = 0); if result then LastMemInfo := meminfo; end; end; var PsapiDll: THandle; function EnumAllProcesses: TCardinalDynArray; var n, count: cardinal; begin result := nil; if not DelayedProc(EnumProcesses, PsapiDll, 'Psapi.dll', 'EnumProcesses') then exit; count := 0; n := 2048; // retrieve 2KB of IDs, i.e. 512 processes, by default repeat SetLength(result, n); if EnumProcesses(pointer(result), n * 4, count) then count := count shr 2 // from bytes to count else count := 0; // error if count < n then break; // count=n if buffer was too small inc(n, 1024); until n > 8192; if count = 0 then result := nil // on error else DynArrayFakeLength(result, count); // no realloc end; function EnumProcessName(PID: cardinal): RawUtf8; var h: THandle; len: DWord; name: array[0..4095] of WideChar; begin result := ''; if PID = 0 then exit; h := OpenProcess(OpenProcessAccess, false, PID); if h <> 0 then try if Assigned(QueryFullProcessImageNameW) then begin len := high(name); if QueryFullProcessImageNameW(h, 0, name, @len) then Win32PWideCharToUtf8(name, len, result); end else if DelayedProc(GetModuleFileNameExW, PsapiDll, 'Psapi.dll', 'GetModuleFileNameExW') and (GetModuleFileNameExW(h, 0, name, high(name)) <> 0) then Win32PWideCharToUtf8(name, result); finally CloseHandle(h); end; end; // some definitions missing on Delphi and/or FPC type TProcessEntry32 = record dwSize: DWORD; cntUsage: DWORD; th32ProcessID: DWORD; // this process th32DefaultHeapID: PtrUInt; th32ModuleID:DWORD; // associated exe cntThreads: DWORD; th32ParentProcessID: DWORD; // this process's parent process pcPriClassBase: integer; // Base priority of process's threads dwFlags: DWORD; szExeFile: array [0..MAX_PATH - 1] of WideChar; // Path end; TThreadEntry32 = record dwSize: DWord; cntUsage: DWord; th32ThreadID: DWord; // this thread th32OwnerProcessID: DWord; // Process this thread is associated with tpBasePri: integer; tpDeltaPri: integer; dwFlags: DWord; end; const TH32CS_SNAPPROCESS = $00000002; TH32CS_SNAPTHREAD = $00000004; function AttachConsole(pid: cardinal): BOOL; stdcall; external kernel32; function GetConsoleWindow: HWND; stdcall; external kernel32; function CreateToolhelp32Snapshot(dwFlags, th32ProcessID: DWord): THandle; stdcall; external kernel32; function Process32FirstW(hSnapshot: THandle; var lppe: TProcessEntry32): BOOL; stdcall; external kernel32; function Process32NextW(hSnapshot: THandle; var lppe: TProcessEntry32): BOOL; stdcall; external kernel32; function Thread32First(hSnapshot: THandle; var lpte: TThreadEntry32): BOOL; stdcall; external kernel32; function Thread32Next(hSnapshot: THandle; var lpte: TThreadEntry32): BOOL; stdcall; external kernel32; function PostThreadMessage(idThread: DWord; Msg: UINT; wParam: WPARAM; lParam: LPARAM): BOOL; stdcall; external user32 name 'PostThreadMessageW'; function RawProcessInfo(pid: cardinal; var e: TProcessEntry32): boolean; var snap: THandle; begin result := false; if integer(pid) <= 0 then pid := GetCurrentProcessId; snap := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if snap <= 0 then exit; FillCharFast(e, SizeOf(e), 0); e.dwSize := SizeOf(e); result := true; if Process32FirstW(snap, e) then // loop over all processes of the system repeat if e.th32ProcessID = pid then exit; until not Process32NextW(snap, e); CloseHandle(snap); result := false; end; function GetParentProcess(PID: cardinal): cardinal; var e: TProcessEntry32; begin if RawProcessInfo(PID, e) then result := e.th32ParentProcessID else result := 0; end; function RetrieveSystemTimes(out IdleTime, KernelTime, UserTime: Int64): boolean; var ftidl, ftkrn, ftusr: TFileTime; begin result := Assigned(GetSystemTimes) and GetSystemTimes(ftidl, ftkrn, ftusr); if not result then exit; FileTimeToInt64(ftidl, IdleTime); FileTimeToInt64(ftkrn, KernelTime); FileTimeToInt64(ftusr, UserTime); end; function RetrieveLoadAvg: RawUtf8; begin result := ''; // call RetrieveSystemTimes() instead end; function DelayedGetProcessMemoryInfo: boolean; begin result:= DelayedProc(GetProcessMemoryInfo, PsapiDll, 'Psapi.dll', 'GetProcessMemoryInfo'); end; function RetrieveProcessInfo(PID: cardinal; out KernelTime, UserTime: Int64; out WorkKB, VirtualKB: cardinal): boolean; var h: THandle; ftkrn, ftusr, ftp, fte: TFileTime; mem: TProcessMemoryCounters; begin result := false; if (not Assigned(GetProcessTimes)) or (not DelayedGetProcessMemoryInfo) then exit; h := OpenProcess(OpenProcessAccess, false, PID); if h = 0 then exit; try if GetProcessTimes(h, ftp, fte, ftkrn, ftusr) then begin FileTimeToInt64(ftkrn, KernelTime); FileTimeToInt64(ftusr, UserTime); FillCharFast(mem, SizeOf(mem), 0); mem.cb := SizeOf(mem); if GetProcessMemoryInfo(h, mem, SizeOf(mem)) then begin WorkKB := mem.WorkingSetSize shr 10; VirtualKB := mem.PagefileUsage shr 10; end; result := true; end; finally CloseHandle(h); end; end; function CoCreateGuid(out guid: THash128Rec): HRESULT; stdcall; external 'ole32.dll'; procedure XorOSEntropy(var e: THash512Rec); var h: THash128Rec; ft: packed record krn, usr, p, e: TFileTime; end; mem: TProcessMemoryCounters; begin QueryPerformanceCounter(h.Lo); // e.h3 xored with raw timestamps e.i[6] := e.i[6] xor h.Lo; if Assigned(GetProcessTimes) then GetProcessTimes(GetCurrentProcess, ft.p, ft.e, ft.krn, ft.usr); DefaultHasher128(@e.h0, @ft, SizeOf(ft)); if Assigned(GetProcessMemoryInfo) then // may have been delayed begin mem.cb := SizeOf(mem); GetProcessMemoryInfo(GetCurrentProcess, mem, SizeOf(mem)); DefaultHasher128(@e.h1, @mem, SizeOf(mem)); end; if Assigned(GetSystemTimes) then GetSystemTimes(ft.usr, ft.p, ft.e); DefaultHasher128(@e.h2, @ft, SizeOf(ft)); CoCreateGuid(h); // very fast on Windows - used to obfuscate system info e.i[0] := e.i[0] xor h.Lo; e.i[1] := e.i[1] xor h.Hi; CoCreateGuid(h); e.i[2] := e.i[2] xor h.Lo; e.i[3] := e.i[3] xor h.Hi; CoCreateGuid(h); e.i[4] := e.i[4] xor h.Lo; e.i[5] := e.i[5] xor h.Hi; CoCreateGuid(h); e.i[6] := e.i[6] xor h.Lo; e.i[7] := e.i[7] xor h.Hi; QueryPerformanceCounter(h.Lo); // is likely to have changed in-between e.i[7] := e.i[7] xor h.Lo; // e.h3 xored with raw timestamps end; function FillSystemRandom(Buffer: PByteArray; Len: integer; AllowBlocking: boolean): boolean; var prov: HCRYPTPROV; begin result := false; if Len <= 0 then exit; // warning: on some Windows versions, this could take up to 30 ms! if CryptoApi.Available then if CryptoApi.AcquireContextA(prov, nil, nil, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) then begin result := CryptoApi.GenRandom(prov, Len, Buffer); CryptoApi.ReleaseContext(prov, 0); end; if not result then // OS API call failed -> fallback to our Lecuyer's gsl_rng_taus2 generator RandomBytes(pointer(Buffer), Len); end; function TProcessInfo.Init: boolean; begin FillCharFast(self, SizeOf(self), 0); // no monitoring API available under oldest Windows result := Assigned(GetSystemTimes) and Assigned(GetProcessTimes) and DelayedGetProcessMemoryInfo; end; function TProcessInfo.Start: boolean; var ftidl, ftkrn, ftusr: TFileTime; sidl, skrn, susr: Int64; begin result := Assigned(GetSystemTimes) and GetSystemTimes(ftidl, ftkrn, ftusr); if not result then exit; FileTimeToInt64(ftidl, sidl); FileTimeToInt64(ftkrn, skrn); FileTimeToInt64(ftusr, susr); fDiffIdle := sidl - fSysPrevIdle; fDiffKernel := skrn - fSysPrevKernel; fDiffUser := susr - fSysPrevUser; fDiffTotal := fDiffKernel + fDiffUser; // kernel time also includes idle time dec(fDiffKernel, fDiffIdle); fSysPrevIdle := sidl; fSysPrevKernel := skrn; fSysPrevUser := susr; end; function TProcessInfo.PerProcess(PID: cardinal; Now: PDateTime; out Data: TSystemUseData; var PrevKernel, PrevUser: Int64): boolean; var h: THandle; ftkrn, ftusr, ftp, fte: TFileTime; pkrn, pusr: Int64; mem: TProcessMemoryCounters; begin result := false; FillCharFast(Data, SizeOf(Data), 0); h := OpenProcess(OpenProcessAccess, false, PID); if h <> 0 then try if GetProcessTimes(h, ftp, fte, ftkrn, ftusr) then begin if Now <> nil then Data.Timestamp := Now^; FileTimeToInt64(ftkrn, pkrn); FileTimeToInt64(ftusr, pusr); if (PrevKernel <> 0) and (fDiffTotal > 0) then begin Data.Kernel := ((pkrn - PrevKernel) * 100) / fDiffTotal; Data.User := ((pusr - PrevUser) * 100) / fDiffTotal; end; PrevKernel := pkrn; PrevUser := pusr; FillCharFast(mem, SizeOf(mem), 0); mem.cb := SizeOf(mem); if GetProcessMemoryInfo(h, mem, SizeOf(mem)) then begin Data.WorkKB := mem.WorkingSetSize shr 10; Data.VirtualKB := mem.PagefileUsage shr 10; end; result := true; end; finally CloseHandle(h); end; end; function TProcessInfo.PerSystem(out Idle, Kernel, User: single): boolean; begin if fDiffTotal <= 0 then begin Idle := 0; Kernel := 0; User := 0; result := false; end else begin Kernel := {%H-}SimpleRoundTo2Digits((fDiffKernel * 100) / fDiffTotal); User := {%H-}SimpleRoundTo2Digits((fDiffUser * 100) / fDiffTotal); Idle := 100 - Kernel - User; // ensure sum is always 100% result := true; end; end; {$ifndef UNICODE} // 64-bit aware Windows API missing on FPC and oldest Delphi type TMemoryStatusEx = record dwLength: DWord; dwMemoryLoad: DWord; ullTotalPhys: QWord; ullAvailPhys: QWord; ullTotalPageFile: QWord; ullAvailPageFile: QWord; ullTotalVirtual: QWord; ullAvailVirtual: QWord; ullAvailExtendedVirtual: QWord; end; // information about the system's current usage of both physical and virtual memory function GlobalMemoryStatusEx(var lpBuffer: TMemoryStatusEx): BOOL; stdcall; external kernel32; {$endif UNICODE} function GetMemoryInfo(out info: TMemoryInfo; withalloc: boolean): boolean; {$ifdef WITH_FASTMM4STATS} var Heap: TMemoryManagerState; sb: PtrInt; {$endif WITH_FASTMM4STATS} var global: TMemoryStatusEx; mem: TProcessMemoryCounters; begin FillCharFast(global, SizeOf(global), 0); global.dwLength := SizeOf(global); result := GlobalMemoryStatusEx(global); info.percent := global.dwMemoryLoad; info.memtotal := global.ullTotalPhys; info.memfree := global.ullAvailPhys; info.filetotal := global.ullTotalPageFile; info.filefree := global.ullAvailPageFile; info.vmtotal := global.ullTotalVirtual; info.vmfree := global.ullAvailVirtual; info.allocreserved := 0; info.allocused := 0; if not withalloc then exit; {$ifdef WITH_FASTMM4STATS} // override OS information by actual FastMM4 GetMemoryManagerState(Heap); // direct raw FastMM4 access info.allocused := Heap.TotalAllocatedMediumBlockSize + Heap.TotalAllocatedLargeBlockSize; info.allocreserved := Heap.ReservedMediumBlockAddressSpace + Heap.ReservedLargeBlockAddressSpace; for sb := 0 to high(Heap.SmallBlockTypeStates) do with Heap.SmallBlockTypeStates[sb] do begin inc(info.allocused, UseableBlockSize * AllocatedBlockCount); inc(info.allocreserved, ReservedAddressSpace); end; {$else} if not DelayedGetProcessMemoryInfo then exit; FillcharFast(mem, SizeOf(mem), 0); mem.cb := SizeOf(mem); GetProcessMemoryInfo(GetCurrentProcess, mem, SizeOf(mem)); info.allocreserved := mem.PeakWorkingSetSize; info.allocused := mem.WorkingSetSize; {$endif WITH_FASTMM4STATS} end; function GetDiskFreeSpaceExW(lpDirectoryName: PWideChar; var lpFreeBytesAvailableToCaller, lpTotalNumberOfBytes, lpTotalNumberOfFreeBytes: QWord): LongBool; stdcall; external kernel32; { // DeviceIoControl(IOCTL_DISK_GET_PARTITION_INFO) requires root -> not used function DeviceIoControl(hDevice: THandle; dwIoControlCode: DWord; lpInBuffer: Pointer; nInBufferSize: DWord; lpOutBuffer: Pointer; nOutBufferSize: DWord; var lpBytesReturned: DWord; lpOverlapped: POverlapped): BOOL; stdcall; external kernel32; } function GetDiskInfo(var aDriveFolderOrFile: TFileName; out aAvailableBytes, aFreeBytes, aTotalBytes: QWord; aVolumeName: PSynUnicode): boolean; var tmp: array[0..MAX_PATH - 1] of WideChar; dummy, flags: DWord; dn: SynUnicode; begin if aDriveFolderOrFile = '' then aDriveFolderOrFile := SysUtils.UpperCase( ExtractFileDrive(Executable.ProgramFilePath)); dn := SynUnicode(aDriveFolderOrFile); // use RTL for UTF-16 conversion if (dn <> '') and (dn[2] = ':') and (dn[3] = #0) then dn := dn + '\'; if (aVolumeName <> nil) and (aVolumeName^ = '') then begin tmp[0] := #0; GetVolumeInformationW(pointer(dn), tmp, MAX_PATH, nil, dummy, flags, nil, 0); aVolumeName^ := tmp; end; result := GetDiskFreeSpaceExW(pointer(dn), aAvailableBytes, aTotalBytes, aFreeBytes); end; function GetDiskPartitions: TDiskPartitions; var drives, drive, m, n: integer; fn: TFileName; volume: SynUnicode; av, fr, tot: QWord; p: ^TDiskPartition; begin result := nil; n := 0; fn := '#:'; drives := GetLogicalDrives; m := 1 shl 2; // bit 2 = drive C for drive := 3 to 26 do begin // retrieve partitions mounted as C..Z drives if drives and m <> 0 then begin fn[1] := char(64 + drive); if GetDiskInfo(fn, av, fr, tot, @volume) then begin SetLength(result, n + 1); p := @result[n]; Win32PWideCharToUtf8(pointer(volume), length(volume), p^.name); p^.mounted := fn; p^.size := tot; volume := ''; inc(n); end; end; m := m shl 1; end; end; var TextAttr: integer = ord(ccDarkGray); procedure TextColor(Color: TConsoleColor); var oldAttr: integer; begin if not HasConsole then exit; oldAttr := TextAttr; TextAttr := (TextAttr and $F0) or ord(Color); if TextAttr <> oldAttr then SetConsoleTextAttribute(StdOut, TextAttr); end; procedure TextBackground(Color: TConsoleColor); var oldAttr: integer; begin if not HasConsole then exit; oldAttr := TextAttr; TextAttr := (TextAttr and $0F) or (ord(Color) shl 4); if TextAttr <> oldAttr then SetConsoleTextAttribute(StdOut, TextAttr); end; var ConsoleCriticalSection: TOSLock; procedure ConsoleWrite(const Text: RawUtf8; Color: TConsoleColor; NoLineFeed, NoColor: boolean); var txt: RawByteString; l: PtrInt; written: cardinal; begin if not HasConsole then exit; txt := Utf8ToConsole(Text); l := length(txt); if not NoLineFeed then begin SetLength(txt, l + 2); // faster to reallocate than WriteFile() twice PWord(@PByteArray(txt)[l])^ := $0a0d; // CRLF inc(l, 2); end; ConsoleCriticalSection.Lock; try if not NoColor then TextColor(Color); WriteFile(StdOut, pointer(txt)^, l, written, nil); // FlushFileBuffers(StdOut); // don't: would block until read on the pipe finally ConsoleCriticalSection.UnLock; end; end; function ConsoleKeyPressed(ExpectedKey: Word): boolean; var events, read: DWord; rec: TInputRecord; h: THandle; begin result := false; h := GetStdHandle(STD_INPUT_HANDLE); events := 0; GetNumberOfConsoleInputEvents(h, events); if events <> 0 then begin PeekConsoleInput(h, rec, 1, read); if read <> 0 then if rec.EventType = KEY_EVENT then if rec.Event.KeyEvent.bKeyDown and ((ExpectedKey = 0) or (rec.Event.KeyEvent.wVirtualKeyCode = ExpectedKey)) then result := true else FlushConsoleInputBuffer(h) else FlushConsoleInputBuffer(h); end; end; type TConsoleHandleCtrlC = class private class procedure HandleCtrlC; end; var ConsoleHandleCtrlCPressed: boolean; class procedure TConsoleHandleCtrlC.HandleCtrlC; begin ConsoleHandleCtrlCPressed := true; end; procedure ConsoleWaitForEnterKey; var msg: TMsg; begin ConsoleHandleCtrlCPressed := false; HandleCtrlC(TConsoleHandleCtrlC.HandleCtrlC); try if GetCurrentThreadID = MainThreadID then // process the messages from the main thread while waiting while not ConsoleKeyPressed(VK_RETURN) and not ConsoleHandleCtrlCPressed do begin if IsMultiThread then CheckSynchronize{$ifndef DELPHI6OROLDER}(100){$endif} else Sleep(100); while PeekMessage(msg, 0, 0, 0, PM_REMOVE) do if msg.Message = WM_QUIT then exit // stop waiting when the process is gracefully closing else begin TranslateMessage(msg); DispatchMessage(msg); end; end else // just intercept any WM_QUIT message on this sub-thread while not ConsoleKeyPressed(VK_RETURN) and not ConsoleHandleCtrlCPressed do begin Sleep(100); if PeekMessage(msg, 0, 0, 0, PM_REMOVE) then if msg.Message = WM_QUIT then exit; // nothing to dispatch with PostThreadMessage() end; finally HandleCtrlC(nil); end; end; {$I+} {$ifdef ISDELPHI} var // Delphi doesn't define this global variable need by ConsoleReadBody StdInputHandle: THandle; {$endif ISDELPHI} function ConsoleStdInputLen: integer; begin if StdInputHandle = 0 then StdInputHandle := GetStdHandle(STD_INPUT_HANDLE); if not PeekNamedPipe(StdInputHandle, nil, 0, nil, @result, nil) then result := 0; end; procedure Utf8ToConsoleDoConv(const Utf8: RawUtf8; var Console: RawByteString); var tmp16, tmp: TSynTempBuffer; begin Utf8ToWin32PWideChar(Utf8, tmp16); if tmp16.len = 0 then begin Console := Utf8; // input is not valid UTF-8 -> return without conversion exit; end; tmp.Init(tmp16.len * 3); CharToOemBuffW(tmp16.buf, tmp.buf, tmp16.len + 1); // +1 = ending #0 tmp16.Done; FastSetStringCP(Console, tmp.buf, StrLen(tmp.buf), CP_OEMCP); tmp.Done; end; function Utf8ToConsole(const S: RawUtf8): RawByteString; begin if IsAnsiCompatible(S) then result := S // no conversion needed else Utf8ToConsoleDoConv(S, result); end; function TFileVersion.RetrieveInformationFromFileName: boolean; var Size, Size2: DWord; Pt: Pointer; Trans: PWordArray; LanguageInfo: RawUtf8; Info: ^TVSFixedFileInfo; FileTime: TFILETIME; SystemTime: TSYSTEMTIME; tmp: TW32Temp; function ReadResourceByName(const From: RawUtf8): RawUtf8; var StrValPt: pointer; sz: DWord; u: SynUnicode; begin u := Utf8Decode('\StringFileInfo\' + LanguageInfo + '\' + From); VerQueryValueW(Pt, pointer(u), StrValPt, sz); if sz > 0 then Win32PWideCharToUtf8(StrValPt, result) else result := ''; end; begin result := false; if fFileName = '' then exit; // GetFileVersionInfo() modifies the filename parameter data while parsing // -> copy the FileName into local tmp buffer to create a writable copy Size := GetFileVersionInfoSizeW(W32Copy(fFileName, tmp), Size2); if Size > 0 then begin GetMem(Pt, Size); try if GetFileVersionInfoW(W32Copy(fFileName, tmp), 0, Size, Pt) then begin VerQueryValueW(Pt, '\', pointer(Info), Size2); with Info^ do begin SetVersion({major=} dwFileVersionMS shr 16, {minor=} word(dwFileVersionMS), {release=} dwFileVersionLS shr 16, {build=} word(dwFileVersionLS)); if (dwFileDateLS <> 0) and (dwFileDateMS <> 0) then begin FileTime.dwLowDateTime := dwFileDateLS; // built date from version info FileTime.dwHighDateTime := dwFileDateMS; FileTimeToSystemTime(FileTime, SystemTime); fBuildDateTime := EncodeDate( SystemTime.wYear, SystemTime.wMonth, SystemTime.wDay); end; end; VerQueryValueW(Pt, '\VarFileInfo\Translation', pointer(Trans), Size2); if Size2 >= 4 then begin _fmt('%4.4x%4.4x', [Trans^[0], Trans^[1]], LanguageInfo); CompanyName := ReadResourceByName('CompanyName'); FileDescription := ReadResourceByName('FileDescription'); FileVersion := ReadResourceByName('FileVersion'); InternalName := ReadResourceByName('InternalName'); LegalCopyright := ReadResourceByName('LegalCopyright'); OriginalFilename := ReadResourceByName('OriginalFilename'); ProductName := ReadResourceByName('ProductName'); ProductVersion := ReadResourceByName('ProductVersion'); Comments := ReadResourceByName('Comments'); end; result := true; end; finally Freemem(Pt); end; end; end; procedure GetUserHost(out User, Host: RawUtf8); var tmp: array[byte] of WideChar; tmpsize: cardinal; begin tmpsize := SizeOf(tmp); GetComputerNameW(tmp{%H-}, tmpsize); Win32PWideCharToUtf8(@tmp, Host); tmpsize := SizeOf(tmp); GetUserNameW(tmp, tmpsize); Win32PWideCharToUtf8(@tmp, User); end; var SHFolderDll: THandle; // avoid unneeded reference to ShlObj.pas // - late binding is mandatory to be used on WinPE which does NOT have this dll // - late binding also ensure that we load libraries only when needed SHGetFolderPath: function(hwnd: hwnd; csidl: integer; hToken: THandle; dwFlags: DWord; pszPath: PChar): HRESULT; stdcall; const CSIDL_PERSONAL = $0005; CSIDL_LOCAL_APPDATA = $001C; // local non roaming user folder CSIDL_COMMON_APPDATA = $0023; CSIDL_COMMON_DOCUMENTS = $002E; CSIDL: array[TSystemPath] of integer = ( CSIDL_COMMON_APPDATA, // spCommonData // C:\ProgramData CSIDL_LOCAL_APPDATA, // spUserData // C:\Users\\AppData\Local CSIDL_COMMON_DOCUMENTS, // spCommonDocuments // C:\Users\Public\Documents CSIDL_PERSONAL, // spUserDocuments // C:\Users\\Documents 0, // spTemp 0); // spLog // note: for SYSTEM user, got C:\Windows\System32\config\systemprofile\AppData // or C:\Windows\SysWOW64\config\systemprofile\AppData (on Win32 over Win64) procedure _ComputeSystemPath(kind: TSystemPath; var result: TFileName); const _ENV: array[TSystemPath] of TFileName = ( 'ALLUSERSAPPDATA', // spCommonData 'LOCALAPPDATA', // spUserData '', // spCommonDocuments '', // spUserDocuments 'TEMP', // spTemp ''); // spLog var tmp: array[0..MAX_PATH] of char; begin result := ''; case kind of spLog: begin // try \log - without [idwExcludeWinSys] (writable is enough) result := Executable.ProgramFilePath; if not IsDirectoryWritable(result) then // fallback to 'C:\Users\\AppData\Local\-log' result := format('%s%s-', [GetSystemPath(spUserData), Executable.ProgramName]); result := EnsureDirectoryExists(result + 'log'); if IsDirectoryWritable(result) then exit; // found a folder able to receive new logs // 'C:\Users\\AppData\Local\Temp\-log' result := EnsureDirectoryExists(format('%s%s-log', [GetSystemPath(spTemp), Executable.ProgramName])); end; spTemp: begin // typically 'C:\Users\\AppData\Local\Temp' if GetTempPath(MAX_PATH, @tmp) <> 0 then result := tmp; // retrieve from dedicated standard API if result = '' then result := GetEnvironmentVariable(_ENV[spTemp]); // fallback end; else if (CSIDL[kind] <> 0) and DelayedProc(SHGetFolderPath, SHFolderDll, 'SHFolder.dll', 'SHGetFolderPath' + _AW) and (SHGetFolderPath(0, CSIDL[kind], 0, 0, @tmp) = S_OK) then // retrieved from official CSIDL result := tmp else begin // fallback to environment variables (very unlikely) result := GetEnvironmentVariable(_ENV[kind]); if result = '' then begin result := GetEnvironmentVariable('APPDATA'); if result = '' then result := Executable.ProgramFilePath; end; end; end; if result <> '' then result := IncludeTrailingPathDelimiter(result); // no EnsureDirectoryExists end; procedure PatchCode(Old, New: pointer; Size: PtrInt; Backup: pointer; LeaveUnprotected: boolean); var RestoreProtection, Ignore: DWord; i: PtrInt; begin if VirtualProtect(Old, Size, PAGE_EXECUTE_READWRITE, RestoreProtection) then begin if Backup <> nil then for i := 0 to Size - 1 do // do not use Move() here PByteArray(Backup)^[i] := PByteArray(Old)^[i]; for i := 0 to Size - 1 do // do not use Move() here PByteArray(Old)^[i] := PByteArray(New)^[i]; if not LeaveUnprotected then VirtualProtect(Old, Size, RestoreProtection, Ignore); FlushInstructionCache(GetCurrentProcess, Old, Size); if not CompareMemFixed(Old, New, Size) then raise Exception.Create('PatchCode?'); end; end; { ****************** Operating System Specific Types (e.g. TWinRegistry) } { TWinRegistry } const _HKEY: array[TWinRegistryRoot] of HKEY = ( HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, HKEY_USERS); function TWinRegistry.ReadOpen(root: TWinRegistryRoot; const keyname: RawUtf8; closefirst: boolean): boolean; var tmp: TSynTempBuffer; begin if closefirst then Close; key := 0; result := RegOpenKeyExW( _HKEY[root], Utf8ToWin32PWideChar(keyname, tmp), 0, KEY_READ, key) = 0; tmp.Done; end; procedure TWinRegistry.Close; begin if key <> 0 then RegCloseKey(key); end; function TWinRegistry.ReadString(const entry: SynUnicode; andtrim: boolean): RawUtf8; var rtype, rsize, res: DWord; tmp: TSynTempBuffer; begin result := ''; rsize := {%H-}tmp.Init; // most of the time, a single call is enough res := RegQueryValueExW(key, pointer(entry), nil, @rtype, tmp.buf, @rsize); if res <> 0 then if res = ERROR_MORE_DATA then begin tmp.Init(rsize); // more than 4KB of data (unlikely) res := RegQueryValueExW(key, pointer(entry), nil, nil, tmp.buf, @rsize); end else exit; if res = 0 then begin case rtype of REG_SZ, REG_EXPAND_SZ, REG_MULTI_SZ: // StrLen() will return the first value of REG_MULTI_SZ Win32PWideCharToUtf8(tmp.buf, result); end; if andtrim then TrimSelf(result); end; tmp.Done; end; function TWinRegistry.ReadData(const entry: SynUnicode): RawByteString; var rsize: DWord; begin result := ''; if RegQueryValueExW(key, pointer(entry), nil, nil, nil, @rsize) <> 0 then exit; SetLength(result, rsize); if RegQueryValueExW(key, pointer(entry), nil, nil, pointer(result), @rsize) <> 0 then result := ''; end; function TWinRegistry.ReadDword(const entry: SynUnicode): cardinal; begin if not ReadBuffer(entry, @result, SizeOf(result)) then result := 0; end; function TWinRegistry.ReadQword(const entry: SynUnicode): QWord; begin if not ReadBuffer(entry, @result, SizeOf(result)) then result := 0; end; function TWinRegistry.ReadBuffer(const entry: SynUnicode; Data: pointer; DataLen: DWord): boolean; begin result := RegQueryValueExW(key, pointer(entry), nil, nil, Data, @DataLen) = 0; end; function TWinRegistry.ReadSize(const entry: SynUnicode): integer; begin if RegQueryValueExW(key, pointer(entry), nil, nil, nil, @result) <> 0 then result := -1; end; function TWinRegistry.ReadEnumEntries: TRawUtf8DynArray; var count, maxlen, i, len: DWord; tmp: TSynTempBuffer; begin result := nil; count := 0; if (RegQueryInfoKeyW(key, nil, nil, nil, @count, @maxlen, nil, nil, nil, nil, nil, nil) <> 0) or (count = 0) then exit; SetLength(result, count); inc(maxlen); tmp.Init(maxlen * 2); for i := 0 to count - 1 do begin len := maxlen; if RegEnumKeyExW(key, i, tmp.buf, len, nil, nil, nil, nil) = 0 then Win32PWideCharToUtf8(tmp.buf, len, result[i]); end; tmp.Done; end; const _WSP: array[TWinSystemPrivilege] of string[32] = ( // note: string[32] to ensure there is a trailing #0 for all items 'SeCreateTokenPrivilege', // wspCreateToken 'SeAssignPrimaryTokenPrivilege', // wspAssignPrimaryToken 'SeLockMemoryPrivilege', // wspLockMemory 'SeIncreaseQuotaPrivilege', // wspIncreaseQuota 'SeUnsolicitedInputPrivilege', // wspUnsolicitedInput 'SeMachineAccountPrivilege', // wspMachineAccount 'SeTcbPrivilege', // wspTCP 'SeSecurityPrivilege', // wspSecurity 'SeTakeOwnershipPrivilege', // wspTakeOwnership 'SeLoadDriverPrivilege', // wspLoadDriver 'SeSystemProfilePrivilege', // wspSystemProfile 'SeSystemtimePrivilege', // wspSystemTime 'SeProfileSingleProcessPrivilege', // wspProfSingleProcess 'SeIncreaseBasePriorityPrivilege', // wspIncBasePriority 'SeCreatePagefilePrivilege', // wspCreatePageFile 'SeCreatePermanentPrivilege', // wspCreatePermanent 'SeBackupPrivilege', // wspBackup 'SeRestorePrivilege', // wspRestore 'SeShutdownPrivilege', // wspShutdown 'SeDebugPrivilege', // wspDebug 'SeAuditPrivilege', // wspAudit 'SeSystemEnvironmentPrivilege', // wspSystemEnvironment 'SeChangeNotifyPrivilege', // wspChangeNotify 'SeRemoteShutdownPrivilege', // wspRemoteShutdown 'SeUndockPrivilege', // wspUndock 'SeSyncAgentPrivilege', // wspSyncAgent 'SeEnableDelegationPrivilege', // wspEnableDelegation 'SeManageVolumePrivilege', // wspManageVolume 'SeImpersonatePrivilege', // wspImpersonate 'SeCreateGlobalPrivilege', // wspCreateGlobal 'SeTrustedCredManAccessPrivilege', // wspTrustedCredmanAccess 'SeRelabelPrivilege', // wspRelabel 'SeIncreaseWorkingSetPrivilege', // wspIncWorkingSet 'SeTimeZonePrivilege', // wspTimeZone 'SeCreateSymbolicLinkPrivilege'); // wspCreateSymbolicLink _TokenVirtualizationEnabled = TTokenInformationClass(24); // for oldest Delphi type TOKEN_PRIVILEGES = packed record PrivilegeCount : DWord; Privileges : array[0..0] of LUID_AND_ATTRIBUTES; end; PTOKEN_PRIVILEGES = ^TOKEN_PRIVILEGES; TOKEN_USER = record User: SID_AND_ATTRIBUTES; end; PTOKEN_USER = ^TOKEN_USER; TOKEN_GROUPS = record GroupCount: DWord; Groups: array [0..0] of SID_AND_ATTRIBUTES; end; PTOKEN_GROUPS = ^TOKEN_GROUPS; function OpenProcessToken(ProcessHandle: THandle; DesiredAccess: DWord; var TokenHandle: THandle): BOOL; stdcall; external advapi32; function LookupPrivilegeValueA(lpSystemName, lpName: PAnsiChar; var lpLuid: TLargeInteger): BOOL; stdcall; external advapi32; function LookupPrivilegeNameA(lpSystemName: PAnsiChar; var lpLuid: TLargeInteger; lpName: PAnsiChar; var cbName: DWord): BOOL; stdcall; external advapi32; function LookupAccountSidW(lpSystemName: PWideChar; Sid: PSID; Name: PWideChar; var cchName: DWord; ReferencedDomainName: PAnsiChar; var cchReferencedDomainName: DWord; var peUse: DWord): BOOL; stdcall; external advapi32; function AdjustTokenPrivileges(TokenHandle: THandle; DisableAllPrivileges: BOOL; const NewState: TOKEN_PRIVILEGES; BufferLength: DWord; PreviousState: PTokenPrivileges; ReturnLength: PDWord): BOOL; stdcall; external advapi32; function IsSystemFolder(const Folder: TFileName): boolean; begin if CompareText(copy(Folder, 2, 9), ':\windows') = 0 then result := ord(Folder[11]) in [0, ord('\')] else result := (CompareText(copy(Folder, 2, 15), ':\program files') = 0) and ((ord(Folder[17]) in [0, ord('\')]) or (CompareText(copy(Folder, 17, 6), ' (x86)') = 0)); end; {$ifdef CPU32} var IsUacEnabled: (iueUntested, iueDisabled, iueEnabled); function IsUacVirtualizationEnabled: boolean; var token: THandle; enabled, len: DWORD; begin if IsUacEnabled = iueUntested then if OSVersion < wVista then IsUacEnabled := iueDisabled // no UAC on Windows XP else begin IsUacEnabled := iueEnabled; // enabled by default if OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, token) then begin enabled := 1; len := SizeOf(enabled); if GetTokenInformation(token, _TokenVirtualizationEnabled, @enabled, SizeOf(enabled), len) and (enabled = 0) then // returns enabled=0 if mormot.win.default.manifest.res is included IsUacEnabled := iueDisabled; CloseHandle(token); end; end; result := IsUacEnabled = iueEnabled; end; function IsUacVirtualFolder(const Folder: TFileName): boolean; begin // note: IsUacVirtualizationEnabled returns false if our manifest is included result := (OSVersion >= wVista) and // no UAC on Windows XP IsUacVirtualizationEnabled and IsSystemFolder(Folder); end; {$else} function IsUacVirtualizationEnabled: boolean; begin result := false; // never enabled for a Win64 process end; function IsUacVirtualFolder(const Folder: TFileName): boolean; begin result := false; // never enabled for a Win64 process end; {$endif CPU32} function RawTokenOpen(wtt: TWinTokenType; access: cardinal): THandle; begin if wtt = wttProcess then begin if not OpenProcessToken(GetCurrentProcess, access, result) then RaiseLastError('OpenToken: OpenProcessToken'); end else if not OpenThreadToken(GetCurrentThread, access, false, result) then if GetLastError = ERROR_NO_TOKEN then begin // try to impersonate the thread if not ImpersonateSelf(SecurityImpersonation) or not OpenThreadToken(GetCurrentThread, access, false, result) then RaiseLastError('OpenToken: ImpersonateSelf'); end else RaiseLastError('OpenToken: OpenThreadToken'); end; function RawTokenGetInfo(tok: THandle; tic: TTokenInformationClass; var buf: TSynTempBuffer): cardinal; begin buf.Init; // stack-allocated buffer (always enough) result := 0; // error if (tok = INVALID_HANDLE_VALUE) or (tok = 0) or GetTokenInformation(tok, tic, buf.buf, buf.len, result) then exit; // we directly store the output buffer on buf stack if GetLastError <> ERROR_INSUFFICIENT_BUFFER then begin result := 0; exit; end; buf.Done; buf.Init(result); // we need a bigger buffer if not GetTokenInformation(tok, tic, buf.buf, buf.len, result) then result := 0; end; function RawTokenSid(tok: THandle; var buf: TSynTempBuffer): PSid; begin if RawTokenGetInfo(tok, TokenUser, buf) >= SizeOf(TOKEN_USER) then result := PSid(PTOKEN_USER(buf.buf)^.User.Sid) // within buf.buf/len else result := nil; end; function CurrentSid(wtt: TWinTokenType; name, domain: PRawUtf8): RawUtf8; var sid: RawSid; begin CurrentRawSid(sid, wtt, name, domain); result := RawSidToText(sid); end; procedure CurrentRawSid(out sid: RawSid; wtt: TWinTokenType; name, domain: PRawUtf8); var h: THandle; p: PSid; n, d: RawUtf8; tmp: TSynTempBuffer; begin h := RawTokenOpen(wtt, TOKEN_QUERY); p := RawTokenSid(h, tmp); if p <> nil then begin ToRawSid(p, sid); if (name <> nil) or (domain <> nil) then begin LookupSid(p, n, d); if name <> nil then name^ := n; if domain <> nil then domain^ := d; end; end; tmp.Done; CloseHandle(h); end; function ToText(p: TWinSystemPrivilege): PShortString; begin result := @_WSP[p]; end; function RawTokenGroups(tok: THandle; var buf: TSynTempBuffer): PSids; var nfo: PTokenGroups; i: PtrInt; begin result := nil; if RawTokenGetInfo(tok, TokenGroups, buf) < SizeOf({%H-}nfo^) then exit; nfo := buf.buf; if nfo.GroupCount = 0 then exit; SetLength(result, nfo.GroupCount); for i := 0 to nfo.GroupCount - 1 do result[i] := pointer(nfo.Groups[i].Sid); // within buf.buf/len end; function TokenGroupsText(tok: THandle): TRawUtf8DynArray; var tmp: TSynTempBuffer; begin result := SidsToText(RawTokenGroups(tok, tmp)); tmp.Done; end; function TokenHasGroup(tok: THandle; sid: PSid): boolean; var tmp: TSynTempBuffer; i: PtrInt; begin result := false; if (sid <> nil) and (RawTokenGetInfo(tok, TokenGroups, tmp) <> 0) then with PTokenGroups(tmp.buf)^ do for i := 0 to GroupCount - 1 do if SidCompare(pointer(Groups[i].Sid), sid) = 0 then begin result := true; break; end; tmp.Done; end; function TokenHasAnyGroup(tok: THandle; const sid: RawSidDynArray): boolean; var tmp: TSynTempBuffer; begin result := HasAnySid(RawTokenGroups(tok, tmp), sid); tmp.Done; end; function CurrentGroups(wtt: TWinTokenType; var tmp: TSynTempBuffer): PSids; var h: THandle; begin h := RawTokenOpen(wtt, TOKEN_QUERY); result := RawTokenGroups(h, tmp); CloseHandle(h); end; function CurrentGroupsSid(wtt: TWinTokenType): TRawUtf8DynArray; var tmp: TSynTempBuffer; begin result := SidsToText(CurrentGroups(wtt, tmp)); tmp.Done; end; function CurrentKnownGroups(wtt: TWinTokenType): TWellKnownSids; var tmp: TSynTempBuffer; begin result := SidToKnownGroups(CurrentGroups(wtt, tmp)); tmp.Done; end; function CurrentUserHasGroup(sid: PSid; wtt: TWinTokenType): boolean; var h: THandle; begin h := RawTokenOpen(wtt, TOKEN_QUERY); result := TokenHasGroup(h, sid); CloseHandle(h); end; function CurrentUserHasGroup(wks: TWellKnownSid; wtt: TWinTokenType): boolean; begin result := (wks <> wksNull) and CurrentUserHasGroup(pointer(KnownRawSid(wks)), wtt); end; function CurrentUserHasGroup(const sid: RawUtf8; wtt: TWinTokenType): boolean; var s: TSid; begin result := TextToSid(pointer(sid), s) and CurrentUserHasGroup(@s, wtt); end; function CurrentUserHasAnyGroup(const sid: RawSidDynArray; wtt: TWinTokenType): boolean; var tmp: TSynTempBuffer; begin result := HasAnySid(CurrentGroups(wtt, tmp), sid); tmp.Done; end; function CurrentUserHasGroup(const name, domain, server: RawUtf8; wtt: TWinTokenType): boolean; var i: PtrInt; sids: PSids; n, d: RawUtf8; tmp: TSynTempBuffer; begin result := false; sids := CurrentGroups(wtt, tmp); for i := 0 to length(sids) - 1 do if (SidToKnown(sids[i]) = wksNull) and (LookupSid(sids[i], n, d, server) = stTypeGroup) then if PropNameEquals(n, name) and ((domain = '') or PropNameEquals(d, domain)) then begin result := true; break; end; tmp.Done; end; function CurrentUserIsAdmin: boolean; begin result := CurrentUserHasGroup(wksBuiltinAdministrators); end; function LookupSid(sid: PSid; out name, domain: RawUtf8; const server: RawUtf8): TSidType; var n, d: array[byte] of WideChar; s: TSynTempBuffer; nl, dl, use: cardinal; begin result := stUndefined; if sid = nil then exit; nl := SizeOf(n); dl := SizeOf(d); if LookupAccountSidW(Utf8ToWin32PWideChar(server, s), sid, @n, nl, @d, dl, use) then begin Win32PWideCharToUtf8(@n, name); Win32PWideCharToUtf8(@d, domain); if use <= byte(high(TSidType)) then result := TSidType(use); end; s.Done; end; function LookupSid(const sid: RawUtf8; out name, domain: RawUtf8; const server: RawUtf8): TSidType; var s: TSid; begin if TextToSid(pointer(sid), s) then result := LookupSid(@s, name, domain, server) else result := stUndefined; end; function LookupToken(tok: THandle; out name, domain: RawUtf8; const server: RawUtf8): boolean; var tmp: TSynTempBuffer; begin result := LookupSid(RawTokenSid(tok, tmp) ,name, domain, server) <> stUndefined; tmp.Done; end; function LookupToken(tok: THandle; const server: RawUtf8): RawUtf8; var name, domain: RawUtf8; begin if LookupToken(tok, name, domain, server) then result := domain + '\' + name else result := ''; end; { TSynWindowsPrivileges } procedure TSynWindowsPrivileges.Init(aTokenPrivilege: TWinTokenType; aLoadPrivileges: boolean); begin fAvailable := []; fEnabled := []; fDefEnabled := []; fToken := RawTokenOpen(aTokenPrivilege, TOKEN_QUERY or TOKEN_ADJUST_PRIVILEGES); if aLoadPrivileges then LoadPrivileges; end; procedure TSynWindowsPrivileges.Done(aRestoreInitiallyEnabled: boolean); var p: TWinSystemPrivilege; new: TWinSystemPrivileges; begin if aRestoreInitiallyEnabled then begin new := fEnabled - fDefEnabled; for p := low(p) to high(p) do if p in new then Disable(p); end; CloseHandle(fToken); fToken := 0; end; function TSynWindowsPrivileges.Enable(aPrivilege: TWinSystemPrivilege): boolean; begin result := aPrivilege in fEnabled; if result or not (aPrivilege in fAvailable) or not SetPrivilege(aPrivilege, true) then exit; Include(fEnabled, aPrivilege); result := true; end; function TSynWindowsPrivileges.Disable( aPrivilege: TWinSystemPrivilege): boolean; begin result := not (aPrivilege in fEnabled); if result or not (aPrivilege in fAvailable) or not SetPrivilege(aPrivilege, false) then exit; Exclude(fEnabled, aPrivilege); result := true; end; procedure TSynWindowsPrivileges.LoadPrivileges; var buf: TSynTempBuffer; name: string[127]; tp: PTOKEN_PRIVILEGES; i: PtrInt; len: cardinal; p: TWinSystemPrivilege; priv: PLUIDANDATTRIBUTES; begin if Token = 0 then raise EOSException.Create('LoadPriviledges: no token'); fAvailable := []; fEnabled := []; fDefEnabled := []; try if RawTokenGetInfo(Token, TokenPrivileges, buf) = 0 then RaiseLastError('LoadPriviledges: GetTokenInformation'); tp := buf.buf; priv := @tp.Privileges; for i := 1 to tp.PrivilegeCount do begin len := high(name); if not LookupPrivilegeNameA(nil, priv.Luid, @name[1], len) or (len = 0) then RaiseLastError('LoadPriviledges: LookupPrivilegeNameA'); name[0] := AnsiChar(len); for p := low(p) to high(p) do if not (p in fAvailable) and PropNameEquals(PShortString(@name), PShortString(@_WSP[p])) then begin include(fAvailable, p); if priv.Attributes and SE_PRIVILEGE_ENABLED <> 0 then include(fDefEnabled, p); break; end; inc(priv); end; fEnabled := fDefEnabled; finally buf.Done; end; end; function TSynWindowsPrivileges.SetPrivilege( wsp: TWinSystemPrivilege; on: boolean): boolean; var tp: TOKEN_PRIVILEGES; id: TLargeInteger; tpprev: TOKEN_PRIVILEGES; cbprev: DWord; begin result := false; if not LookupPrivilegeValueA(nil, @_WSP[wsp][1], id) then exit; tp.PrivilegeCount := 1; tp.Privileges[0].Luid := PInt64(@id)^; tp.Privileges[0].Attributes := 0; cbprev := SizeOf(TOKEN_PRIVILEGES); AdjustTokenPrivileges( Token, false, tp, SizeOf(TOKEN_PRIVILEGES), @tpprev, @cbprev); if GetLastError <> ERROR_SUCCESS then exit; tpprev.PrivilegeCount := 1; tpprev.Privileges[0].Luid := PInt64(@id)^; with tpprev.Privileges[0] do if on then Attributes := Attributes or SE_PRIVILEGE_ENABLED else Attributes := Attributes xor (SE_PRIVILEGE_ENABLED and Attributes); AdjustTokenPrivileges( Token, false, tpprev, cbprev, nil, nil); if GetLastError <> ERROR_SUCCESS then exit; result := true; end; const ntdll = 'NTDLL.DLL'; type _PPS_POST_PROCESS_INIT_ROUTINE = ULONG; PUNICODE_STRING = ^UNICODE_STRING; UNICODE_STRING = packed record Length: word; MaximumLength: word; {$ifdef CPUX64} _align: array[0..3] of byte; {$endif CPUX64} Buffer: PWideChar; end; PMS_PEB_LDR_DATA = ^MS_PEB_LDR_DATA; MS_PEB_LDR_DATA = packed record Reserved1: array[0..7] of byte; Reserved2: array[0..2] of pointer; InMemoryOrderModuleList: LIST_ENTRY; end; PMS_RTL_USER_PROCESS_PARAMETERS = ^MS_RTL_USER_PROCESS_PARAMETERS; MS_RTL_USER_PROCESS_PARAMETERS = packed record Reserved1: array[0..15] of byte; Reserved2: array[0..9] of pointer; ImagePathName: UNICODE_STRING; CommandLine: UNICODE_STRING ; end; PMS_PEB = ^MS_PEB; MS_PEB = packed record Reserved1: array[0..1] of byte; BeingDebugged: BYTE; Reserved2: array[0..0] of byte; {$ifdef CPUX64} _align1: array[0..3] of byte; {$endif CPUX64} Reserved3: array[0..1] of pointer; Ldr: PMS_PEB_LDR_DATA; ProcessParameters: PMS_RTL_USER_PROCESS_PARAMETERS; Reserved4: array[0..103] of byte; Reserved5: array[0..51] of pointer; PostProcessInitRoutine: _PPS_POST_PROCESS_INIT_ROUTINE; Reserved6: array[0..127] of byte; {$ifdef CPUX64} _align2: array[0..3] of byte; {$endif CPUX64} Reserved7: array[0..0] of pointer; SessionId: ULONG; {$ifdef CPUX64} _align3: array[0..3] of byte; {$endif CPUX64} end; PMS_PROCESS_BASIC_INFORMATION = ^MS_PROCESS_BASIC_INFORMATION; MS_PROCESS_BASIC_INFORMATION = packed record ExitStatus: integer; {$ifdef CPUX64} _align1: array[0..3] of byte; {$endif CPUX64} PebBaseAddress: PMS_PEB; AffinityMask: PtrUInt; BasePriority: integer; {$ifdef CPUX64} _align2: array[0..3] of byte; {$endif CPUX64} UniqueProcessId: PtrUInt; InheritedFromUniqueProcessId: PtrUInt; end; {$Z4} PROCESSINFOCLASS = ( ProcessBasicInformation = 0, ProcessDebugPort = 7, ProcessWow64Information = 26, ProcessImageFileName = 27, ProcessBreakOnTermination = 29, ProcessSubsystemInformation = 75); {$Z1} NTSTATUS = integer; PVOID = pointer; PPVOID = ^PVOID; OBJECT_ATTRIBUTES = record Length: ULONG; RootDirectory: THandle; ObjectName: PUNICODE_STRING; Attributes: ULONG; SecurityDescriptor: Pointer; // Points to type SECURITY_DESCRIPTOR SecurityQualityOfService: Pointer; // Points to type SECURITY_QUALITY_OF_SERVICE end; POBJECT_ATTRIBUTES = ^OBJECT_ATTRIBUTES; var // low-level (undocumented) ntdll.dll functions - accessed via late-binding NtQueryInformationProcess: function(ProcessHandle: THandle; ProcessInformationClass: PROCESSINFOCLASS; ProcessInformation: pointer; ProcessInformationLength: ULONG; ReturnLength: PULONG): NTSTATUS; stdcall; RtlInitUnicodeString: function(var DestinationString: UNICODE_STRING; const SourceString: PWideChar): NTSTATUS; stdcall; NtOpenSection: function (SectionHandle: PHANDLE; DesiredAccess: ACCESS_MASK; ObjectAttributes: POBJECT_ATTRIBUTES): NTSTATUS; stdcall; NtMapViewOfSection: function (SectionHandle, ProcessHandle: THandle; BaseAddress: PPVOID; ZeroBits: ULONG; CommitSize: ULONG; var SectionOffset: TLargeInteger; ViewSize: PULONG; InheritDisposition: DWord; AllocationType: ULONG; Protect: ULONG): NTSTATUS; stdcall; NtUnmapViewOfSection: function (ProcessHandle: THandle; BaseAddress: PVOID): NTSTATUS; stdcall; function ReadSystemMemory(address, size: PtrUInt): RawByteString; var memfile: UNICODE_STRING; att: OBJECT_ATTRIBUTES; mem: THandle; add: TLargeInteger; virt: pointer; begin result := ''; if (size <= 4 shl 20) and // map up to 4MB Assigned(RtlInitUnicodeString) and Assigned(NtOpenSection) and Assigned(NtMapViewOfSection) and Assigned(NtUnmapViewOfSection) then begin RtlInitUnicodeString(memfile, '\device\physicalmemory'); FillCharFast(att, SizeOf(att), 0); att.Length := SizeOf(att); att.ObjectName := @memfile; att.Attributes := $40; // OBJ_CASE_INSENSITIVE if NtOpenSection(@mem, SECTION_MAP_READ, @att) <> 0 then exit; add := address; virt := nil; if NtMapViewOfSection(mem, INVALID_HANDLE_VALUE, @virt, 0, size, add, @size, 1, 0, PAGE_READONLY) = 0 then begin FastSetRawByteString(result, virt, size); NtUnmapViewOfSection(INVALID_HANDLE_VALUE, virt); end; CloseHandle(mem); end; end; function ReadProcessMemory(hProcess: THandle; const lpBaseAddress: Pointer; lpBuffer: Pointer; nSize: PtrUInt; var lpNumberOfBytesRead: PtrUInt): BOOL; stdcall; external kernel32; function InternalGetProcessInfo(aPID: DWord; out aInfo: TWinProcessInfo): boolean; var bytesread: PtrUInt; sizeneeded: DWord; pbi: MS_PROCESS_BASIC_INFORMATION; peb: MS_PEB; peb_upp: MS_RTL_USER_PROCESS_PARAMETERS; prochandle: THandle; begin result := false; Finalize(aInfo); FillCharFast(aInfo, SizeOf(aInfo), 0); if (APID = 0) and Assigned(NtQueryInformationProcess) then exit; prochandle := OpenProcess( PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, FALSE, aPid); if prochandle = INVALID_HANDLE_VALUE then exit; Include(aInfo.AvailableInfo, wpaiPID); aInfo.PID := aPid; try // read PBI (Process Basic Information) sizeneeded := 0; FillCharFast(pbi, SizeOf(pbi), 0); if NtQueryInformationProcess(prochandle, ProcessBasicInformation, @pbi, Sizeof(pbi), @sizeneeded) < 0 then exit; with aInfo do begin Include(AvailableInfo, wpaiBasic); PID := pbi.UniqueProcessId; ParentPID := pbi.InheritedFromUniqueProcessId; BasePriority := pbi.BasePriority; ExitStatus := pbi.ExitStatus; PEBBaseAddress := pbi.PebBaseAddress; AffinityMask := pbi.AffinityMask; end; // read PEB (Process Environment Block) if not Assigned(pbi.PebBaseAddress) then exit; bytesread := 0; FillCharFast(peb, SizeOf(peb), 0); if not ReadProcessMemory(prochandle, pbi.PebBaseAddress, @peb, SizeOf(peb), bytesread) then exit; Include(aInfo.AvailableInfo, wpaiPEB); aInfo.SessionID := peb.SessionId; aInfo.BeingDebugged := peb.BeingDebugged; FillCharFast(peb_upp, SizeOf(MS_RTL_USER_PROCESS_PARAMETERS), 0); bytesread := 0; if not ReadProcessMemory(prochandle, peb.ProcessParameters, @peb_upp, SizeOf(MS_RTL_USER_PROCESS_PARAMETERS), bytesread) then exit; // command line info if peb_upp.CommandLine.Length > 0 then begin SetLength(aInfo.CommandLine, peb_upp.CommandLine.Length shr 1); bytesread := 0; if not ReadProcessMemory(prochandle, peb_upp.CommandLine.Buffer, pointer(aInfo.CommandLine), peb_upp.CommandLine.Length, bytesread) then exit; Include(aInfo.AvailableInfo, wpaiCommandLine); end; // image info if peb_upp.ImagePathName.Length > 0 then begin SetLength(aInfo.ImagePath, peb_upp.ImagePathName.Length shr 1); bytesread := 0; if not ReadProcessMemory(prochandle, peb_upp.ImagePathName.Buffer, pointer(aInfo.ImagePath), peb_upp.ImagePathName.Length, bytesread) then exit; Include(aInfo.AvailableInfo, wpaiImagePath); end; result := true; finally CloseHandle(prochandle); end; end; procedure GetProcessInfo(aPid: cardinal; out aInfo: TWinProcessInfo); var privileges: TSynWindowsPrivileges; begin privileges.Init(wttThread); try privileges.Enable(wspDebug); InternalGetProcessInfo(aPid, aInfo); finally privileges.Done; end; end; procedure GetProcessInfo(const aPidList: TCardinalDynArray; out aInfo: TWinProcessInfoDynArray); var privileges: TSynWindowsPrivileges; i: PtrInt; begin SetLength(aInfo, Length(aPidList)); privileges.Init(wttThread); try privileges.Enable(wspDebug); for i := 0 to High(aPidList) do InternalGetProcessInfo(aPidList[i], aInfo[i]); finally privileges.Done; end; end; function ReadRegString(Key: THandle; const Path, Value: string): string; var siz, typ: DWord; tmp: array[byte] of char; k: HKey; begin result := ''; if RegOpenKeyEx(Key, pointer(Path), 0, KEY_QUERY_VALUE, k) <> ERROR_SUCCESS then exit; siz := 250; typ := REG_SZ; if RegQueryValueEx(k, pointer(Value), nil, @typ, @tmp, @siz) = ERROR_SUCCESS then result := tmp; RegCloseKey(k); end; { TWinCryptoApi } function TWinCryptoApi.Available: boolean; begin if not Tested then Resolve; result := Assigned(AcquireContextA); end; procedure TWinCryptoApi.Resolve; const NAMES: array[0..7] of PAnsiChar = ( 'CryptAcquireContextA', 'CryptReleaseContext', 'CryptImportKey', 'CryptSetKeyParam', 'CryptDestroyKey', 'CryptEncrypt', 'CryptDecrypt', 'CryptGenRandom'); var P: PPointer; i: PtrInt; begin Tested := true; Handle := GetModuleHandle('advapi32.dll'); if Handle <> 0 then begin P := @@AcquireContextA; for i := 0 to high(NAMES) do begin P^ := LibraryResolve(Handle, NAMES[i]); if P^ = nil then begin PPointer(@@AcquireContextA)^ := nil; break; end; inc(P); end; end; // note: CryptSignMessage and CryptVerifyMessageSignature are in crypt32.dll end; type {$ifdef FPC} {$packrecords C} // mandatory under Win64 {$endif FPC} DATA_BLOB = record cbData: DWord; pbData: PAnsiChar; end; PDATA_BLOB = ^DATA_BLOB; {$ifdef FPC} {$packrecords DEFAULT} {$endif FPC} const crypt32 = 'Crypt32.dll'; CRYPTPROTECT_UI_FORBIDDEN = 1; CRYPT_STRING_BASE64HEADER = 0; // = PEM textual format function CryptProtectData(const DataIn: DATA_BLOB; szDataDescr: PWideChar; OptionalEntropy: PDATA_BLOB; Reserved, PromptStruct: Pointer; dwFlags: DWord; var DataOut: DATA_BLOB): BOOL; stdcall; external crypt32; function CryptUnprotectData(const DataIn: DATA_BLOB; szDataDescr: PWideChar; OptionalEntropy: PDATA_BLOB; Reserved, PromptStruct: Pointer; dwFlags: DWord; var DataOut: DATA_BLOB): BOOL; stdcall; external crypt32; function CryptDataForCurrentUserDPAPI(const Data, AppSecret: RawByteString; Encrypt: boolean): RawByteString; var src, dst, ent: DATA_BLOB; e: PDATA_BLOB; ok: boolean; begin src.pbData := pointer(Data); src.cbData := length(Data); if AppSecret <> '' then begin ent.pbData := pointer(AppSecret); ent.cbData := length(AppSecret); e := @ent; end else e := nil; if Encrypt then ok := CryptProtectData( src, nil, e, nil, nil, CRYPTPROTECT_UI_FORBIDDEN, dst) else ok := CryptUnprotectData( src, nil, e, nil, nil, CRYPTPROTECT_UI_FORBIDDEN, dst); if ok then begin FastSetRawByteString(result, dst.pbData, dst.cbData); LocalFree(HLOCAL(dst.pbData)); end else result := ''; end; function CertOpenSystemStoreW(hProv: HCRYPTPROV; szSubsystemProtocol: PWideChar): HCERTSTORE ; stdcall; external crypt32; function CertEnumCertificatesInStore(hCertStore: HCERTSTORE; pPrevCertContext: PCCERT_CONTEXT): PCCERT_CONTEXT; stdcall; external crypt32; function CryptBinaryToStringA(pBinary: PByte; cbBinary, dwFlags: DWord; pszString: PAnsiChar; var pchString: DWord): BOOL; stdcall; external crypt32; function CertCloseStore(hCertStore: HCERTSTORE; dwFlags: DWord): BOOL; stdcall; external crypt32; function _GetSystemStoreAsPem(CertStore: TSystemCertificateStore): RawUtf8; var store: HCERTSTORE; ctx: PCCERT_CONTEXT; resultlen, certlen: DWord; tmp: TSynTempBuffer; begin // call the Windows API to retrieve the System certificates result := ''; resultlen := 0; store := CertOpenSystemStoreW(nil, WINDOWS_CERTSTORE[CertStore]); try ctx := CertEnumCertificatesInStore(store, nil); while ctx <> nil do begin certlen := 0; if not CryptBinaryToStringA(ctx^.pbCertEncoded, ctx^.cbCertEncoded, CRYPT_STRING_BASE64HEADER, nil, certlen) then break; tmp.Init(certlen); // a PEM is very likely to be < 8KB so will be on stack if CryptBinaryToStringA(ctx^.pbCertEncoded, ctx^.cbCertEncoded, CRYPT_STRING_BASE64HEADER, tmp.buf, certlen) then begin SetLength(result, resultlen + certlen); MoveFast(tmp.buf^, PByteArray(result)[resultlen], certlen); inc(resultlen, certlen); end; tmp.Done; ctx := CertEnumCertificatesInStore(store, ctx); // next certificate end; finally CertCloseStore(store, 0); end; end; function SearchSmbios(const mem: RawByteString; var info: TRawSmbiosInfo): PtrUInt; forward; // implemented later in mormot.core.os.pas const _RSMB_ = $52534D42; // potential location of the SMBIOS buffer pointers within a 64KB fixed frame SMB_START = $000f0000; SMB_STOP = $00100000; function _GetRawSmbios(var info: TRawSmbiosInfo): boolean; var siz: DWord; tmp: RawByteString; addr: PtrUInt; get: function(sig, id: DWord; buf: pointer; siz: DWord): PtrUInt; stdcall; begin // first try to use Vista+ API which supports EFI get := GetProcAddress(GetModuleHandle(kernel32), 'GetSystemFirmwareTable'); if Assigned(get) then begin siz := get(_RSMB_, 0, nil, 0); // first call to retrieve the full size if siz > SizeOf(info) then begin FastNewRawByteString(tmp, siz); get(_RSMB_, 0, pointer(tmp), siz); PInt64(@info)^ := PInt64(tmp)^; // header fields = 64-bit FastSetRawByteString(info.data, @PInt64Array(tmp)[1], siz - SizeOf(Int64)); result := true; exit; end; end; // on XP, read directly from physical memory via ntdll.dll low-level API result := false; tmp := ReadSystemMemory(SMB_START, SMB_STOP - SMB_START); if tmp = '' then exit; addr := SearchSmbios(tmp, info); if addr = 0 then exit; info.data := ReadSystemMemory(addr, info.Length); result := info.data <> ''; end; procedure DirectSmbiosInfo(out info: TSmbiosBasicInfos); begin // not needed - GetRawSmbios() is likely to work with no administrator rights end; threadvar // do not publish for compilation within Delphi packages CoInitCounter: integer; // avoid including ActiveX unit function CoInitialize(_para1: pointer): HRESULT; stdcall; external 'ole32.dll'; procedure CoUninitialize; stdcall; external 'ole32.dll'; procedure CoInit; begin inc(CoInitCounter); // is a threadvar: no InterlockedIncrement() needed if CoInitCounter = 1 then CoInitialize(nil); end; procedure CoUninit; begin if CoInitCounter <= 0 then raise EOleSysError.Create('You should call TOleDBConnection.Free from the same ' + 'thread which called its Create: i.e. call MyProps.EndCurrentThread from an ' + 'THttpServerGeneric.OnHttpThreadTerminate event - see ticket 213544b2f5'); dec(CoInitCounter); if CoInitCounter = 0 then CoUninitialize; end; { ****************** Unix Daemon and Windows Service Support } function OpenServiceManager(const TargetComputer, DatabaseName: RawUtf8; dwDesiredAccess: cardinal): SC_HANDLE; var t1, t2: TSynTempBuffer; begin result := OpenSCManagerW( Utf8ToWin32PWideChar(TargetComputer, t1), Utf8ToWin32PWideChar(DatabaseName, t2), dwDesiredAccess); t1.Done; t2.Done; end; function OpenServiceInstance(hSCManager: SC_HANDLE; const ServiceName: RawUtf8; dwDesiredAccess: cardinal): SC_HANDLE; var t: TSynTempBuffer; begin result := OpenServiceW( hSCManager, Utf8ToWin32PWideChar(ServiceName, t), dwDesiredAccess); t.Done; end; { TServiceController } type EService = class(Exception); constructor TServiceController.CreateNewService(const TargetComputer, DatabaseName, Name, DisplayName: RawUtf8; const Path: TFileName; const OrderGroup: RawUtf8; const Dependencies: RawUtf8; const Username: RawUtf8; const Password: RawUtf8; DesiredAccess: cardinal; ServiceType: cardinal; StartType: cardinal; ErrorControl: cardinal); var exeName: TFileName; exeNameW: SynUnicode; depW: PWideChar; i: PtrInt; t0, t1, t2, t3, t4, t5: TSynTempBuffer; begin inherited Create; if Path = '' then begin TService.DoLog(sllError, 'CreateNewService(''%'',''%'') with no Path', [Name, DisplayName], self); exit; end; if TargetComputer = '' then if GetDriveType(pointer(ExtractFileDrive(Path))) = DRIVE_REMOTE then begin exeName := ExpandUNCFileName(Path); if (copy(exeName, 1, 12) <> '\\localhost\') or (exeName[14] <> '$') then raise EService.CreateFmt( '%s.CreateNewService(''%s'',''%s'') on remote drive: Path=%s is %s', [ClassNameShort(self)^, Name, DisplayName, Path, exeName]); system.delete(exeName, 1, 12); // \\localhost\c$\... -> c:\... exeName[2] := ':'; end else exeName := Path; exeNameW := SynUnicode(exeName); // use RTL for TFileName to UTF-16 fName := Name; fSCHandle := OpenServiceManager( TargetComputer, DatabaseName, SC_MANAGER_ALL_ACCESS); if fSCHandle = 0 then RaiseLastError('TServiceController.CreateService: OpenServiceManager', EService); depW := Utf8ToWin32PWideChar(Dependencies, t0); if depW <> nil then begin for i := 0 to t0.len - 1 do if depW[i] = ';' then depW[i] := #0; // as expected by CreateServiceW() API depW[t0.len + 1] := #0; // should end with #0#0 end; fHandle := CreateServiceW(fSCHandle, Utf8ToWin32PWideChar(Name, t1), Utf8ToWin32PWideChar(DisplayName, t2), DesiredAccess, ServiceType, StartType, ErrorControl, pointer(exeNameW), Utf8ToWin32PWideChar(OrderGroup, t3), nil, depW, Utf8ToWin32PWideChar(Username, t4), Utf8ToWin32PWideChar(Password, t5)); t0.Done; t1.Done; t2.Done; t3.Done; t4.Done; t5.Done; if fHandle = 0 then RaiseLastError('TServiceController.CreateService:', EService); TService.DoLog(sllInfo, 'CreateService(''%'',''%'',''%'')', [Name, DisplayName, exeName], self); end; constructor TServiceController.CreateOpenService( const TargetComputer, DataBaseName, Name: RawUtf8; DesiredAccess: cardinal); begin inherited Create; fName := RawUtf8(Name); fSCHandle := OpenServiceManager(TargetComputer, DataBaseName, GENERIC_READ); if fSCHandle = 0 then begin TService.DoLog(sllLastError, 'OpenSCManager(''%'',''%'') for [%]', [TargetComputer, DataBaseName, fName], self); exit; end; fHandle := OpenServiceInstance(fSCHandle, Name, DesiredAccess); if fHandle = 0 then TService.DoLog(sllLastError, 'OpenService(%)', [Name], self); end; function TServiceController.Delete: boolean; begin result := false; if fHandle <> 0 then if DeleteService(fHandle) then begin result := CloseServiceHandle(fHandle); fHandle := 0; end else TService.DoLog(sllLastError, 'DeleteService(%)', [fName], self); end; destructor TServiceController.Destroy; begin if fHandle <> 0 then begin CloseServiceHandle(fHandle); fHandle := 0; end; if fSCHandle <> 0 then begin CloseServiceHandle(fSCHandle); fSCHandle := 0; end; inherited; end; function TServiceController.GetState: TServiceState; begin if (self = nil) or (fSCHandle = 0) or (fHandle = 0) then result := ssNotInstalled else result := CurrentStateToServiceState(GetStatus.dwCurrentState); TService.DoLog(sllTrace, 'GetState(%)=%', [fName, ToText(result)^], self); end; function TServiceController.GetStatus: TServiceStatus; begin FillCharFast(fStatus, SizeOf(fStatus), 0); QueryServiceStatus(fHandle, fStatus); result := fStatus; end; function TServiceController.Pause: boolean; begin if fHandle = 0 then result := false else result := ControlService(fHandle, SERVICE_CONTROL_PAUSE, fStatus); end; function TServiceController.Refresh: boolean; begin if fHandle = 0 then result := false else result := ControlService(fHandle, SERVICE_CONTROL_INTERROGATE, fStatus); end; function TServiceController.Resume: boolean; begin if fHandle = 0 then result := false else result := ControlService(fHandle, SERVICE_CONTROL_CONTINUE, fStatus); end; function TServiceController.Shutdown: boolean; begin if fHandle = 0 then result := false else result := ControlService(fHandle, SERVICE_CONTROL_SHUTDOWN, fStatus); end; function TServiceController.Start(const Args: array of PWideChar): boolean; begin TService.DoLog(sllDebug, 'Start(%) Args=% Handle=%', [fName, length(Args), fHandle], self); if fHandle = 0 then begin TService.DoLog(sllError, 'Start(%): no Service', [fName], self); result := false; exit; end; if length(Args) = 0 then result := StartServiceW(fHandle, 0, nil) else result := StartServiceW(fHandle, length(Args), @Args[0]); if not result then TService.DoLog(sllLastError, 'Start(%) failed', [fName], self); end; function TServiceController.Stop: boolean; begin if fHandle = 0 then result := false else result := ControlService(fHandle, SERVICE_CONTROL_STOP, fStatus); end; function TServiceController.SetDescription(const Description: RawUtf8): boolean; var sd: TServiceDescription; t: TSynTempBuffer; begin if Description = '' then begin result := false; exit; end; sd.lpDestription := Utf8ToWin32PWideChar(Description, t); result := ChangeServiceConfig2W(fHandle, SERVICE_CONFIG_DESCRIPTION, @sd); t.Done; end; class procedure TServiceController.CheckParameters( const ExeFileName: TFileName; const ServiceName, DisplayName, Description: RawUtf8; const Dependencies: RawUtf8); var param: string; i: integer; procedure ShowError(Msg: RawUtf8); begin Msg := _fmt('%s: "%s" failed for %s', [ServiceName, Msg, param]); TService.DoLog(sllLastError, '%', [Msg], nil); ConsoleWrite(Msg, ccLightRed); end; begin for i := 1 to ParamCount do begin param := SysUtils.LowerCase(paramstr(i)); TService.DoLog(sllInfo, 'Controling % with command [%]', [ServiceName, param], nil); if param = '/install' then TServiceController.Install( ServiceName, DisplayName, Description, true, ExeFileName, Dependencies) else with TServiceController.CreateOpenService('', '', ServiceName) do try if State = ssErrorRetrievingState then ShowError('State') else if param = '/uninstall' then begin if not Stop then ShowError('Stop'); if not Delete then ShowError('Delete'); end else if param = '/stop' then begin if not Stop then ShowError('Stop'); end else if param = '/start' then begin if not Start([]) then ShowError('Start'); end; finally Free; end; end; end; class function TServiceController.Install( const Name, DisplayName, Description: RawUtf8; AutoStart: boolean; ExeName: TFileName; const Dependencies, Username, Password: RawUtf8): TServiceState; var ctrl: TServiceController; start: cardinal; begin if AutoStart then start := SERVICE_AUTO_START else start := SERVICE_DEMAND_START; if ExeName = '' then ExeName := Executable.ProgramFileName; ctrl := TServiceController.CreateNewService( '', '', Name, DisplayName, ExeName, '', Dependencies, UserName, Password, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, start); try result := ctrl.State; if result <> ssNotInstalled then ctrl.SetDescription(Description); TService.DoLog(sllDebug, 'Install(%)=%', [Name, ToText(result)^], ctrl); finally ctrl.Free; end; end; class function TServiceController.CurrentState(const Name: RawUtf8): TServiceState; begin try with CreateOpenService('', '', Name, SERVICE_QUERY_STATUS) do try result := GetState; finally Free; end; except result := ssErrorRetrievingState; end; end; { TService } class procedure TService.DoLog(Level: TSynLogLevel; const Fmt: RawUtf8; const Args: array of const; Instance: TObject); begin if Assigned(WindowsServiceLog) then WindowsServiceLog(Level, Fmt, Args, Instance); end; constructor TService.Create(const aServiceName, aDisplayName: RawUTf8); begin fServiceName := aServiceName; if aDisplayName = '' then fDisplayName := aServiceName else fDisplayName := aDisplayName; fServiceType := SERVICE_WIN32_OWN_PROCESS or SERVICE_INTERACTIVE_PROCESS; fStartType := SERVICE_AUTO_START; fStatusRec.dwServiceType := fServiceType; fStatusRec.dwCurrentState := SERVICE_STOPPED; fStatusRec.dwControlsAccepted := 31; fStatusRec.dwWin32ExitCode := NO_ERROR; DoLog(sllDebug, 'Create: % (%) running as [%]', [ServiceName, aDisplayName, Executable.ProgramFullSpec], self); end; procedure TService.CtrlHandle(Code: cardinal); begin DoCtrlHandle(Code); end; const _CMD: array[0.. 5] of string[11] = ( 'UNKNOWN', 'STOP', 'PAUSE', 'CONTINUE', 'INTERROGATE', 'SHUTDOWN'); procedure TService.DoCtrlHandle(Code: cardinal); var c: PShortString; begin if Code <= high(_CMD) then c := @_CMD[Code] else c := @_CMD[0]; DoLog(sllTrace, '% DoCtrlHandle(SERVICE_CONTROL_%=%)', [ServiceName, c^, Code], self); try case Code of SERVICE_CONTROL_STOP: begin ReportStatus(SERVICE_STOP_PENDING, NO_ERROR, 0); try if Assigned(fOnStop) then fOnStop(Self); ReportStatus(SERVICE_STOPPED, NO_ERROR, 0); except ReportStatus(SERVICE_STOPPED, ERROR_CAN_NOT_COMPLETE, 0); end; end; SERVICE_CONTROL_PAUSE: begin ReportStatus(SERVICE_PAUSE_PENDING, NO_ERROR, 0); try if Assigned(fOnPause) then fOnPause(Self); ReportStatus(SERVICE_PAUSED, NO_ERROR, 0) except ReportStatus(SERVICE_PAUSED, ERROR_CAN_NOT_COMPLETE, 0) end; end; SERVICE_CONTROL_CONTINUE: begin ReportStatus(SERVICE_CONTINUE_PENDING, NO_ERROR, 0); try if Assigned(fOnResume) then fOnResume(Self); ReportStatus(SERVICE_RUNNING, NO_ERROR, 0); except ReportStatus(SERVICE_RUNNING, ERROR_CAN_NOT_COMPLETE, 0); end; end; SERVICE_CONTROL_SHUTDOWN: begin if Assigned(fOnShutdown) then fOnShutdown(Self); Code := 0; end; SERVICE_CONTROL_INTERROGATE: begin SetServiceStatus(fStatusHandle, fStatusRec); if Assigned(fOnInterrogate) then fOnInterrogate(Self); end; end; if Assigned(fOnControl) then fOnControl(Self, Code); except end; DoLog(sllTrace, '% DoCtrlHandle(SERVICE_CONTROL_%=%) finished', [ServiceName, c^, Code], self); end; procedure TService.Execute; begin try if Assigned(fOnStart) then fOnStart(Self); ReportStatus(SERVICE_RUNNING, NO_ERROR, 0); if Assigned(fOnExecute) then fOnExecute(Self); except ReportStatus(SERVICE_RUNNING, ERROR_CAN_NOT_COMPLETE, 0); end; DoLog(sllTrace, '% Execute finished', [ServiceName], self); end; function TService.GetArgCount: Integer; begin result := length(fArgsList); end; function TService.GetArgs(Idx: Integer): RawUtf8; begin if cardinal(Idx) > cardinal(high(fArgsList)) then result := '' else // avoid GPF result := fArgsList[Idx]; end; function TService.GetControlHandler: TServiceControlHandler; begin result := fControlHandler; if not Assigned(result) then DoLog(sllError, '% GetControlHandler=nil: use TServiceSingle or ' + 'assign a custom ControlHandler', [ServiceName], self); end; function TService.GetInstalled: boolean; begin with TServiceController.CreateOpenService( '', '', fServiceName, SERVICE_QUERY_STATUS) do try result := Handle <> 0; finally Free; end; end; function TService.Install(const Params: TFileName): boolean; var schService: SC_HANDLE; schSCManager: SC_HANDLE; ServicePath: TFileName; p: SynUnicode; t1, t2: TSynTempBuffer; begin result := false; if installed then exit; ServicePath := Executable.ProgramFileName; if Params <> '' then ServicePath := ServicePath + ' ' + Params; p := SynUnicode(ServicePath); // use RTL for TFileName to UTF-16 conversion schSCManager := OpenSCManagerW(nil, nil, SC_MANAGER_ALL_ACCESS); if schSCManager <= 0 then exit; schService := CreateServiceW(schSCManager, Utf8ToWin32PWideChar(fServiceName, t1), Utf8ToWin32PWideChar(fDisplayName, t2), SERVICE_ALL_ACCESS, fServiceType, fStartType, SERVICE_ERROR_NORMAL, pointer(p), nil, nil, nil, nil, nil); t1.Done; t2.Done; if schService > 0 then begin result := true; CloseServiceHandle(schService); end; CloseServiceHandle(schSCManager); end; procedure TService.Remove; begin with TServiceController.CreateOpenService( '', '', fServiceName, SERVICE_ALL_ACCESS) do try if Handle = 0 then exit; Stop; Delete; finally Free; end; end; function TService.ReportStatus(dwState, dwExitCode, dwWait: cardinal): BOOL; var status: PShortString; begin status := ToText(CurrentStateToServiceState(dwState)); DoLog(sllTrace, '% ReportStatus(%=%,%)=%', [ServiceName, WinErrorConstant(dwExitCode), dwExitCode, dwWait, status^], self); if dwState = SERVICE_START_PENDING then fStatusRec.dwControlsAccepted := 0 else fStatusRec.dwControlsAccepted := 31; fStatusRec.dwCurrentState := dwState; fStatusRec.dwWin32ExitCode := dwExitCode; fStatusRec.dwWaitHint := dwWait; if (dwState = SERVICE_RUNNING) or (dwState = SERVICE_STOPPED) then fStatusRec.dwCheckPoint := 0 else inc(fStatusRec.dwCheckPoint); result := SetServiceStatus(fStatusHandle, fStatusRec); if not result then DoLog(sllLastError, '% ReportStatus(%,%)=% SetServiceStatus() failed', [ServiceName, dwExitCode, dwWait, status^], self); end; procedure TService.SetControlHandler(const Value: TServiceControlHandler); begin fControlHandler := Value; end; procedure TService.SetStatus(const Value: TServiceStatus); begin fStatusRec := Value; if fStatusHandle <> 0 then SetServiceStatus(fStatusHandle, fStatusRec); end; procedure TService.Start; begin with TServiceController.CreateOpenService( '', '', fServiceName, SERVICE_ALL_ACCESS) do try Start([]); finally Free; end; end; procedure TService.Stop; begin with TServiceController.CreateOpenService( '', '', fServiceName, SERVICE_ALL_ACCESS) do try Stop; finally Free; end; end; procedure TService.ServiceProc(ArgCount: integer; Args: PPWideChar); var i: PtrInt; t: TSynTempBuffer; begin SetCurrentThreadName('ServiceProc'); DoLog(sllTrace, 'ServiceProc: ArgCount=% ServiceSingle=%', [ArgCount, self], self); if self = nil then exit; dec(ArgCount); // first argument is the service name to be ignored if (Args = nil) or (ArgCount <= 0) then fArgsList := nil // no argument else begin SetLength(fArgsList, ArgCount); for i := 0 to ArgCount - 1 do begin inc(Args); // first was service name Win32PWideCharToUtf8(Args^, fArgsList[i]); // to string end; end; fStatusHandle := RegisterServiceCtrlHandlerW( Utf8ToWin32PWideChar(fServiceName, t), @ControlHandler); t.Done; if fStatusHandle = 0 then begin ReportStatus(SERVICE_STOPPED, GetLastError, 0); exit; end; ReportStatus(SERVICE_START_PENDING, 0, 0); Execute; end; function CurrentStateToServiceState(CurrentState: cardinal): TServiceState; begin case CurrentState of SERVICE_STOPPED: result := ssStopped; SERVICE_START_PENDING: result := ssStarting; SERVICE_STOP_PENDING: result := ssStopping; SERVICE_RUNNING: result := ssRunning; SERVICE_CONTINUE_PENDING: result := ssResuming; SERVICE_PAUSE_PENDING: result := ssPausing; SERVICE_PAUSED: result := ssPaused; else // e.g. SERVICE_CONTROL_SHUTDOWN result := ssNotInstalled; end; end; function GetServicePid(const aServiceName: RawUtf8; aServiceState: PServiceState): cardinal; var ss: TServiceState; st: TServiceStatus; ssp: TServiceStatusProcess; scm: THandle; svc: THandle; size: cardinal; begin result := 0; ss := ssErrorRetrievingState; scm := OpenSCManagerW(nil, nil, SC_MANAGER_CONNECT); if scm <> 0 then try svc := OpenServiceInstance(scm, aServiceName, SERVICE_QUERY_STATUS); if svc <> 0 then try if QueryServiceStatusEx(svc, SC_STATUS_PROCESS_INFO, @ssp, SizeOf(TServiceStatusProcess), size) then begin result := ssp.dwProcessId; if aServiceState <> nil then begin FillCharFast(st, SizeOf(st), 0); QueryServiceStatus(svc, st); ss := CurrentStateToServiceState(st.dwCurrentState); end; end else TService.DoLog(sllLastError, 'GetServicePid(%)', [aServiceName], nil); finally CloseServiceHandle(svc); end else ss := ssNotInstalled; finally CloseServiceHandle(scm); end; if aServiceState <> nil then aServiceState^ := ss; end; { function that a service process specifies as the entry point function of a particular service. The function can have any application-defined name - Args points to an array of pointers that point to null-terminated argument strings. The first argument in the array is the name of the service, and subsequent arguments are any strings passed to the service by the process that called the StartService function to start the service. Args can be nil if there are no arguments. } procedure ServiceProc(ArgCount: cardinal; Args: PPWideChar); stdcall; begin ServiceSingle.ServiceProc(ArgCount, Args); end; function ServiceSingleRun: boolean; var S: array[0..1] of TServiceTableEntry; t: TSynTempBuffer; begin if ServiceSingle = nil then begin result := false; exit; end; S[0].lpServiceName := Utf8ToWin32PWideChar(ServiceSingle.ServiceName, t); S[0].lpServiceProc := ServiceProc; S[1].lpServiceName := nil; S[1].lpServiceProc := nil; { TODO : disable EExternal exception logging in ServicesSingleRun? } result := StartServiceCtrlDispatcherW(@S); t.Done; end; { TServiceSingle } procedure SingleServiceControlHandler(Opcode: LongWord); stdcall; begin if ServiceSingle <> nil then ServiceSingle.DoCtrlHandle(Opcode); end; constructor TServiceSingle.Create(const aServiceName, aDisplayName: RawUtf8); begin if ServiceSingle <> nil then raise EOSException.Create('Only one TServiceSingle is allowed at a time'); inherited Create(aServiceName, aDisplayName); ServiceSingle := self; SetControlHandler(SingleServiceControlHandler); end; destructor TServiceSingle.Destroy; begin try inherited; finally if ServiceSingle = self then ServiceSingle := nil; end; end; function WaitProcess(pid: cardinal; waitseconds: integer): boolean; var ph: THandle; begin result := false; ph := OpenProcess(SYNCHRONIZE, false, pid); if ph = 0 then exit; result := WaitForSingleObject(ph, waitseconds * 1000) = WAIT_OBJECT_0; CloseHandle(ph); end; function CancelProcess(pid: cardinal; waitseconds: integer): boolean; begin result := false; if integer(pid) <= 0 then exit; if GetConsoleWindow <> 0 then // can attach to a single console FreeConsole; if not AttachConsole(pid) then // attach to the pid console exit; SetConsoleCtrlHandler(nil, true); // nil=ignore the event ourself GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); // send Ctrl+C event FreeConsole; // detach SetConsoleCtrlHandler(nil, false); // remove our nil=ignore handler result := WaitProcess(pid, waitseconds); end; function QuitProcess(pid: cardinal; waitseconds: integer): boolean; var snap: THandle; e: TThreadEntry32; begin result := false; if integer(pid) <= 0 then exit; snap := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if snap <= 0 then exit; FillCharFast(e, SizeOf(e), 0); e.dwSize := SizeOf(e); if Thread32First(snap, e) then // loop over all threads of the system repeat if e.th32OwnerProcessID = pid then if PostThreadMessage(e.th32ThreadID, WM_QUIT, 0, 0) then result := true; // at least one thread found until not Thread32Next(snap, e); CloseHandle(snap); if result and (waitseconds <> 0) then result := WaitProcess(pid, waitseconds); end; function KillProcess(pid: cardinal; waitseconds: integer): boolean; var ph: THandle; begin result := false; if integer(pid) <= 0 then exit; ph := OpenProcess(PROCESS_TERMINATE or SYNCHRONIZE, false, pid); if ph = 0 then exit; result := TerminateProcess(ph, 0) and (WaitForSingleObject(ph, waitseconds * 1000) <> WAIT_TIMEOUT); CloseHandle(ph); end; var OnHandleCtrlC: TThreadMethod; function ConsoleCtrlHandler(typ : dword) : BOOL; stdcall; begin result := false; if Assigned(OnHandleCtrlC) then case typ of CTRL_C_EVENT, CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, CTRL_SHUTDOWN_EVENT: begin OnHandleCtrlC(); result := true; end; end; end; function HandleCtrlC(const OnClose: TThreadMethod): boolean; begin result := SetConsoleCtrlHandler(@ConsoleCtrlHandler, Assigned(OnClose)); if result then OnHandleCtrlC := OnClose; end; function DropPriviledges(const UserName: RawUtf8): boolean; begin result := false; end; function ChangeRoot(const FolderName: RawUtf8): boolean; begin result := false; end; type TJobObjectInfoClass = ( BasicLimitInformation = 2, JobObjectBasicProcessIdList = 3, BasicUIRestrictions = 4, SecurityLimitInformation = 5, EndOfJobTimeInformation = 6, AssociateCompletionPortInformation = 7, ExtendedLimitInformation = 9, GroupInformation = 11); TJobObjectBasicLimitInformation = record PerProcessUserTimeLimit: LARGE_INTEGER; PerJobUserTimeLimit: LARGE_INTEGER; LimitFlags: DWord; MinimumWorkingSetSize: PtrUInt; MaximumWorkingSetSize: PtrUInt; ActiveProcessLimit: DWord; Affinity: PtrUInt; PriorityClass: DWord; SchedulingClass: DWord; end; TIOCounter = record ReadOperationCount: QWord; WriteOperationCount: QWord; OtherOperationCount: QWord; ReadTransferCount: QWord; WriteTransferCount: QWord; OtherTransferCount: QWord; end; TJobObjectExtendedLimitInformation = record BasicLimitInformation: TJobObjectBasicLimitInformation; IoInfo: TIOCounter; ProcessMemoryLimit: PtrUInt; JobMemoryLimit: PtrUInt; PeakProcessMemoryUsed: PtrUInt; PeakJobMemoryUsed: PtrUInt; end; const // to create a child process in a new job object // https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects CREATE_BREAKAWAY_FROM_JOB = $1000000; JOB_OBJECT_LIMIT_PROCESS_MEMORY = $00000100; JOB_OBJECT_LIMIT_JOB_MEMORY = $00000200; JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION = $00000400; JOB_OBJECT_LIMIT_BREAKAWAY_OK = $00000800; JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK = $00001000; JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = $00002000; function CreateJobObjectA(lpJobAttributes: PSecurityAttributes; lpName: PWideChar): THandle; stdcall; external kernel32; function SetInformationJobObject(hJob: THandle; JobObjectInformationClass: TJobObjectInfoClass; lpJobObjectInformation: pointer; cbJobObjectInformationLength: DWord): BOOL; stdcall; external kernel32; function AssignProcessToJobObject(hJob, hProcess: THandle): BOOL; stdcall; external kernel32; // redefined here so that we can share code with FPC and Delphi function CreateProcessW(lpApplicationName: PWideChar; lpCommandLine: PWideChar; lpProcessAttributes, lpThreadAttributes: PSecurityAttributes; bInheritHandles: BOOL; dwCreationFlags: cardinal; lpEnvironment: Pointer; lpCurrentDirectory: PWideChar; const lpStartupInfo: TStartupInfo; out lpProcessInformation: TProcessInformation): BOOL; stdcall; external kernel32; function GetExitCodeProcess(hProcess: THandle; out lpExitCode: cardinal): BOOL; stdcall; external kernel32; function CreateJobToClose(parentpid: cardinal): THandle; var security: TSecurityAttributes; limits: TJobObjectExtendedLimitInformation; jobname: RawUtf8; begin security.nLength := SizeOf(security); security.bInheritHandle := false; // should be false security.lpSecurityDescriptor := nil; _fmt('AutoCloseChild%d', [parentpid], jobname); result := CreateJobObjectA(@security, pointer(jobname)); if result = 0 then exit; FillCharFast(limits, SizeOf(limits), 0); limits.BasicLimitInformation.LimitFlags := JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE or JOB_OBJECT_LIMIT_BREAKAWAY_OK; if SetInformationJobObject(result, ExtendedLimitInformation, @limits, SizeOf(limits)) then exit; CloseHandle(result); // error initializing the job (too old or too new OS?) result := 0; end; function AssignJobToProcess(job, process: THandle; const ctxt: ShortString): boolean; begin result := (job <> 0) and AssignProcessToJobObject(job, process); if result then TService.DoLog(sllTrace, 'RunCommand: % AssignProcessToJobObject success', [ctxt], nil) else TService.DoLog(sllDebug, 'RunCommand: % AssignProcessToJobObject failed % %', [ctxt, GetLastError, WinErrorConstant(GetLastError)], nil); end; function RunProcess(const path, arg1: TFileName; waitfor: boolean; const arg2, arg3, arg4, arg5, env: TFileName; options: TRunOptions): integer; begin result := RunCommand(Format('"%s" %s %s %s %s %s', [path, arg1, arg2, arg3, arg4, arg5]), waitfor, env, options); end; var EnvironmentCache: SynUnicode; EnvironmentCacheLock: TLightLock; // just set once procedure GetEnvironmentCache; var e, p: PWideChar; begin EnvironmentCacheLock.Lock; if EnvironmentCache = '' then begin e := GetEnvironmentStringsW; p := e; while p^ <> #0 do inc(p, StrLenW(p) + 1); // go to name=value#0 pairs end SetString(EnvironmentCache, e, (PtrUInt(p) - PtrUInt(e)) shr 1); FreeEnvironmentStringsW(e); end; EnvironmentCacheLock.UnLock; end; function RunCommand(const cmd: TFileName; waitfor: boolean; const env: TFileName; options: TRunOptions; waitfordelayms: cardinal; processhandle: PHandle; redirected: PRawByteString; const onoutput: TOnRedirect; const wrkdir: TFileName): integer; var startupinfo: TStartupInfo; // _STARTUPINFOW or _STARTUPINFOA is equal here processinfo: TProcessInformation; security: TSecurityAttributes; exe, path: TFileName; rd, wr, job: THandle; // CreateProcess can alter the strings -> use local SynUnicode temp variables wcmd, wenv, wpath: SynUnicode; endtix: Int64; flags, exitcode, res: cardinal; ram: TRunAbortMethods; created, terminated: boolean; i, l: PtrInt; procedure RedirectOutput(flush: boolean); var new: RawByteString; pending, n: cardinal; tmp: TSynTempBuffer; begin repeat pending := 0; if not PeekNamedPipe(rd, nil, 0, nil, @pending, nil) or (pending = 0) then begin if (not flush) and Assigned(onoutput) and onoutput('', processinfo.dwProcessId) then exitcode := WAIT_OBJECT_0; // onoutput() returned true to abort break; end; if pending > SizeOf(tmp) then pending := SizeOf(tmp); n := 0; Win32Check(ReadFile(rd, tmp, pending, n, nil)); if n <= 0 then break; if redirected <> nil then begin SetLength(redirected^, l + PtrInt(n)); MoveFast(tmp, PByteArray(redirected^)[l], n); // append without convert inc(l, n); end; if Assigned(onoutput) then begin SetString(new, PAnsiChar(@tmp), n); if onoutput(new, processinfo.dwProcessId) then // notify new content // onoutput() callback returned true to stop the execution if not flush then begin exitcode := WAIT_OBJECT_0; break; end; end; until false; end; begin // https://support.microsoft.com/en-us/help/175986/info-understanding-createprocess-and-command-line-arguments result := -1; // extract path and exe from cmd input if cmd = '' then exit; if cmd[1] = '"' then begin exe := copy(cmd, 2, maxInt); i := Pos('"', exe); if i = 0 then exit; SetLength(exe, i - 1); // unquote "exe" string end else begin i := Pos(' ', cmd); if i = 0 then exe := cmd // no parameter else exe := copy(cmd, 1, i - 1); // split exe and parameter(s) end; path := wrkdir; if (path = '') and (exe <> '') then path := ExtractFilePath(ExpandFileName(exe)); if (path = '') and FileExists(Executable.ProgramFilePath + exe) then path := Executable.ProgramFilePath; // prefers the current folder // prepare the CreateProcess arguments wcmd := SynUnicode(cmd); UniqueString(wcmd); wpath := SynUnicode(path); if env <> '' then begin wenv := SynUnicode(env); if roEnvAddExisting in options then begin if EnvironmentCache = '' then GetEnvironmentCache; wenv := EnvironmentCache + wenv; end else UniqueString(wenv); end; security.nLength := SizeOf(security); security.bInheritHandle := true; security.lpSecurityDescriptor := nil; // launch the process FillCharFast(startupinfo, SizeOf(startupinfo), 0); startupinfo.cb := SizeOf(startupinfo); ram := RunAbortMethods; l := 0; rd := 0; job := 0; if Assigned(onoutput) or (redirected <> nil) then if CreatePipe(rd, wr, @security, 0) then begin SetHandleInformation(rd, HANDLE_FLAG_INHERIT, 0); startupinfo.wShowWindow := SW_HIDE; startupinfo.hStdInput := GetStdHandle(STD_INPUT_HANDLE); startupinfo.hStdOutput := wr; startupinfo.hStdError := wr; startupinfo.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW; if redirected <> nil then redirected^ := ''; exclude(ram, ramCtrlC); // not compatible with redirection end else rd := 0; // https://docs.microsoft.com/en-en/windows/desktop/ProcThread/process-creation-flags FillCharFast(processinfo, SizeOf(processinfo), 0); flags := CREATE_UNICODE_ENVIRONMENT or CREATE_DEFAULT_ERROR_MODE; if not (roWinNoProcessDetach in options) then flags := flags or (DETACHED_PROCESS or CREATE_NEW_PROCESS_GROUP); if roWinJobCloseChildren in options then // create the child process in a new job object flags := flags or CREATE_BREAKAWAY_FROM_JOB; // actually create the new process created := CreateProcessW(nil, pointer(wcmd), @security, @security, true, flags, pointer({%H-}wenv), pointer(wpath), startupinfo, processinfo); if (not created) and (flags and CREATE_BREAKAWAY_FROM_JOB <> 0) then begin TService.DoLog(sllTrace, 'RunCommand: unsupported CREATE_BREAKAWAY_FROM_JOB = % %', [GetLastError, WinErrorConstant(GetLastError)], nil); flags := flags and (not CREATE_BREAKAWAY_FROM_JOB); wcmd := SynUnicode(cmd); // CreateProcesW() modified wcmd content: recreate UniqueString(wcmd); created := CreateProcessW(nil, pointer(wcmd), @security, @security, true, flags, pointer({%H-}wenv), pointer(wpath), startupinfo, processinfo); end; if not created then begin result := -GetLastError; // returns CreateProcessW() error as negative TService.DoLog(sllTrace, 'RunCommand: CreateProcess = % %', [-result, WinErrorConstant(-result)], nil); exit; end; // setup the newly created process if processhandle <> nil then processhandle^ := processinfo.hProcess; if flags and CREATE_BREAKAWAY_FROM_JOB <> 0 then begin job := CreateJobToClose(processinfo.dwProcessID); if (job <> 0) and not AssignJobToProcess(job, processinfo.hProcess, 'CloseChildren') then begin CloseHandle(job); job := 0; end; end; if Assigned(onoutput) then onoutput('', processinfo.dwProcessId); // main wait (and redirect) until the process is finished (or not) if rd <> 0 then begin // wait and redirect - see https://stackoverflow.com/a/25725197/458259 CloseHandle(wr); if waitfordelayms = INFINITE then endtix := 0 else endtix := GetTickCount64 + waitfordelayms; repeat exitcode := WaitForSingleObject(processinfo.hProcess, 50); // note: WaitForMultipleObjects() with rd burns 100% of one core :( Win32Check(exitcode <> WAIT_FAILED); RedirectOutput({flush=}false); until (exitcode = WAIT_OBJECT_0) or ((endtix <> 0) and (GetTickCount64 > endtix)); if GetExitCodeProcess(processinfo.hProcess, exitcode) and (exitcode <> STILL_ACTIVE) then result := exitcode // process ended from natural death -> return code else begin result := -GetLastError; // not able to retrieve exit code // e.g. -STILL_ACTIVE if aborted by onoutput()=true above terminated := false; if RunAbortTimeoutSecs > 0 then begin if ramCtrlC in ram then // try Ctrl+C (disabled above) begin terminated := CancelProcess(processinfo.dwProcessId, RunAbortTimeoutSecs); TService.DoLog(sllTrace, 'RunCommand: CancelProcess(%)=%', [processinfo.dwProcessId, ord(terminated)], nil); end; if (not terminated) and (ramQuit in ram) then begin // try WM_QUIT terminated := QuitProcess(processinfo.dwProcessId, 0); TService.DoLog(sllTrace, 'RunCommand: QuitProcess(%)=%', [processinfo.dwProcessId, ord(terminated)], nil); if terminated then begin endtix := GetTickCount64 + RunAbortTimeoutSecs * 1000; // wait ended repeat res := WaitForSingleObject(processinfo.hProcess, 10); RedirectOutput({flush=}true); // mandatory to unlock pipe until (res <> WAIT_TIMEOUT) or (GetTickCount64 > endtix); terminated := res = WAIT_OBJECT_0; end; end; end; RedirectOutput({flush=}true); // ensure there is no pending data if terminated and // gracefully ended -> try to retrieve the exit code GetExitCodeProcess(processinfo.hProcess, exitcode) then result := exitcode else begin TerminateProcess(processinfo.hProcess, result); // forced kill TService.DoLog(sllTrace, 'RunCommand: TerminateProcess(%)=%', [processinfo.dwProcessId, result], nil); end; end; end else if waitfor then if WaitForSingleObject(processinfo.hProcess, waitfordelayms) = WAIT_FAILED then if waitfordelayms <> INFINITE then result := -1 // still runing after waitfordelayms else result := -GetLastError // failed to wait else if GetExitCodeProcess(processinfo.hProcess, exitcode) then result := exitcode // waited for process to end -> return code else result := -GetLastError // was not able to retrieve exit code else // waitfor is false: asynchronous process launch result := 0; // release the handles created for this process CloseHandle(processinfo.hProcess); CloseHandle(processinfo.hThread); if rd <> 0 then // CloseHandle(wr) has already be done CloseHandle(rd); if job <> 0 then CloseHandle(job); end; function RunRedirect(const cmd: TFileName; exitcode: PInteger; const onoutput: TOnRedirect; waitfordelayms: cardinal; setresult: boolean; const env, wrkdir: TFileName; options: TRunOptions): RawByteString; var res: integer; redir: PRawByteString; begin result := ''; if setresult then redir := @result else redir := nil; res := RunCommand(cmd, true, env, options, waitfordelayms, nil, redir, onoutput, wrkdir); if exitcode <> nil then exitcode^ := res; end; { ****************** Gather Operating System Information } const // lpMinimumApplicationAddress retrieved from Windows is very low ($10000) // - i.e. maximum number of ID per table would be 65536 in TOrm.GetID // - so we'll force an higher and almost "safe" value as 1,048,576 // (real value from runnning Windows is greater than $400000) MIN_PTR_VALUE = $100000; // see http://msdn.microsoft.com/en-us/library/ms724833(v=vs.85).aspx VER_NT_WORKSTATION = 1; VER_NT_DOMAIN_CONTROLLER = 2; VER_NT_SERVER = 3; SM_SERVERR2 = 89; PROCESSOR_ARCHITECTURE_AMD64 = 9; type TSystemLogicalProcessorRelation = ( RelationProcessorCore, RelationNumaNode, RelationCache, RelationProcessorPackage, RelationGroup); TSystemLogicalProcessorCache = ( CacheUnified, CacheInstruction, CacheData, CacheTrace); {$ifdef CPU64} {$A8} {$else} {$A4} {$endif CPU64} TSystemLogicalProcessorInformation = record ProcessorMask: PtrUInt; case Relationship: TSystemLogicalProcessorRelation of RelationProcessorCore: ( ProcessorCoreFlags: BYTE); RelationNumaNode: ( NumaNodeNumber: DWord); RelationCache: ( Cache: record Level: BYTE; Associativity: BYTE; LineSize: WORD; Size: DWord; CacheType: TSystemLogicalProcessorCache; end); RelationGroup: ( Reserved: array [0..1] of QWord); // to define the actual struct size end; {$A+} {$ifndef UNICODE} function GetVersionEx(var lpVersionInformation: TOSVersionInfoEx): BOOL; stdcall; external kernel32 name 'GetVersionExA'; {$endif UNICODE} function GetLocalTimeOffset: Integer; // not defined in oldest Delphi var tzi: TTimeZoneInformation; begin case GetTimeZoneInformation(tzi) of TIME_ZONE_ID_UNKNOWN: result := tzi.Bias; TIME_ZONE_ID_STANDARD: result := tzi.Bias + tzi.StandardBias; TIME_ZONE_ID_DAYLIGHT: result := tzi.Bias + tzi.DaylightBias; else result := 0; end; end; function UUID_CACHE: TFileName; begin // where to cache our computed UUID as a local file result := GetSystemPath(spCommonData) + 'synopse.uuid'; end; procedure InitializeSpecificUnit; var h: THandle; IsWow64Process: function(Handle: THandle; var Res: BOOL): BOOL; stdcall; GetNativeSystemInfo: procedure(var SystemInfo: TSystemInfo); stdcall; GetLogicalProcessorInformation: function( var Info: TSystemLogicalProcessorInformation; Len: PDWord): BOOL; stdcall; wine_get_version: function: PAnsiChar; stdcall; mem: TMemoryStatusEx; Res: BOOL; P: pointer; Vers: TWindowsVersion; cpu, manuf, prod, prodver: RawUtf8; reg: TWinRegistry; proc: array of TSystemLogicalProcessorInformation; i: integer; siz: DWord; begin {$ifdef ASMX86} {$ifndef HASNOSSE2} if not (cfSSE2 in CpuFeatures) then begin // avoid illegal opcode in MoveFast() and SynLZ functions {$ifdef ISDELPHI} // FPC_X86 already redirect to FastCode RTL Move() RedirectCode(@MoveFast, @System.Move); {$endif ISDELPHI} RedirectCode(@SynLZcompress1, @SynLZcompress1Pas); RedirectCode(@SynLZdecompress1, @SynLZdecompress1Pas); ConsoleWrite('WARNING: too old CPU - recompile with HASNOSSE2', ccLightRed); // note: FillCharFast is handled by mormot.core.base via ERMSB // and Byte/Word/IntegerScanIndex() are likely to GPF at runtime end; {$endif HASNOSSE2} {$endif ASMX86} // late-binding of newest Windows APIs h := GetModuleHandle(kernel32); GetTickCount64 := GetProcAddress(h, 'GetTickCount64'); if not Assigned(GetTickCount64) then // WinXP+ GetTickCount64 := @GetTickCount64ForXP; GetSystemTimePreciseAsFileTime := GetProcAddress(h, 'GetSystemTimePreciseAsFileTime'); if not Assigned(GetSystemTimePreciseAsFileTime) then // Win8+ GetSystemTimePreciseAsFileTime := @GetSystemTimeAsFileTime; {$ifdef WITH_VECTOREXCEPT} AddVectoredExceptionHandler := GetProcAddress(h, 'AddVectoredExceptionHandler'); {$endif WITH_VECTOREXCEPT} QueryPerformanceFrequency(PInt64(@_QueryPerformanceFrequency)^); if _QueryPerformanceFrequency = 0 then raise Exception.Create('QueryPerformanceFrequency=0'); // paranoid _QueryPerformanceFrequencyPer10 := _QueryPerformanceFrequency = 10000000; IsWow64Process := GetProcAddress(h, 'IsWow64Process'); Res := false; IsWow64 := Assigned(IsWow64Process) and IsWow64Process(GetCurrentProcess, Res) and Res; if IsWow64 then // see http://msdn.microsoft.com/en-us/library/ms724381(v=VS.85).aspx GetNativeSystemInfo := GetProcAddress(h, 'GetNativeSystemInfo') else @GetNativeSystemInfo := nil; GetSystemTimes := GetProcAddress(h, 'GetSystemTimes'); GetProcessTimes := GetProcAddress(h, 'GetProcessTimes'); QueryFullProcessImageNameW := GetProcAddress(h, 'QueryFullProcessImageNameW'); GetLogicalProcessorInformation := GetProcAddress(h, 'GetLogicalProcessorInformation'); InitializeSRWLock := GetProcAddress(h, 'InitializeSRWLock'); AcquireSRWLockExclusive := GetProcAddress(h, 'AcquireSRWLockExclusive'); ReleaseSRWLockExclusive := GetProcAddress(h, 'ReleaseSRWLockExclusive'); if not Assigned(InitializeSRWLock) or not Assigned(AcquireSRWLockExclusive) or not Assigned(ReleaseSRWLockExclusive) then begin // SRW was introduced with Vista: on XP, fallback to our TLightLock InitializeSRWLock := @InitializeSRWLockForXP; AcquireSRWLockExclusive := @AcquireSRWLockExclusiveForXP; ReleaseSRWLockExclusive := @ReleaseSRWLockExclusiveForXP; end; // retrieve system information TimeZoneLocalBias := -GetLocalTimeOffset; FillcharFast(SystemInfo, SizeOf(SystemInfo), 0); if Assigned(GetNativeSystemInfo) then GetNativeSystemInfo(SystemInfo) else Windows.GetSystemInfo(SystemInfo); GetMem(P, 10); // ensure that using MIN_PTR_VALUE won't break anything if (PtrUInt(P) > MIN_PTR_VALUE) and (PtrUInt(SystemInfo.lpMinimumApplicationAddress) <= MIN_PTR_VALUE) then PtrUInt(SystemInfo.lpMinimumApplicationAddress) := MIN_PTR_VALUE; Freemem(P); FillCharFast(mem, SizeOf(mem), 0); mem.dwLength := SizeOf(mem); if GlobalMemoryStatusEx(mem) then SystemMemorySize := mem.ullTotalPhys; OSVersionInfo.dwOSVersionInfoSize := SizeOf(OSVersionInfo); GetVersionEx(OSVersionInfo); Vers := wUnknown; with OSVersionInfo do // see https://msdn.microsoft.com/en-us/library/windows/desktop/ms724833 case dwMajorVersion of 5: case dwMinorVersion of 0: Vers := w2000; 1: Vers := wXP; 2: if (wProductType = VER_NT_WORKSTATION) and (SystemInfo.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_AMD64) then Vers := wXP_64 else if GetSystemMetrics(SM_SERVERR2) = 0 then Vers := wServer2003 else Vers := wServer2003_R2; end; 6: case dwMinorVersion of 0: Vers := wVista; 1: Vers := wSeven; 2: Vers := wEight; 3: Vers := wEightOne; 4: Vers := wTen; end; 10: Vers := wTen; end; if Vers >= wVista then begin // see https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions if OSVersionInfo.wProductType <> VER_NT_WORKSTATION then begin // Server edition inc(Vers, 2); // e.g. wEight -> wServer2012 if Vers = wServer2016 then // we identify only LTSC server versions if OSVersionInfo.dwBuildNumber >= 17763 then if OSVersionInfo.dwBuildNumber >= 20285 then // released as 20348 Vers := wServer2022_64 else Vers := wServer2019_64; end else if (Vers = wTen) and (OSVersionInfo.dwBuildNumber >= 22000) then // Windows 11 has always 22000.### Vers := wEleven; if (SystemInfo.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_AMD64) and (Vers < wServer2019_64) then inc(Vers); // e.g. wEight -> wEight64 end; OSVersion := Vers; OpenProcessAccess := PROCESS_QUERY_LIMITED_INFORMATION; if Vers < wVista then OpenProcessAccess := PROCESS_QUERY_INFORMATION or PROCESS_VM_READ; OSVersion32.os := osWindows; OSVersion32.win := Vers; OSVersion32.winbuild := OSVersionInfo.dwBuildNumber; h := GetModuleHandle(ntdll); if h > 0 then begin wine_get_version := GetProcAddress(h, 'wine_get_version'); if Assigned(wine_get_version) then begin OSVersionInfoEx := wine_get_version; OSVersionInfoEx := TrimU('Wine ' + TrimU(OSVersionInfoEx)); end; NtQueryInformationProcess := GetProcAddress(h, 'NtQueryInformationProcess'); RtlInitUnicodeString := GetProcAddress(h, 'RtlInitUnicodeString'); NtOpenSection := GetProcAddress(h, 'NtOpenSection'); NtMapViewOfSection := GetProcAddress(h, 'NtMapViewOfSection'); NtUnmapViewOfSection := GetProcAddress(h, 'NtUnmapViewOfSection'); end; // retrieve Software/Hardware information from Registry if reg.ReadOpen(wrLocalMachine, 'Software\Microsoft\Windows NT\CurrentVersion') then begin WindowsUbr := reg.ReadDword('UBR'); WindowsProductName := reg.ReadString('ProductName'); WindowsDisplayVersion := reg.ReadString('DisplayVersion'); end; with OSVersionInfo do begin _fmt('Windows %s (%d.%d.%d)', [WINDOWS_NAME[Vers], dwMajorVersion, dwMinorVersion, dwBuildNumber], OSVersionText); if wServicePackMajor <> 0 then insert(_fmt('SP%d ', [wServicePackMajor]), OSVersionText, PosExChar('(', OSVersionText)); end; if WindowsUbr <> 0 then insert(_fmt('.%d', [WindowsUbr]), OSVersionText, length(OSVersionText)); if WindowsDisplayVersion <> '' then insert(WindowsDisplayVersion + ' ', OSVersionText, PosExChar('(', OSVersionText)); if OSVersionInfoEx <> '' then OSVersionText := OSVersionText + ' - ' + OSVersionInfoEx; if reg.ReadOpen(wrLocalMachine, 'Hardware\Description\System\CentralProcessor\0', true) then begin cpu := reg.ReadString('ProcessorNameString'); if cpu = '' then cpu := reg.ReadString('Identifier'); end; if reg.ReadOpen(wrLocalMachine, 'Hardware\Description\System\BIOS', true) then begin manuf := reg.ReadString('SystemManufacturer'); if manuf <> '' then manuf := manuf + ' '; prod := reg.ReadString('SystemProductName'); prodver := reg.ReadString('SystemVersion'); if prodver = '' then prodver := reg.ReadString('BIOSVersion'); end; if ({%H-}prod = '') or ({%H-}prodver = '') then begin if reg.ReadOpen(wrLocalMachine, 'Hardware\Description\System', true) then begin if prod = '' then prod := reg.ReadString('SystemBiosVersion'); if prodver = '' then prodver := reg.ReadString('VideoBiosVersion'); end; end; reg.Close; BiosInfoText := manuf{%H-} + prod; if prodver <> '' then BiosInfoText := BiosInfoText + ' ' + prodver; if {%H-}cpu = '' then cpu := RawUtf8(GetEnvironmentVariable('PROCESSOR_IDENTIFIER')); if Assigned(GetLogicalProcessorInformation) then begin SetLength(proc, 1024); siz := SizeOf(proc[0]) * length(proc); if GetLogicalProcessorInformation(proc[0], @siz) then begin for i := 0 to (siz div SizeOf(proc[0])) - 1 do with proc[i] do case Relationship of RelationProcessorPackage: // physical processor socket AddPtrUInt(TPtrUIntDynArray(CpuSocketsMask), CpuSockets, ProcessorMask); RelationCache: // raw cache information if Cache.CacheType in [CacheUnified, CacheData] then if (Cache.Level >= low(CpuCache)) and (Cache.Level <= high(CpuCache)) then with CpuCache[Cache.Level] do if (Count = 0) or (Cache.CacheType <> CacheUnified) then begin inc(Count); Size := Cache.Size; LineSize := Cache.LineSize; end; end; for i := high(CpuCache) downto low(CpuCache) do begin CpuCacheSize := CpuCache[i].Size; if CpuCacheSize <> 0 then // append the biggest level Cache size begin cpu := _fmt('%s [%s]', [cpu, _oskb(CpuCacheSize)]); break; end; end; for i := low(CpuCache) to high(CpuCache) do with CpuCache[i] do if Count <> 0 then if Count = 1 then CpuCacheText := _fmt('%s L%d=%s ', [CpuCacheText, i, _oskb(Size)]) else CpuCacheText := _fmt('%s L%d=%d*%s ', [CpuCacheText, i, Count, _oskb(Size)]); TrimSelf(CpuCacheText); end; end; if CpuSockets = 0 then CpuSockets := 1; // e.g. on XP prior to SP3 _fmt('%d x %s (' + CPU_ARCH_TEXT + ')', [SystemInfo.dwNumberOfProcessors, cpu], CpuInfoText); // writeln(CpuInfoText); writeln(CpuCacheText); end; procedure FinalizeSpecificUnit; begin if CryptoApi.Handle <> 0 then Windows.FreeLibrary(CryptoApi.Handle); if CoInitCounter <> 0 then ConsoleWrite('Missing CoUninit (e.g. TOleDBConnection.Destroy call)'); end;