mirror of
https://github.com/danieleteti/delphimvcframework.git
synced 2024-11-15 15:55:54 +01:00
10858 lines
380 KiB
ObjectPascal
10858 lines
380 KiB
ObjectPascal
/// Framework Core Low-Level Wrappers to the Operating-System API
|
|
// - this unit is a part of the Open Source Synopse mORMot framework 2,
|
|
// licensed under a MPL/GPL/LGPL three license - see LICENSE.md
|
|
unit mormot.core.os;
|
|
|
|
{
|
|
*****************************************************************************
|
|
|
|
Cross-platform functions shared by all framework units
|
|
- Some Cross-System Type and Constant Definitions
|
|
- Gather Operating System Information
|
|
- Operating System Specific Types (e.g. TWinRegistry)
|
|
- Unicode, Time, File, Console, Library process
|
|
- Cross-Platform Charset and CodePage Support
|
|
- Per Class Properties O(1) Lookup via vmtAutoTable Slot (e.g. for RTTI cache)
|
|
- TSynLocker/TSynLocked and Low-Level Threading Features
|
|
- Unix Daemon and Windows Service Support
|
|
|
|
Aim of this unit is to centralize most used OS-specific API calls, like a
|
|
SysUtils unit on steroids, to avoid $ifdef/$endif in "uses" clauses.
|
|
In practice, no "Windows", nor "Linux/Posix" reference should be needed in
|
|
regular units, once mormot.core.os is included. :)
|
|
This unit only refers to mormot.core.base so can be used almost stand-alone.
|
|
|
|
*****************************************************************************
|
|
}
|
|
|
|
interface
|
|
|
|
{$I mormot.defines.inc}
|
|
|
|
uses
|
|
{$ifdef OSWINDOWS}
|
|
Windows, // needed here e.g. for redefinition/redirection of standard types
|
|
Messages,
|
|
{$endif OSWINDOWS}
|
|
classes,
|
|
contnrs,
|
|
types,
|
|
sysutils,
|
|
mormot.core.base;
|
|
|
|
|
|
{ ****************** Some Cross-System Type and Constant Definitions }
|
|
|
|
const
|
|
{$ifdef OSWINDOWS}
|
|
/// operating-system dependent Line Feed characters (#13#10 or #10)
|
|
CRLF = #13#10;
|
|
/// operating-system dependent wildchar to match all files in a folder
|
|
FILES_ALL = '*.*';
|
|
/// operating-system dependent "inverted" delimiter for NormalizeFileName()
|
|
InvertedPathDelim = '/';
|
|
/// operating-system dependent boolean if paths are case-insensitive
|
|
PathCaseInsensitive = true;
|
|
{$else}
|
|
/// operating-system dependent Line Feed characters
|
|
CRLF = #10;
|
|
/// operating-system dependent wildchar to match all files in a folder
|
|
FILES_ALL = '*';
|
|
/// operating-system dependent "inverted" delimiter for NormalizeFileName()
|
|
InvertedPathDelim = '\';
|
|
/// operating-system dependent boolean if paths are case-insensitive
|
|
PathCaseInsensitive = false;
|
|
{$endif OSWINDOWS}
|
|
|
|
/// human-friendly alias to open a file for exclusive writing
|
|
fmShareRead = fmShareDenyWrite;
|
|
/// human-friendly alias to open a file for exclusive reading
|
|
fmShareWrite = fmShareDenyRead;
|
|
/// human-friendly alias to open a file with no read/write exclusion
|
|
fmShareReadWrite = fmShareDenyNone;
|
|
|
|
/// a convenient constant to open a file for reading without exclusion
|
|
fmOpenReadShared = fmOpenRead or fmShareReadWrite;
|
|
|
|
/// a convenient constant to open a file for writing without exclusion
|
|
fmOpenWriteShared = fmOpenReadWrite or fmShareReadWrite;
|
|
|
|
/// a convenient constant to create a file without exclusion
|
|
fmCreateShared = fmCreate or fmShareReadWrite;
|
|
|
|
/// a convenient array constant to open a file for writing without exclusion
|
|
fmCreateOrRewrite: array[{rewrite=}boolean] of cardinal = (
|
|
fmCreateShared,
|
|
fmOpenWriteShared);
|
|
|
|
const
|
|
/// void HTTP Status Code (not a standard value, for internal use only)
|
|
HTTP_NONE = 0;
|
|
/// HTTP Status Code for "Continue"
|
|
HTTP_CONTINUE = 100;
|
|
/// HTTP Status Code for "Switching Protocols"
|
|
HTTP_SWITCHINGPROTOCOLS = 101;
|
|
/// HTTP Status Code for "Success"
|
|
HTTP_SUCCESS = 200;
|
|
/// HTTP Status Code for "Created"
|
|
HTTP_CREATED = 201;
|
|
/// HTTP Status Code for "Accepted"
|
|
HTTP_ACCEPTED = 202;
|
|
/// HTTP Status Code for "Non-Authoritative Information"
|
|
HTTP_NONAUTHORIZEDINFO = 203;
|
|
/// HTTP Status Code for "No Content"
|
|
HTTP_NOCONTENT = 204;
|
|
/// HTTP Status Code for "Reset Content"
|
|
HTTP_RESETCONTENT = 205;
|
|
/// HTTP Status Code for "Partial Content"
|
|
HTTP_PARTIALCONTENT = 206;
|
|
/// HTTP Status Code for "Multiple Choices"
|
|
HTTP_MULTIPLECHOICES = 300;
|
|
/// HTTP Status Code for "Moved Permanently"
|
|
HTTP_MOVEDPERMANENTLY = 301;
|
|
/// HTTP Status Code for "Found"
|
|
HTTP_FOUND = 302;
|
|
/// HTTP Status Code for "See Other"
|
|
HTTP_SEEOTHER = 303;
|
|
/// HTTP Status Code for "Not Modified"
|
|
HTTP_NOTMODIFIED = 304;
|
|
/// HTTP Status Code for "Use Proxy"
|
|
HTTP_USEPROXY = 305;
|
|
/// HTTP Status Code for "Temporary Redirect"
|
|
HTTP_TEMPORARYREDIRECT = 307;
|
|
/// HTTP Status Code for "Permanent Redirect"
|
|
HTTP_PERMANENTREDIRECT = 308;
|
|
/// HTTP Status Code for "Bad Request"
|
|
HTTP_BADREQUEST = 400;
|
|
/// HTTP Status Code for "Unauthorized"
|
|
HTTP_UNAUTHORIZED = 401;
|
|
/// HTTP Status Code for "Forbidden"
|
|
HTTP_FORBIDDEN = 403;
|
|
/// HTTP Status Code for "Not Found"
|
|
HTTP_NOTFOUND = 404;
|
|
// HTTP Status Code for "Method Not Allowed"
|
|
HTTP_NOTALLOWED = 405;
|
|
// HTTP Status Code for "Not Acceptable"
|
|
HTTP_NOTACCEPTABLE = 406;
|
|
// HTTP Status Code for "Proxy Authentication Required"
|
|
HTTP_PROXYAUTHREQUIRED = 407;
|
|
/// HTTP Status Code for "Request Time-out"
|
|
HTTP_TIMEOUT = 408;
|
|
/// HTTP Status Code for "Conflict"
|
|
HTTP_CONFLICT = 409;
|
|
/// HTTP Status Code for "Payload Too Large"
|
|
HTTP_PAYLOADTOOLARGE = 413;
|
|
/// HTTP Status Code for "Range Not Satisfiable"
|
|
HTTP_RANGENOTSATISFIABLE = 416;
|
|
/// HTTP Status Code for "I'm a teapot"
|
|
HTTP_TEAPOT = 418;
|
|
/// HTTP Status Code for "Internal Server Error"
|
|
HTTP_SERVERERROR = 500;
|
|
/// HTTP Status Code for "Not Implemented"
|
|
HTTP_NOTIMPLEMENTED = 501;
|
|
/// HTTP Status Code for "Bad Gateway"
|
|
HTTP_BADGATEWAY = 502;
|
|
/// HTTP Status Code for "Service Unavailable"
|
|
HTTP_UNAVAILABLE = 503;
|
|
/// HTTP Status Code for "Gateway Timeout"
|
|
HTTP_GATEWAYTIMEOUT = 504;
|
|
/// HTTP Status Code for "HTTP Version Not Supported"
|
|
HTTP_HTTPVERSIONNONSUPPORTED = 505;
|
|
|
|
/// clearly wrong response code, used by THttpServerRequest.SetAsyncResponse
|
|
// - for internal THttpAsyncServer asynchronous process
|
|
HTTP_ASYNCRESPONSE = 777;
|
|
|
|
/// the successful HTTP response codes after a GET request
|
|
HTTP_GET_OK = [HTTP_SUCCESS, HTTP_NOCONTENT, HTTP_PARTIALCONTENT];
|
|
|
|
/// retrieve the HTTP reason text from its integer code as PRawUtf8
|
|
// - e.g. StatusCodeToText(200)^='OK'
|
|
// - as defined in http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
|
// - returns the generic 'Invalid Request' for any unknown Code
|
|
function StatusCodeToText(Code: cardinal): PRawUtf8;
|
|
|
|
/// retrieve the HTTP reason text from its integer code
|
|
// - as defined in http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
|
procedure StatusCodeToReason(Code: cardinal; var Reason: RawUtf8);
|
|
|
|
/// convert any HTTP_* constant to an integer status code and its English text
|
|
// - returns e.g. '200 OK' or '404 Not Found', calling StatusCodeToText()
|
|
function StatusCodeToShort(Code: cardinal): TShort47;
|
|
|
|
/// returns true for successful HTTP status codes, i.e. in 200..399 range
|
|
// - will map mainly SUCCESS (200), CREATED (201), NOCONTENT (204),
|
|
// PARTIALCONTENT (206), NOTMODIFIED (304) or TEMPORARYREDIRECT (307) codes
|
|
// - any HTTP status not part of this range will be identified as erronous
|
|
// request in the internal server statistics
|
|
function StatusCodeIsSuccess(Code: integer): boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// check the supplied HTTP header to not contain more than one EOL
|
|
// - to avoid unexpected HTTP body injection, e.g. from unsafe business code
|
|
function IsInvalidHttpHeader(head: PUtf8Char; headlen: PtrInt): boolean;
|
|
|
|
|
|
const
|
|
/// HTTP header name for the content type, as defined in the corresponding RFC
|
|
HEADER_CONTENT_TYPE = 'Content-Type: ';
|
|
|
|
/// HTTP header name for the content type, in upper case
|
|
// - as defined in the corresponding RFC
|
|
// - could be used e.g. with IdemPChar() to retrieve the Content-Type value
|
|
HEADER_CONTENT_TYPE_UPPER = 'CONTENT-TYPE: ';
|
|
|
|
/// HTTP header name for the client IP, in upper case
|
|
// - as defined in our HTTP server classes
|
|
// - could be used e.g. with IdemPChar() to retrieve the remote IP address
|
|
HEADER_REMOTEIP_UPPER = 'REMOTEIP: ';
|
|
|
|
/// HTTP header name for the authorization token, in upper case
|
|
// - could be used e.g. with IdemPChar() to retrieve a JWT value
|
|
// - will detect header computed e.g. by motmot.net.http's
|
|
// AuthorizationBearer()
|
|
HEADER_BEARER_UPPER = 'AUTHORIZATION: BEARER ';
|
|
|
|
/// MIME content type used for JSON communication (as used by the Microsoft
|
|
// WCF framework and the YUI framework)
|
|
// - no 'charset=UTF-8' encoding is necessary, as by specified by RFC 7159
|
|
JSON_CONTENT_TYPE = 'application/json';
|
|
|
|
/// HTTP header for MIME content type used for plain JSON
|
|
// - i.e. 'Content-Type: application/json'
|
|
JSON_CONTENT_TYPE_HEADER = HEADER_CONTENT_TYPE + JSON_CONTENT_TYPE;
|
|
|
|
/// MIME content type used for plain JSON, in upper case
|
|
// - could be used e.g. with IdemPChar() to retrieve the Content-Type value
|
|
JSON_CONTENT_TYPE_UPPER = 'APPLICATION/JSON';
|
|
|
|
/// HTTP header for MIME content type used for plain JSON, in upper case
|
|
// - could be used e.g. with IdemPChar() to retrieve the Content-Type value
|
|
JSON_CONTENT_TYPE_HEADER_UPPER =
|
|
HEADER_CONTENT_TYPE_UPPER + JSON_CONTENT_TYPE_UPPER;
|
|
|
|
/// MIME content type used for plain UTF-8 text
|
|
TEXT_CONTENT_TYPE = 'text/plain; charset=UTF-8';
|
|
|
|
/// HTTP header for MIME content type used for plain UTF-8 text
|
|
TEXT_CONTENT_TYPE_HEADER = HEADER_CONTENT_TYPE + TEXT_CONTENT_TYPE;
|
|
|
|
/// MIME content type used for UTF-8 encoded HTML
|
|
HTML_CONTENT_TYPE = 'text/html; charset=UTF-8';
|
|
|
|
/// HTTP header for MIME content type used for UTF-8 encoded HTML
|
|
HTML_CONTENT_TYPE_HEADER = HEADER_CONTENT_TYPE + HTML_CONTENT_TYPE;
|
|
|
|
/// MIME content type used for UTF-8 encoded XML
|
|
XML_CONTENT_TYPE = 'text/xml';
|
|
|
|
/// HTTP header for MIME content type used for UTF-8 encoded XML
|
|
XML_CONTENT_TYPE_HEADER = HEADER_CONTENT_TYPE + XML_CONTENT_TYPE;
|
|
|
|
/// MIME content type used for raw binary data
|
|
BINARY_CONTENT_TYPE = 'application/octet-stream';
|
|
|
|
/// MIME content type used for raw binary data, in upper case
|
|
BINARY_CONTENT_TYPE_UPPER = 'APPLICATION/OCTET-STREAM';
|
|
|
|
/// HTTP header for MIME content type used for raw binary data
|
|
BINARY_CONTENT_TYPE_HEADER = HEADER_CONTENT_TYPE + BINARY_CONTENT_TYPE;
|
|
|
|
/// MIME content type used for a JPEG picture
|
|
JPEG_CONTENT_TYPE = 'image/jpeg';
|
|
|
|
/// a IdemPPChar() compatible array of textual MIME content types
|
|
// - as used e.g. by IsHtmlContentTypeTextual()
|
|
CONTENT_TYPE_TEXTUAL: array[0..7] of PAnsiChar = (
|
|
JSON_CONTENT_TYPE_UPPER,
|
|
'TEXT/',
|
|
'APPLICATION/XML',
|
|
'APPLICATION/JSON',
|
|
'APPLICATION/JAVASCRIPT',
|
|
'APPLICATION/X-JAVASCRIPT',
|
|
'IMAGE/SVG+XML',
|
|
nil);
|
|
|
|
/// internal HTTP content-type for efficient static file sending
|
|
// - detected e.g. by http.sys' THttpApiServer.Request or via the NGINX
|
|
// X-Accel-Redirect header's THttpServer.Process (see
|
|
// THttpServer.NginxSendFileFrom) for direct sending with no local bufferring
|
|
// - the OutCustomHeader should contain the proper 'Content-type: ....'
|
|
// corresponding to the file (e.g. by calling GetMimeContentType() function)
|
|
STATICFILE_CONTENT_TYPE = '!STATICFILE';
|
|
|
|
/// internal HTTP content-type Header for efficient static file sending
|
|
STATICFILE_CONTENT_TYPE_HEADER =
|
|
HEADER_CONTENT_TYPE + STATICFILE_CONTENT_TYPE;
|
|
|
|
/// uppercase version of HTTP header for static file content serving
|
|
STATICFILE_CONTENT_TYPE_HEADER_UPPPER =
|
|
HEADER_CONTENT_TYPE_UPPER + STATICFILE_CONTENT_TYPE;
|
|
|
|
/// used to notify e.g. the THttpServerRequest not to wait for any response
|
|
// from the client
|
|
// - is not to be used in normal HTTP process, but may be used e.g. by
|
|
// TWebSocketProtocolRest.ProcessFrame() to avoid to wait for an incoming
|
|
// response from the other endpoint
|
|
NORESPONSE_CONTENT_TYPE = '!NORESPONSE';
|
|
|
|
/// HTTP body following RFC 2324 standard e.g. for banned IP
|
|
HTTP_BANIP_RESPONSE: string[201] =
|
|
'HTTP/1.0 418 I''m a teapot'#13#10 +
|
|
'Content-Length: 125'#13#10 +
|
|
'Content-Type: text/plain'#13#10#13#10 +
|
|
'Server refuses to brew coffee because it is currently a teapot.'#13#10 +
|
|
'Do not mess with it and retry from this IP in a few seconds.';
|
|
|
|
/// JSON compatible representation of a boolean value, i.e. 'false' and 'true'
|
|
// - can be used e.g. in logs, or anything accepting a ShortString
|
|
BOOL_STR: array[boolean] of string[7] = (
|
|
'false', 'true');
|
|
|
|
/// the JavaScript-like values of non-number IEEE constants
|
|
// - as recognized by FloatToShortNan, and used by TTextWriter.Add()
|
|
// when serializing such single/double/extended floating-point values
|
|
JSON_NAN: array[TFloatNan] of string[11] = (
|
|
'0', '"NaN"', '"Infinity"', '"-Infinity"');
|
|
|
|
var
|
|
/// MIME content type used for JSON communication
|
|
// - i.e. 'application/json' as stated by datatracker.ietf.org/doc/html/rfc7159
|
|
// - this global will be initialized with JSON_CONTENT_TYPE constant, to
|
|
// avoid a memory allocation each time it is assigned to a variable
|
|
JSON_CONTENT_TYPE_VAR: RawUtf8;
|
|
|
|
/// HTTP header for MIME content type used for plain JSON
|
|
// - this global will be initialized with JSON_CONTENT_TYPE_HEADER constant,
|
|
// to avoid a memory allocation each time it is assigned to a variable
|
|
JSON_CONTENT_TYPE_HEADER_VAR: RawUtf8;
|
|
|
|
/// can be used to avoid a memory allocation for res := 'null'
|
|
// - this global will be initialized with 'null' constant, to
|
|
// avoid a memory allocation each time it is assigned to a variable
|
|
NULL_STR_VAR: RawUtf8;
|
|
|
|
/// JSON compatible representation of a boolean value, i.e. 'false' and 'true'
|
|
// - can be used when a RawUtf8 string is expected
|
|
// - this global will be initialized with 'false' and 'true' constants, to
|
|
// avoid a memory allocation each time it is assigned to a variable
|
|
BOOL_UTF8: array[boolean] of RawUtf8;
|
|
|
|
|
|
type
|
|
/// Security IDentifier (SID) Authority, encoded as 48-bit binary
|
|
TSidAuth = array[0..5] of byte;
|
|
PSidAuth = ^TSidAuth;
|
|
|
|
/// Security IDentifier (SID) binary format, as retrieved e.g. by Windows API
|
|
// - this definition is not detailed on oldest Delphi, and not available on
|
|
// POSIX, whereas it makes sense to also have it, e.g. for server process
|
|
TSid = packed record
|
|
Revision: byte;
|
|
SubAuthorityCount: byte;
|
|
IdentifierAuthority: TSidAuth;
|
|
SubAuthority: array[byte] of cardinal;
|
|
end;
|
|
PSid = ^TSid;
|
|
PSids = array of PSid;
|
|
|
|
/// define a list of well-known Security IDentifier (SID) groups
|
|
// - for instance, wksBuiltinAdministrators is set for local administrators
|
|
// - warning: does not exactly match winnt.h WELL_KNOWN_SID_TYPE enumeration
|
|
TWellKnownSid = (
|
|
wksNull,
|
|
wksWorld,
|
|
wksLocal,
|
|
wksConsoleLogon,
|
|
wksCreatorOwner,
|
|
wksCreatorGroup,
|
|
wksCreatorOwnerServer,
|
|
wksCreatorGroupServer,
|
|
wksIntegrityUntrusted,
|
|
wksIntegrityLow,
|
|
wksIntegrityMedium,
|
|
wksIntegrityMediumPlus,
|
|
wksIntegrityHigh,
|
|
wksIntegritySystem,
|
|
wksIntegrityProtectedProcess,
|
|
wksIntegritySecureProcess,
|
|
wksAuthenticationAuthorityAsserted,
|
|
wksAuthenticationServiceAsserted,
|
|
wksAuthenticationFreshKeyAuth,
|
|
wksAuthenticationKeyTrust,
|
|
wksAuthenticationKeyPropertyMfa,
|
|
wksAuthenticationKeyPropertyAttestation,
|
|
wksNtAuthority,
|
|
wksDialup,
|
|
wksNetwork,
|
|
wksBatch,
|
|
wksInteractive,
|
|
wksService,
|
|
wksAnonymous,
|
|
wksProxy,
|
|
wksEnterpriseControllers,
|
|
wksSelf,
|
|
wksAuthenticatedUser,
|
|
wksRestrictedCode,
|
|
wksTerminalServer,
|
|
wksRemoteLogonId,
|
|
wksThisOrganisation,
|
|
wksIisUser,
|
|
wksLocalSystem,
|
|
wksLocalService,
|
|
wksNetworkService,
|
|
wksLocalAccount,
|
|
wksLocalAccountAndAdministrator,
|
|
wksBuiltinDomain,
|
|
wksBuiltinAdministrators,
|
|
wksBuiltinUsers,
|
|
wksBuiltinGuests,
|
|
wksBuiltinPowerUsers,
|
|
wksBuiltinAccountOperators,
|
|
wksBuiltinSystemOperators,
|
|
wksBuiltinPrintOperators,
|
|
wksBuiltinBackupOperators,
|
|
wksBuiltinReplicator,
|
|
wksBuiltinRasServers,
|
|
wksBuiltinPreWindows2000CompatibleAccess,
|
|
wksBuiltinRemoteDesktopUsers,
|
|
wksBuiltinNetworkConfigurationOperators,
|
|
wksBuiltinIncomingForestTrustBuilders,
|
|
wksBuiltinPerfMonitoringUsers,
|
|
wksBuiltinPerfLoggingUsers,
|
|
wksBuiltinAuthorizationAccess,
|
|
wksBuiltinTerminalServerLicenseServers,
|
|
wksBuiltinDcomUsers,
|
|
wksBuiltinIUsers,
|
|
wksBuiltinCryptoOperators,
|
|
wksBuiltinUnknown,
|
|
wksBuiltinCacheablePrincipalsGroups,
|
|
wksBuiltinNonCacheablePrincipalsGroups,
|
|
wksBuiltinEventLogReadersGroup,
|
|
wksBuiltinCertSvcDComAccessGroup,
|
|
wksBuiltinRdsRemoteAccessServers,
|
|
wksBuiltinRdsEndpointServers,
|
|
wksBuiltinRdsManagementServers,
|
|
wksBuiltinHyperVAdmins,
|
|
wksBuiltinAccessControlAssistanceOperators,
|
|
wksBuiltinRemoteManagementUsers,
|
|
wksBuiltinDefaultSystemManagedGroup,
|
|
wksBuiltinStorageReplicaAdmins,
|
|
wksBuiltinDeviceOwners,
|
|
wksCapabilityInternetClient,
|
|
wksCapabilityInternetClientServer,
|
|
wksCapabilityPrivateNetworkClientServer,
|
|
wksCapabilityPicturesLibrary,
|
|
wksCapabilityVideosLibrary,
|
|
wksCapabilityMusicLibrary,
|
|
wksCapabilityDocumentsLibrary,
|
|
wksCapabilityEnterpriseAuthentication,
|
|
wksCapabilitySharedUserCertificates,
|
|
wksCapabilityRemovableStorage,
|
|
wksCapabilityAppointments,
|
|
wksCapabilityContacts,
|
|
wksBuiltinAnyPackage,
|
|
wksBuiltinAnyRestrictedPackage,
|
|
wksNtlmAuthentication,
|
|
wksSChannelAuthentication,
|
|
wksDigestAuthentication);
|
|
|
|
/// define a set of well-known SID
|
|
TWellKnownSids = set of TWellKnownSid;
|
|
|
|
/// custom binary buffer type used as convenient Windows SID storage
|
|
RawSid = type RawByteString;
|
|
|
|
/// a dynamic array of binary SID storage buffers
|
|
RawSidDynArray = array of RawSid;
|
|
|
|
|
|
/// a wrapper around MemCmp() on two Security IDentifier binary buffers
|
|
// - will first compare by length, then by content
|
|
function SidCompare(a, b: PSid): integer;
|
|
|
|
/// compute the actual binary length of a Security IDentifier buffer, in bytes
|
|
function SidLength(sid: PSid): PtrInt;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
|
|
/// allocate a RawSid instance from a PSid raw handler
|
|
procedure ToRawSid(sid: PSid; out result: RawSid);
|
|
|
|
/// check if a RawSid binary buffer has the expected length of a valid SID
|
|
function IsValidRawSid(const sid: RawSid): boolean;
|
|
|
|
/// search within SID dynamic array for a given SID
|
|
function HasSid(const sids: PSids; sid: PSid): boolean;
|
|
|
|
/// search within SID dynamic array for a given dynamic array of SID buffers
|
|
function HasAnySid(const sids: PSids; const sid: RawSidDynArray): boolean;
|
|
|
|
/// append a SID buffer pointer to a dynamic array of SID buffers
|
|
procedure AddRawSid(var sids: RawSidDynArray; sid: PSid);
|
|
|
|
/// convert a Security IDentifier as text, following the standard representation
|
|
procedure SidToTextShort(sid: PSid; var result: shortstring);
|
|
|
|
/// convert a Security IDentifier as text, following the standard representation
|
|
function SidToText(sid: PSid): RawUtf8;
|
|
|
|
/// convert several Security IDentifier as text dynamic array
|
|
function SidsToText(sids: PSids): TRawUtf8DynArray;
|
|
|
|
/// convert a Security IDentifier as text, following the standard representation
|
|
function RawSidToText(const sid: RawSid): RawUtf8;
|
|
|
|
/// parse a Security IDentifier text, following the standard representation
|
|
// - won't support hexadecimal IdentifierAuthority, i.e. S-1-0x######-....
|
|
function TextToSid(P: PUtf8Char; out sid: TSid): boolean;
|
|
|
|
/// parse a Security IDentifier text, following the standard representation
|
|
function TextToRawSid(const text: RawUtf8): RawSid; overload;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
|
|
/// parse a Security IDentifier text, following the standard representation
|
|
function TextToRawSid(const text: RawUtf8; out sid: RawSid): boolean; overload;
|
|
|
|
/// returns a Security IDentifier of a well-known SID as binary
|
|
// - is using an internal cache for the returned RawSid instances
|
|
function KnownRawSid(wks: TWellKnownSid): RawSid;
|
|
|
|
/// returns a Security IDentifier of a well-known SID as standard text
|
|
// - e.g. wksBuiltinAdministrators as 'S-1-5-32-544'
|
|
function KnownSidToText(wks: TWellKnownSid): PShortString;
|
|
|
|
/// recognize most well-known SID from a Security IDentifier binary buffer
|
|
// - returns wksNull if the supplied buffer was not recognized
|
|
function SidToKnown(sid: PSid): TWellKnownSid; overload;
|
|
|
|
/// recognize most well-known SID from a Security IDentifier standard text
|
|
// - returns wksNull if the supplied text was not recognized
|
|
function SidToKnown(const text: RawUtf8): TWellKnownSid; overload;
|
|
|
|
/// recognize some well-known SIDs from the supplied SID dynamic array
|
|
function SidToKnownGroups(const sids: PSids): TWellKnownSids;
|
|
|
|
|
|
{ ****************** Gather Operating System Information }
|
|
|
|
type
|
|
/// Exception types raised by this mormot.core.os unit
|
|
EOSException = class(ExceptionWithProps);
|
|
|
|
/// the known operating systems
|
|
// - it will also recognize most Linux distributions
|
|
TOperatingSystem = (
|
|
osUnknown,
|
|
osWindows,
|
|
osLinux,
|
|
osOSX,
|
|
osBSD,
|
|
osPOSIX,
|
|
osArch,
|
|
osAurox,
|
|
osDebian,
|
|
osFedora,
|
|
osGentoo,
|
|
osKnoppix,
|
|
osMint,
|
|
osMandrake,
|
|
osMandriva,
|
|
osNovell,
|
|
osUbuntu,
|
|
osSlackware,
|
|
osSolaris,
|
|
osSuse,
|
|
osSynology,
|
|
osTrustix,
|
|
osClear,
|
|
osUnited,
|
|
osRedHat,
|
|
osLFS,
|
|
osOracle,
|
|
osMageia,
|
|
osCentOS,
|
|
osCloud,
|
|
osXen,
|
|
osAmazon,
|
|
osCoreOS,
|
|
osAlpine,
|
|
osAndroid);
|
|
|
|
/// the recognized Windows versions
|
|
// - defined even outside OSWINDOWS to access e.g. from monitoring tools
|
|
TWindowsVersion = (
|
|
wUnknown,
|
|
w2000,
|
|
wXP,
|
|
wXP_64,
|
|
wServer2003,
|
|
wServer2003_R2,
|
|
wVista,
|
|
wVista_64,
|
|
wServer2008,
|
|
wServer2008_64,
|
|
wSeven,
|
|
wSeven_64,
|
|
wServer2008_R2,
|
|
wServer2008_R2_64,
|
|
wEight,
|
|
wEight_64,
|
|
wServer2012,
|
|
wServer2012_64,
|
|
wEightOne,
|
|
wEightOne_64,
|
|
wServer2012R2,
|
|
wServer2012R2_64,
|
|
wTen,
|
|
wTen_64,
|
|
wServer2016,
|
|
wServer2016_64,
|
|
wEleven,
|
|
wEleven_64,
|
|
wServer2019_64,
|
|
wServer2022_64);
|
|
|
|
/// the running Operating System, encoded as a 32-bit integer
|
|
TOperatingSystemVersion = packed record
|
|
case os: TOperatingSystem of
|
|
osUnknown: (
|
|
b: array[0..2] of byte);
|
|
osWindows: (
|
|
win: TWindowsVersion;
|
|
winbuild: word);
|
|
osLinux: (
|
|
utsrelease: array[0..2] of byte);
|
|
end;
|
|
|
|
const
|
|
/// the recognized MacOS versions, as plain text
|
|
// - indexed from OSVersion32.utsrelease[2] kernel revision
|
|
MACOS_NAME: array[8 .. 24] of RawUtf8 = (
|
|
'10.4 Tiger',
|
|
'10.5 Leopard',
|
|
'10.6 Snow Leopard',
|
|
'10.7 Lion',
|
|
'10.8 Mountain Lion',
|
|
'10.9 Mavericks',
|
|
'10.10 Yosemite',
|
|
'10.11 El Capitan',
|
|
'10.12 Sierra',
|
|
'10.13 High Sierra',
|
|
'10.14 Mojave',
|
|
'10.15 Catalina',
|
|
'11 Big Sur',
|
|
'12 Monterey',
|
|
'13 Ventura',
|
|
'14 Sonoma',
|
|
'15 Glow'); // use known internal codename for upcoming version
|
|
|
|
/// the recognized Windows versions, as plain text
|
|
// - defined even outside OSWINDOWS to allow process e.g. from monitoring tools
|
|
WINDOWS_NAME: array[TWindowsVersion] of RawUtf8 = (
|
|
'',
|
|
'2000',
|
|
'XP',
|
|
'XP 64bit',
|
|
'Server 2003',
|
|
'Server 2003 R2',
|
|
'Vista',
|
|
'Vista 64bit',
|
|
'Server 2008',
|
|
'Server 2008 64bit',
|
|
'7',
|
|
'7 64bit',
|
|
'Server 2008 R2',
|
|
'Server 2008 R2 64bit',
|
|
'8',
|
|
'8 64bit',
|
|
'Server 2012',
|
|
'Server 2012 64bit',
|
|
'8.1',
|
|
'8.1 64bit',
|
|
'Server 2012 R2',
|
|
'Server 2012 R2 64bit',
|
|
'10',
|
|
'10 64bit',
|
|
'Server 2016',
|
|
'Server 2016 64bit',
|
|
'11',
|
|
'11 64bit',
|
|
'Server 2019 64bit',
|
|
'Server 2022 64bit');
|
|
|
|
/// the recognized Windows versions which are 32-bit
|
|
WINDOWS_32 = [
|
|
w2000,
|
|
wXP,
|
|
wServer2003,
|
|
wServer2003_R2,
|
|
wVista,
|
|
wServer2008,
|
|
wSeven,
|
|
wServer2008_R2,
|
|
wEight,
|
|
wServer2012,
|
|
wEightOne,
|
|
wServer2012R2,
|
|
wTen,
|
|
wServer2016,
|
|
wEleven];
|
|
|
|
/// translate one operating system (and distribution) into a its common name
|
|
OS_NAME: array[TOperatingSystem] of RawUtf8 = (
|
|
'Unknown',
|
|
'Windows',
|
|
'Linux',
|
|
'OSX',
|
|
'BSD',
|
|
'POSIX',
|
|
'Arch',
|
|
'Aurox',
|
|
'Debian',
|
|
'Fedora',
|
|
'Gentoo',
|
|
'Knoppix',
|
|
'Mint',
|
|
'Mandrake',
|
|
'Mandriva',
|
|
'Novell',
|
|
'Ubuntu',
|
|
'Slackware',
|
|
'Solaris',
|
|
'Suse',
|
|
'Synology',
|
|
'Trustix',
|
|
'Clear',
|
|
'United',
|
|
'RedHat',
|
|
'LFS',
|
|
'Oracle',
|
|
'Mageia',
|
|
'CentOS',
|
|
'Cloud',
|
|
'Xen',
|
|
'Amazon',
|
|
'CoreOS',
|
|
'Alpine',
|
|
'Android');
|
|
|
|
/// translate one operating system (and distribution) into a single character
|
|
// - may be used internally e.g. for a HTTP User-Agent header, as with
|
|
// TFileVersion.UserAgent and UserAgentParse()
|
|
OS_INITIAL: array[TOperatingSystem] of AnsiChar = (
|
|
'?', // Unknown
|
|
'W', // Windows
|
|
'L', // Linux
|
|
'X', // OSX
|
|
'B', // BSD
|
|
'P', // POSIX
|
|
'A', // Arch
|
|
'a', // Aurox
|
|
'D', // Debian
|
|
'F', // Fedora
|
|
'G', // Gentoo
|
|
'K', // Knoppix
|
|
'M', // Mint
|
|
'm', // Mandrake
|
|
'n', // Mandriva
|
|
'N', // Novell
|
|
'U', // Ubuntu
|
|
'S', // Slackware
|
|
's', // Solaris
|
|
'u', // Suse
|
|
'Y', // Synology
|
|
'T', // Trustix
|
|
'C', // Clear
|
|
't', // United
|
|
'R', // RedHat
|
|
'l', // LFS
|
|
'O', // Oracle
|
|
'G', // Mageia
|
|
'c', // CentOS
|
|
'd', // Cloud
|
|
'x', // Xen
|
|
'Z', // Amazon
|
|
'r', // CoreOS
|
|
'p', // Alpine
|
|
'J' // Android (J=JVM)
|
|
);
|
|
|
|
/// the operating systems items which actually have a Linux kernel
|
|
OS_LINUX = [
|
|
osLinux,
|
|
osArch .. osAndroid];
|
|
|
|
/// the compiler family used
|
|
COMP_TEXT = {$ifdef FPC}'Fpc'{$else}'Delphi'{$endif};
|
|
|
|
/// the target Operating System used for compilation, as short text
|
|
OS_TEXT =
|
|
{$ifdef OSWINDOWS}
|
|
'Win';
|
|
{$else} {$ifdef OSDARWIN}
|
|
'OSX';
|
|
{$else}{$ifdef OSBSD}
|
|
'BSD';
|
|
{$else} {$ifdef OSANDROID}
|
|
'Android';
|
|
{$else} {$ifdef OSLINUX}
|
|
'Linux';
|
|
{$else}
|
|
'Posix';
|
|
{$endif OSLINUX}
|
|
{$endif OSANDROID}
|
|
{$endif OSBSD}
|
|
{$endif OSDARWIN}
|
|
{$endif OSWINDOWS}
|
|
|
|
/// the CPU architecture used for compilation
|
|
CPU_ARCH_TEXT =
|
|
{$ifdef CPUX86}
|
|
'x86'
|
|
{$else} {$ifdef CPUX64}
|
|
'x64'
|
|
{$else} {$ifdef CPUARM}
|
|
'arm' +
|
|
{$else} {$ifdef CPUAARCH64}
|
|
'aarch' +
|
|
{$ifdef CPUPOWERPC}
|
|
'ppc' +
|
|
{$else} {$ifdef CPUSPARC}
|
|
'sparc' +
|
|
{$endif CPUSPARC}
|
|
{$endif CPUPOWERPC}
|
|
{$endif CPUARM}
|
|
{$endif CPUAARCH64}
|
|
{$ifdef CPU32}
|
|
'32'
|
|
{$else}
|
|
'64'
|
|
{$endif CPU32}
|
|
{$endif CPUX64}
|
|
{$endif CPUX86};
|
|
|
|
var
|
|
/// the target Operating System used for compilation, as TOperatingSystem
|
|
// - a specific Linux distribution may be detected instead of plain osLinux
|
|
OS_KIND: TOperatingSystem =
|
|
{$ifdef OSWINDOWS}
|
|
osWindows
|
|
{$else} {$ifdef OSDARWIN}
|
|
osOSX
|
|
{$else} {$ifdef OSBSD}
|
|
osBSD
|
|
{$else} {$ifdef OSANDROID}
|
|
osAndroid
|
|
{$else} {$ifdef OSLINUX}
|
|
osLinux
|
|
{$else}
|
|
osPOSIX
|
|
{$endif OSLINUX}
|
|
{$endif OSANDROID}
|
|
{$endif OSBSD}
|
|
{$endif OSDARWIN}
|
|
{$endif OSWINDOWS};
|
|
|
|
/// the current Operating System version, as retrieved for the current process
|
|
// - contains e.g. 'Windows Seven 64 SP1 (6.1.7601)' or 'Windows XP SP3 (5.1.2600)' or
|
|
// 'Windows 10 64bit 22H2 (10.0.19045.4046)' or 'macOS 13 Ventura (Darwin 22.3.0)' or
|
|
// 'Ubuntu 16.04.5 LTS - Linux 3.13.0 110 generic#157 Ubuntu SMP Mon Feb 20 11:55:25 UTC 2017'
|
|
OSVersionText: RawUtf8;
|
|
/// some addition system information as text, e.g. 'Wine 1.1.5'
|
|
// - also always appended to OSVersionText high-level description
|
|
// - use if PosEx('Wine', OSVersionInfoEx) > 0 then to check for Wine presence
|
|
OSVersionInfoEx: RawUtf8;
|
|
/// the current Operating System version, as retrieved for the current process
|
|
// and computed by ToTextOS(OSVersionInt32)
|
|
// - contains e.g. 'Windows Vista' or 'Ubuntu Linux 5.4.0' or
|
|
// 'macOS 13 Ventura 22.3.0'
|
|
OSVersionShort: RawUtf8;
|
|
|
|
{$ifdef OSWINDOWS}
|
|
/// on Windows, the Update Build Revision as shown with the "ver/winver" command
|
|
// - to track the current update state of the system
|
|
WindowsUbr: integer;
|
|
/// on Windows, the ready-to-be-displayed text version of the system
|
|
// - e.g. 'Windows 10 Entreprise N'
|
|
WindowsProductName: RawUtf8;
|
|
/// on Windows, the ready-to-be-displayed text version of the system
|
|
// - e.g. '22H2'
|
|
WindowsDisplayVersion: RawUtf8;
|
|
{$endif OSWINDOWS}
|
|
|
|
/// some textual information about the current CPU and its known cache
|
|
// - contains e.g. '4 x Intel(R) Core(TM) i5-7300U CPU @ 2.60GHz [3MB]'
|
|
CpuInfoText: RawUtf8;
|
|
/// the on-chip cache size, in bytes, as returned by the OS
|
|
// - retrieved from /proc/cpuinfo "cache size" entry (L3 cache) on Linux or
|
|
// CpuCache[3/4].Size (from GetLogicalProcessorInformation) on Windows
|
|
CpuCacheSize: cardinal;
|
|
/// the available cache information as returned by the OS
|
|
// - e.g. 'L1=2*32KB L2=256KB L3=3MB' on Windows or '3072 KB' on Linux
|
|
CpuCacheText: RawUtf8;
|
|
/// some textual information about the current computer hardware, from BIOS
|
|
// - contains e.g. 'LENOVO 20HES23B0U ThinkPad T470'
|
|
BiosInfoText: RawUtf8;
|
|
|
|
/// how many hardware CPU sockets are defined on this system
|
|
// - i.e. the number of physical CPU slots, not the number of logical CPU
|
|
// cores as returned by SystemInfo.dwNumberOfProcessors
|
|
// - as used e.g. by SetThreadAffinity()
|
|
CpuSockets: integer;
|
|
|
|
/// Level 1 to 4 CPU caches as returned by GetLogicalProcessorInformation
|
|
// - yes, Intel introduced a Level 4 cache (eDRAM) with some Haswell/Iris CPUs
|
|
// - this information is not retrieved on all Linux / POSIX systems yet
|
|
// - only Unified or Data caches are include (not Instruction or Trace)
|
|
// - note: some CPU - like the Apple M1 - have 128 bytes of LineSize
|
|
CpuCache: array[1..4] of record
|
|
Count, Size, LineSize: cardinal;
|
|
end;
|
|
|
|
{$ifdef OSLINUXANDROID}
|
|
/// contains the Flags: or Features: value of Linux /proc/cpuinfo
|
|
CpuInfoFeatures: RawUtf8;
|
|
{$endif OSLINUXANDROID}
|
|
|
|
/// the running Operating System
|
|
OSVersion32: TOperatingSystemVersion;
|
|
/// the running Operating System, encoded as a 32-bit integer
|
|
OSVersionInt32: integer absolute OSVersion32;
|
|
|
|
/// convert an Operating System type into its text representation
|
|
// - returns e.g. 'Windows Vista' or 'Ubuntu' or 'macOS 13 Ventura'
|
|
function ToText(const osv: TOperatingSystemVersion): RawUtf8; overload;
|
|
|
|
/// convert an Operating System type into its one-word text representation
|
|
// - returns e.g. 'Vista' or 'Ubuntu' or 'OSX'
|
|
function ToTextShort(const osv: TOperatingSystemVersion): RawUtf8;
|
|
|
|
/// convert a 32-bit Operating System type into its full text representation
|
|
// - including the kernel revision (not the distribution version) on POSIX systems
|
|
// - returns e.g. 'Windows Vista', 'Windows 11 64-bit 22000' or 'Ubuntu Linux 5.4.0'
|
|
function ToTextOS(osint32: integer): RawUtf8;
|
|
|
|
/// check if the current OS (i.e. OS_KIND value) match a description
|
|
// - will handle osPosix and osLinux as generic detection of those systems
|
|
// - osUnknown will always return true
|
|
function MatchOS(os: TOperatingSystem): boolean;
|
|
|
|
type
|
|
/// the recognized ARM/AARCH64 CPU types
|
|
// - https://github.com/karelzak/util-linux/blob/master/sys-utils/lscpu-arm.c
|
|
// - is defined on all platforms for cross-system use
|
|
TArmCpuType = (
|
|
actUnknown,
|
|
actARM810,
|
|
actARM920,
|
|
actARM922,
|
|
actARM926,
|
|
actARM940,
|
|
actARM946,
|
|
actARM966,
|
|
actARM1020,
|
|
actARM1022,
|
|
actARM1026,
|
|
actARM11MPCore,
|
|
actARM1136,
|
|
actARM1156,
|
|
actARM1176,
|
|
actCortexA5,
|
|
actCortexA7,
|
|
actCortexA8,
|
|
actCortexA9,
|
|
actCortexA12,
|
|
actCortexA15,
|
|
actCortexA17,
|
|
actCortexR4,
|
|
actCortexR5,
|
|
actCortexR7,
|
|
actCortexR8,
|
|
actCortexM0,
|
|
actCortexM1,
|
|
actCortexM3,
|
|
actCortexM4,
|
|
actCortexM7,
|
|
actCortexM0P,
|
|
actCortexA32,
|
|
actCortexA53,
|
|
actCortexA35,
|
|
actCortexA55,
|
|
actCortexA65,
|
|
actCortexA57,
|
|
actCortexA72,
|
|
actCortexA73,
|
|
actCortexA75,
|
|
actCortexA76,
|
|
actNeoverseN1,
|
|
actCortexA77,
|
|
actCortexA76AE,
|
|
actCortexR52,
|
|
actCortexM23,
|
|
actCortexM33,
|
|
actNeoverseV1,
|
|
actCortexA78,
|
|
actCortexA78AE,
|
|
actCortexX1,
|
|
actCortex510,
|
|
actCortex710,
|
|
actCortexX2,
|
|
actNeoverseN2,
|
|
actNeoverseE1,
|
|
actCortexA78C,
|
|
actCortexX1C,
|
|
actCortexA715,
|
|
actCortexX3,
|
|
actNeoverseV2,
|
|
actCortexA520,
|
|
actCortexA720,
|
|
actCortexX4,
|
|
actNeoverseV3,
|
|
actNeoverseN3);
|
|
/// a set of recognized ARM/AARCH64 CPU types
|
|
TArmCpuTypes = set of TArmCpuType;
|
|
|
|
/// the recognized ARM/AARCH64 CPU hardware implementers
|
|
// - https://github.com/karelzak/util-linux/blob/master/sys-utils/lscpu-arm.c
|
|
TArmCpuImplementer = (
|
|
aciUnknown,
|
|
aciARM,
|
|
aciBroadcom,
|
|
aciCavium,
|
|
aciDEC,
|
|
aciFUJITSU,
|
|
aciHiSilicon,
|
|
aciInfineon,
|
|
aciMotorola,
|
|
aciNVIDIA,
|
|
aciAPM,
|
|
aciQualcomm,
|
|
aciSamsung,
|
|
aciMarvell,
|
|
aciApple,
|
|
aciFaraday,
|
|
aciIntel,
|
|
aciMicrosoft,
|
|
aciPhytium,
|
|
aciAmpere);
|
|
/// a set of recognized ARM/AARCH64 CPU hardware implementers
|
|
TArmCpuImplementers = set of TArmCpuImplementer;
|
|
|
|
/// recognize a given ARM/AARCH64 CPU from its 12-bit hardware ID
|
|
function ArmCpuType(id: word): TArmCpuType;
|
|
|
|
/// recognize a given ARM/AARCH64 CPU type name from its 12-bit hardware ID
|
|
function ArmCpuTypeName(act: TArmCpuType; id: word): RawUtf8;
|
|
|
|
/// recognize a given ARM/AARCH64 CPU implementer from its 8-bit hardware ID
|
|
function ArmCpuImplementer(id: byte): TArmCpuImplementer;
|
|
|
|
/// recognize a given ARM/AARCH64 CPU implementer name from its 8-bit hardware ID
|
|
function ArmCpuImplementerName(aci: TArmCpuImplementer; id: word): RawUtf8;
|
|
|
|
|
|
const
|
|
/// contains the Delphi/FPC Compiler Version as text
|
|
// - e.g. 'Delphi 10.3 Rio', 'Delphi 2010' or 'Free Pascal 3.3.1'
|
|
COMPILER_VERSION: RawUtf8 =
|
|
{$ifdef FPC}
|
|
'Free Pascal'
|
|
{$ifdef VER2_6_4} + ' 2.6.4'{$endif}
|
|
{$ifdef VER3_0} + ' 3.0'
|
|
{$ifdef VER3_0_4} + '.4' {$else}
|
|
{$ifdef VER3_0_2} + '.2' {$endif}
|
|
{$endif VER3_0_4}
|
|
{$endif VER3_0}
|
|
{$ifdef VER3_1} + ' 3.1'
|
|
{$ifdef VER3_1_1} + '.1' {$endif}
|
|
{$endif VER3_1}
|
|
{$ifdef VER3_2} + ' 3.2'
|
|
{$ifdef VER3_2_4} + '.4' {$else}
|
|
{$ifdef VER3_2_3} + '.3' {$else}
|
|
{$ifdef VER3_2_2} + '.2' {$endif}
|
|
{$endif VER3_2_3}
|
|
{$endif VER3_2_4}
|
|
{$endif VER3_2}
|
|
{$ifdef VER3_3} + ' 3.3'
|
|
{$ifdef VER3_3_1} + '.1' {$endif}
|
|
{$endif VER3_3}
|
|
{$ifdef VER3_4} + ' 3.4' {$endif}
|
|
{$else}
|
|
'Delphi'
|
|
{$if defined(VER140)} + ' 6'
|
|
{$elseif defined(VER150)} + ' 7'
|
|
{$elseif defined(VER160)} + ' 8'
|
|
{$elseif defined(VER170)} + ' 2005'
|
|
{$elseif defined(VER185)} + ' 2007'
|
|
{$elseif defined(VER180)} + ' 2006'
|
|
{$elseif defined(VER200)} + ' 2009'
|
|
{$elseif defined(VER210)} + ' 2010'
|
|
{$elseif defined(VER220)} + ' XE'
|
|
{$elseif defined(VER230)} + ' XE2'
|
|
{$elseif defined(VER240)} + ' XE3'
|
|
{$elseif defined(VER250)} + ' XE4'
|
|
{$elseif defined(VER260)} + ' XE5'
|
|
{$elseif defined(VER265)} + ' AppMethod 1'
|
|
{$elseif defined(VER270)} + ' XE6'
|
|
{$elseif defined(VER280)} + ' XE7'
|
|
{$elseif defined(VER290)} + ' XE8'
|
|
{$elseif defined(VER300)} + ' 10 Seattle'
|
|
{$elseif defined(VER310)} + ' 10.1 Berlin'
|
|
{$elseif defined(VER320)} + ' 10.2 Tokyo'
|
|
{$elseif defined(VER330)} + ' 10.3 Rio'
|
|
{$elseif defined(VER340)} + ' 10.4 Sydney'
|
|
{$elseif defined(VER350)} + ' 11'
|
|
{$if declared(RTLVersion113)} + '.3' {$else}
|
|
{$if declared(RTLVersion112)} + '.2' {$else}
|
|
{$if declared(RTLVersion111)} + '.1' {$ifend} {$ifend} {$ifend}
|
|
+ ' Alexandria'
|
|
{$elseif defined(VER360)} + ' 12'
|
|
{$if declared(RTLVersion122)} + '.2' {$else}
|
|
{$if declared(RTLVersion121)} + '.1' {$ifend} {$ifend}
|
|
+ ' Athens'
|
|
{$elseif defined(VER370)} + ' 13 Next'
|
|
{$ifend}
|
|
{$endif FPC}
|
|
{$ifdef CPU64} + ' 64 bit' {$else} + ' 32 bit' {$endif};
|
|
|
|
{$ifndef PUREMORMOT2}
|
|
const
|
|
HTTP_RESP_STATICFILE = STATICFILE_CONTENT_TYPE;
|
|
|
|
/// deprecated function: use COMPILER_VERSION constant instead
|
|
function GetDelphiCompilerVersion: RawUtf8; deprecated;
|
|
{$endif PUREMORMOT2}
|
|
|
|
{$ifdef OSWINDOWS}
|
|
|
|
{$ifdef UNICODE}
|
|
|
|
const
|
|
/// a global constant to be appended for Windows Ansi or Wide API names
|
|
// - match the Wide API on Delphi, since String=UnicodeString
|
|
_AW = 'W';
|
|
|
|
{$else}
|
|
|
|
const
|
|
/// a global constant to be appended for Windows Ansi or Wide API names
|
|
// - match the Ansi API on FPC or oldest Delphi, where String=AnsiString
|
|
_AW = 'A';
|
|
|
|
type
|
|
/// low-level API structure, not defined in old Delphi versions
|
|
TOSVersionInfoEx = record
|
|
dwOSVersionInfoSize: DWORD;
|
|
dwMajorVersion: DWORD;
|
|
dwMinorVersion: DWORD;
|
|
dwBuildNumber: DWORD;
|
|
dwPlatformId: DWORD;
|
|
szCSDVersion: array[0..127] of char;
|
|
wServicePackMajor: WORD;
|
|
wServicePackMinor: WORD;
|
|
wSuiteMask: WORD;
|
|
wProductType: BYTE;
|
|
wReserved: BYTE;
|
|
end;
|
|
|
|
{$endif UNICODE}
|
|
|
|
var
|
|
/// is set to TRUE if the current process is a 32-bit image running under WOW64
|
|
// - WOW64 is the x86 emulator that allows 32-bit Windows-based applications
|
|
// to run seamlessly on 64-bit Windows
|
|
// - equals always FALSE if the current executable is a 64-bit image
|
|
IsWow64: boolean;
|
|
/// the current System information, as retrieved for the current process
|
|
// - under a WOW64 process, it will use the GetNativeSystemInfo() new API
|
|
// to retrieve the real top-most system information
|
|
// - note that the lpMinimumApplicationAddress field is replaced by a
|
|
// more optimistic/realistic value ($100000 instead of default $10000)
|
|
// - under BSD/Linux, only contain dwPageSize and dwNumberOfProcessors fields
|
|
SystemInfo: TSystemInfo;
|
|
/// low-level Operating System information, as retrieved for the current process
|
|
OSVersionInfo: TOSVersionInfoEx;
|
|
/// the current Windows edition, as retrieved for the current process
|
|
OSVersion: TWindowsVersion;
|
|
|
|
{$else OSWINDOWS}
|
|
|
|
var
|
|
/// emulate only some used fields of Windows' TSystemInfo
|
|
SystemInfo: record
|
|
/// retrieved from libc's getpagesize() - is expected to not be 0
|
|
dwPageSize: cardinal;
|
|
/// the number of available logical CPUs
|
|
// - retrieved from HW_NCPU (BSD) or /proc/cpuinfo (Linux)
|
|
// - see CpuSockets for the number of physical CPU sockets
|
|
dwNumberOfProcessors: cardinal;
|
|
/// meaningful system information, as returned by fpuname()
|
|
uts: record
|
|
sysname, release, version: RawUtf8;
|
|
end;
|
|
/// Linux Distribution release name, retrieved from /etc/*-release
|
|
release: RawUtf8;
|
|
end;
|
|
|
|
{$endif OSWINDOWS}
|
|
|
|
/// the number of physical memory bytes available to the process
|
|
// - equals TMemoryInfo.memtotal as retrieved from GetMemoryInfo() at startup
|
|
SystemMemorySize: PtrUInt;
|
|
|
|
{$M+} // to have existing RTTI for published properties
|
|
|
|
type
|
|
/// used to retrieve version information from any EXE
|
|
// - under Linux, all version numbers are set to 0 by default, unless
|
|
// you define the FPCUSEVERSIONINFO conditional and information is
|
|
// extracted from executable resources
|
|
// - for the main executable, do not create once instance of this class, but
|
|
// call GetExecutableVersion / SetExecutableVersion and access the Executable
|
|
// global variable
|
|
TFileVersion = class
|
|
protected
|
|
fDetailed: string;
|
|
fFileName: TFileName;
|
|
fBuildDateTime: TDateTime;
|
|
fVersionInfo, fUserAgent: RawUtf8;
|
|
// change the version - returns true if supplied values are actually new
|
|
function SetVersion(aMajor, aMinor, aRelease, aBuild: integer): boolean;
|
|
public
|
|
/// executable major version number
|
|
Major: integer;
|
|
/// executable minor version number
|
|
Minor: integer;
|
|
/// executable release version number
|
|
Release: integer;
|
|
/// executable release build number
|
|
Build: integer;
|
|
/// build year of this exe file
|
|
BuildYear: word;
|
|
/// version info of the exe file as '3.1'
|
|
// - return "string" type, i.e. UnicodeString for Delphi 2009+
|
|
Main: string;
|
|
/// associated CompanyName string version resource
|
|
CompanyName: RawUtf8;
|
|
/// associated FileDescription string version resource
|
|
FileDescription: RawUtf8;
|
|
/// associated FileVersion string version resource
|
|
FileVersion: RawUtf8;
|
|
/// associated InternalName string version resource
|
|
InternalName: RawUtf8;
|
|
/// associated LegalCopyright string version resource
|
|
LegalCopyright: RawUtf8;
|
|
/// associated OriginalFileName string version resource
|
|
OriginalFilename: RawUtf8;
|
|
/// associated ProductName string version resource
|
|
ProductName: RawUtf8;
|
|
/// associated ProductVersion string version resource
|
|
ProductVersion: RawUtf8;
|
|
/// associated Comments string version resource
|
|
Comments: RawUtf8;
|
|
/// associated Language Translation string version resource
|
|
LanguageInfo: RawUtf8;
|
|
/// initialize the version information, with optional custom values
|
|
// - will set the version numbers, and get BuildDateTime/BuildYear
|
|
// - call RetrieveInformationFromFileName to parse its internal resources
|
|
// - for the main executable, do not use this constructor, but call
|
|
// GetExecutableVersion / SetExecutableVersion and access the Executable
|
|
// global variable
|
|
constructor Create(const aFileName: TFileName; aMajor: integer = 0;
|
|
aMinor: integer = 0; aRelease: integer = 0; aBuild: integer = 0);
|
|
/// open and extract file information from the executable FileName
|
|
// - note that resource extraction is not available on POSIX, unless the
|
|
// FPCUSEVERSIONINFO conditional has been specified in the project options
|
|
function RetrieveInformationFromFileName: boolean;
|
|
/// retrieve the version as a 32-bit integer with Major.Minor.Release
|
|
// - following Major shl 16+Minor shl 8+Release bit pattern
|
|
function Version32: integer;
|
|
/// build date and time of this exe file, as plain text
|
|
function BuildDateTimeString: string;
|
|
/// version info of the exe file as '3.1.0.123' or ''
|
|
// - this method returns '' if Detailed is '0.0.0.0'
|
|
function DetailedOrVoid: string;
|
|
/// returns the version information of this exe file as text
|
|
// - includes FileName (without path), Detailed and BuildDateTime properties
|
|
// - e.g. 'myprogram.exe 3.1.0.123 (2016-06-14 19:07:55)'
|
|
function VersionInfo: RawUtf8;
|
|
/// returns a ready-to-use User-Agent header with exe name, version and OS
|
|
// - e.g. 'myprogram/3.1.0.123W32' for myprogram running on Win32
|
|
// - here OS_INITIAL[] character is used to identify the OS, with '32'
|
|
// appended on Win32 only (e.g. 'myprogram/3.1.0.2W', is for Win64)
|
|
// - use UserAgentParse() to decode this text into meaningful information
|
|
function UserAgent: RawUtf8;
|
|
/// returns the version information of a specified exe file as text
|
|
// - includes FileName (without path), Detailed and BuildDateTime properties
|
|
// - e.g. 'myprogram.exe 3.1.0.123 2016-06-14 19:07:55'
|
|
class function GetVersionInfo(const aFileName: TFileName): RawUtf8;
|
|
published
|
|
/// version info of the exe file as '3.1.0.123'
|
|
// - return "string" type, i.e. UnicodeString for Delphi 2009+
|
|
// - under Linux, always return '0.0.0.0' if no custom version number
|
|
// has been defined
|
|
// - consider using DetailedOrVoid method if '0.0.0.0' is not expected
|
|
property Detailed: string
|
|
read fDetailed write fDetailed;
|
|
/// build date and time of this exe file
|
|
property BuildDateTime: TDateTime
|
|
read fBuildDateTime write fBuildDateTime;
|
|
end;
|
|
|
|
{$M-}
|
|
|
|
/// quickly parse the TFileVersion.UserAgent content
|
|
// - identify e.g. 'myprogram/3.1.0.2W' or 'myprogram/3.1.0.2W32' text
|
|
function UserAgentParse(const UserAgent: RawUtf8;
|
|
out ProgramName, ProgramVersion: RawUtf8;
|
|
out OS: TOperatingSystem): boolean;
|
|
|
|
type
|
|
/// the command line switches supported by TExecutableCommandLine
|
|
// - clkArg is for "exename arg1 arg2 arg3" main indexed arguments
|
|
// - clkOption is for "exename -o --opt1" boolean flags
|
|
// - clkParam is for "exename -n value --name value --name2=value2" pairs
|
|
TExecutableCommandLineKind = (
|
|
clkUndefined,
|
|
clkArg,
|
|
clkOption,
|
|
clkParam);
|
|
|
|
/// implements command-line arguments parsing e.g. for TExecutable.Command
|
|
// - call Arg() Options() and Get/Param() to define and retrieve the flags
|
|
// from their names and supply some description text, then call
|
|
// DetectUnknown and/or FullDescription to interact with the user
|
|
// - by default, will use -/-- switches on POSIX, and / on Windows
|
|
TExecutableCommandLine = class
|
|
protected
|
|
fNames: array[clkArg .. clkParam] of TRawUtf8DynArray;
|
|
fRawParams, fValues: TRawUtf8DynArray; // for clkParam
|
|
fDesc, fDescDetail: array[clkArg .. clkParam] of RawUtf8;
|
|
fRetrieved: array[clkArg .. clkParam] of TBooleanDynArray;
|
|
fDescArg: TRawUtf8DynArray;
|
|
fCaseSensitiveNames: boolean;
|
|
fSwitch: array[{long=}boolean] of RawUtf8;
|
|
fLineFeed, fExeDescription: RawUtf8;
|
|
procedure Describe(const v: array of RawUtf8;
|
|
k: TExecutableCommandLineKind; d, def: RawUtf8; argindex: integer);
|
|
function Find(const v: array of RawUtf8;
|
|
k: TExecutableCommandLineKind = clkUndefined; const d: RawUtf8 = '';
|
|
const def: RawUtf8 = ''; f: PtrInt = 0): PtrInt;
|
|
public
|
|
/// mark and describe an "arg" value by 0-based index in Args[]
|
|
function Arg(index: integer; const description: RawUtf8 = '';
|
|
optional: boolean = true): boolean; overload;
|
|
/// mark and describe a string/TFileName "arg" value by 0-based index in Args[]
|
|
function ArgString(index: integer; const description: RawUtf8 = '';
|
|
optional: boolean = true): string;
|
|
/// mark and describe an "arg" value in Args[]
|
|
function Arg(const name: RawUtf8;
|
|
const description: RawUtf8 = ''): boolean; overload;
|
|
/// mark and describe or or several "arg" value(s) in Args[]
|
|
function Arg(const name: array of RawUtf8;
|
|
const description: RawUtf8 = ''): boolean; overload;
|
|
/// search for "-optionname" switches in Options[]
|
|
function Option(const name: RawUtf8;
|
|
const description: RawUtf8 = ''): boolean; overload;
|
|
/// search for "-optionname" switches in Options[]
|
|
function Option(const name: array of RawUtf8;
|
|
const description: RawUtf8 = ''): boolean; overload;
|
|
/// search for "-parametername" and return its RawUtf8 "parametervalue"
|
|
function Get(const name: RawUtf8; out value: RawUtf8;
|
|
const description: RawUtf8 = ''; const default: RawUtf8 = ''): boolean; overload;
|
|
/// search for "-parametername" and return its RawUtf8 "parametervalue"
|
|
function Get(const name: array of RawUtf8; out value: RawUtf8;
|
|
const description: RawUtf8 = ''; const default: RawUtf8 = ''): boolean; overload;
|
|
/// search for "-parametername" and return all RawUtf8 "parametervalue" occurrences
|
|
function Get(const name: array of RawUtf8; out value: TRawUtf8DynArray;
|
|
const description: RawUtf8 = ''): boolean; overload;
|
|
/// search for "-parametername" and return its plain string "parametervalue"
|
|
function Get(const name: RawUtf8; out value: string;
|
|
const description: RawUtf8 = ''; const default: string = ''): boolean; overload;
|
|
/// search for "-parametername" and return all string "parametervalue" occurrences
|
|
function Get(const name: array of RawUtf8; out value: TStringDynArray;
|
|
const description: RawUtf8 = ''): boolean; overload;
|
|
/// search for "-parametername" and return its plain string "parametervalue"
|
|
function Get(const name: array of RawUtf8; out value: string;
|
|
const description: RawUtf8 = ''; const default: string = ''): boolean; overload;
|
|
/// search for "-parametername" and return all string "parametervalue" occurrences
|
|
function Get(const name: RawUtf8; out value: TStringDynArray;
|
|
const description: RawUtf8 = ''): boolean; overload;
|
|
/// search for "-parametername" and return its integer "parametervalue"
|
|
function Get(const name: RawUtf8; out value: integer;
|
|
const description: RawUtf8 = ''; default: integer = maxInt): boolean; overload;
|
|
/// search for "-parametername" and return its integer "parametervalue"
|
|
function Get(const name: array of RawUtf8; out value: integer;
|
|
const description: RawUtf8 = ''; default: integer = maxInt): boolean; overload;
|
|
/// search for "-parametername" and return its integer "parametervalue"
|
|
function Get(const name: RawUtf8; min, max: integer; out value: integer;
|
|
const description: RawUtf8 = ''; default: integer = maxInt): boolean; overload;
|
|
/// search for "-parametername" and return its integer "parametervalue"
|
|
function Get(const name: array of RawUtf8; min, max: integer;
|
|
out value: integer; const description: RawUtf8 = '';
|
|
default: integer = -1): boolean; overload;
|
|
/// search for "-parametername" parameter in Names[]
|
|
function Has(const name: RawUtf8): boolean; overload;
|
|
/// search for "-parametername" parameter in Names[]
|
|
function Has(const name: array of RawUtf8): boolean; overload;
|
|
/// search for "-parametername" and return '' or its RawUtf8 "parametervalue"
|
|
function Param(const name: RawUtf8; const description: RawUtf8 = '';
|
|
const default: RawUtf8 = ''): RawUtf8; overload;
|
|
/// search for "-parametername" and return '' or its string "parametervalue"
|
|
function ParamS(const name: array of RawUtf8; const description: RawUtf8 = '';
|
|
const default: string = ''): string;
|
|
/// search for "-parametername" and return '' or its RawUtf8 "parametervalue"
|
|
function Param(const name: array of RawUtf8; const description: RawUtf8 = '';
|
|
const default: RawUtf8 = ''): RawUtf8; overload;
|
|
/// search for "-parametername" and return its integer "parametervalue" or default
|
|
function Param(const name: RawUtf8; default: integer;
|
|
const description: RawUtf8 = ''): integer; overload;
|
|
/// search for "-parametername" and return its integer "parametervalue" or default
|
|
function Param(const name: array of RawUtf8; default: integer;
|
|
const description: RawUtf8 = ''): integer; overload;
|
|
/// generate the text from all Arg() Options() and Get/Param() descriptions
|
|
// and the supplied high-level description of the program
|
|
// - the parameter <name> would be extracted from any #word in the
|
|
// description text,
|
|
// - for instance:
|
|
// ! with Executable.Command do
|
|
// ! begin
|
|
// ! ExeDescription := 'An executable to test mORMot Execute.Command';
|
|
// ! verbose := Option(['v', 'verbose'], 'generate verbose output');
|
|
// ! Get(['t', 'threads'], threads, '#number of threads to run', 5);
|
|
// ! ConsoleWrite(FullDescription);
|
|
// ! end;
|
|
// will fill "verbose" and "threads" local variables, and output on Linux:
|
|
// $ An executable to test mORMot Execute.Command
|
|
// $
|
|
// $ Usage: mormot2tests [options] [params]
|
|
// $
|
|
// $ Options:
|
|
// $ -v, --verbose generate verbose output
|
|
// $
|
|
// $ Params:
|
|
// $ -t, --threads <number> (default 5)
|
|
// $ number of threads to run
|
|
function FullDescription(const customexedescription: RawUtf8 = '';
|
|
const exename: RawUtf8 = ''; const onlyusage: RawUtf8 = ''): RawUtf8;
|
|
/// check if the supplied parameters were all registered from previous
|
|
// Arg() Options() and Get/Param() calls
|
|
// - return '' if no unexpected flag has been supplied
|
|
// - return an error message like 'Unexpected --name option' otherwise
|
|
function DetectUnknown: RawUtf8;
|
|
/// call DetectUnknown and output any error message to the console
|
|
// - return false if the parameters are valid
|
|
// - otherwise, return true and caller should exit the process
|
|
function ConsoleWriteUnknown(const exedescription: RawUtf8 = ''): boolean;
|
|
/// define 'h help' and call ConsoleWriteUnknown()
|
|
// - caller should exit the process if this method returned true
|
|
function ConsoleHelpFailed(const exedescription: RawUtf8 = ''): boolean;
|
|
/// fill the stored arguments and options from executable parameters
|
|
// - called e.g. at unit inialization to set Executable.CommandLine variable
|
|
// - you can execute it again e.g. to customize the switches characters
|
|
function Parse(const DescriptionLineFeed: RawUtf8 = CRLF;
|
|
const ShortSwitch: RawUtf8 = {$ifdef OSWINDOWS} '/' {$else} '-' {$endif};
|
|
const LongSwitch: RawUtf8 = {$ifdef OSWINDOWS} '/' {$else} '--' {$endif}): boolean;
|
|
/// remove all recognized arguments and switches
|
|
procedure Clear;
|
|
/// internal method returning a switch text from its identifier
|
|
function SwitchAsText(const v: RawUtf8): RawUtf8;
|
|
/// the ParamStr(1..ParamCount) arguments as RawUtf8, excluding Options[]
|
|
// switches and Params[]/Values[] parameters
|
|
property Args: TRawUtf8DynArray
|
|
read fNames[clkArg];
|
|
/// the "-optionname" boolean switches as stored in ParamStr()
|
|
property Options: TRawUtf8DynArray
|
|
read fNames[clkOption];
|
|
/// the names of "-parametername parametervalue" as stored in ParamStr()
|
|
// - mapping the Values[] associated array
|
|
property Names: TRawUtf8DynArray
|
|
read fNames[clkParam];
|
|
/// the values of "-parametername parametervalue" as stored in ParamStr()
|
|
// - mapping the Names[] associated array
|
|
property Values: TRawUtf8DynArray
|
|
read fValues;
|
|
/// if search within Args[] Options[] or Names[] should be case-sensitive
|
|
property CaseSensitiveNames: boolean
|
|
read fCaseSensitiveNames write fCaseSensitiveNames;
|
|
/// set a text which describes the executable
|
|
// - as used by default by FullDescription() and ConsoleWriteUnknown()
|
|
property ExeDescription: RawUtf8
|
|
read fExeDescription write fExeDescription;
|
|
/// DescriptionLineFeed value from TExecutableCommandLine.Parse()
|
|
property LineFeed: RawUtf8
|
|
read fLineFeed write fLineFeed;
|
|
/// map ParamStr(1 .. ParamCount) values, encoded as RawUtf8
|
|
// - may be used e.g. for regression tests instead of ParamStr()
|
|
property RawParams: TRawUtf8DynArray
|
|
read fRawParams write fRawParams;
|
|
end;
|
|
|
|
/// stores some global information about the current executable and computer
|
|
// - as set at unit initialization into the Executable global variable
|
|
TExecutable = record
|
|
/// the main executable name, without any path nor extension
|
|
// - e.g. 'Test' for 'c:\pathto\Test.exe'
|
|
ProgramName: RawUtf8;
|
|
/// the main executable details, as used e.g. by TSynLog
|
|
// - e.g. 'C:\Dev\lib\SQLite3\exe\TestSQL3.exe 1.2.3.123 (2011-03-29 11:09:06)'
|
|
// - you should have called GetExecutableVersion or SetExecutableVersion
|
|
// to populate this field
|
|
ProgramFullSpec: RawUtf8;
|
|
/// the main executable file name (including full path)
|
|
// - same as paramstr(0)
|
|
ProgramFileName: TFileName;
|
|
/// the main executable full path (excluding .exe file name)
|
|
// - same as ExtractFilePath(paramstr(0))
|
|
ProgramFilePath: TFileName;
|
|
/// the full path of the running executable or library
|
|
// - for an executable, same as paramstr(0)
|
|
// - for a library, will contain the whole .dll file name
|
|
InstanceFileName: TFileName;
|
|
/// the current executable version
|
|
// - you should have called GetExecutableVersion or SetExecutableVersion
|
|
// to populate this field
|
|
Version: TFileVersion;
|
|
/// the current computer host name
|
|
Host: RawUtf8;
|
|
/// the current computer user name
|
|
User: RawUtf8;
|
|
/// some hash representation of this information
|
|
// - the very same executable on the very same computer run by the very
|
|
// same user will always have the same Hash value
|
|
// - is computed from the crc32c of this TExecutable fields: c0 from
|
|
// Version32, CpuFeatures and Host, c1 from User, c2 from ProgramFullSpec
|
|
// and c3 from InstanceFileName
|
|
// - may be used as an entropy seed, or to identify a process execution
|
|
Hash: THash128Rec;
|
|
/// the Command Line arguments, parsed during unit initialization
|
|
Command: TExecutableCommandLine;
|
|
end;
|
|
|
|
var
|
|
/// global information about the current executable and computer
|
|
// - this structure is initialized in this unit's initialization block below
|
|
// but you need to call GetExecutableVersion to initialize its Version fields
|
|
// from the executable version resource (if any)
|
|
// - you can call SetExecutableVersion() with a custom version, if needed
|
|
Executable: TExecutable;
|
|
|
|
{$ifndef PUREMORMOT2}
|
|
/// deprecated global: use Executable variable instead
|
|
ExeVersion: TExecutable absolute Executable;
|
|
{$endif PUREMORMOT2}
|
|
|
|
/// initialize Executable global variable, from the program version resources
|
|
// - is not retrieved at startup, unless this function is especially called
|
|
// - on POSIX, requires FPCUSEVERSIONINFO conditional to be set for the project
|
|
// - use SetExecutableVersion() if you want to force a custom version
|
|
// - is in fact just a wrapper around SetExecutableVersion(0, 0, 0, 0)
|
|
procedure GetExecutableVersion;
|
|
|
|
/// initialize Executable global variable with custom version numbers
|
|
// - GetExecutableVersion will retrieve version information from the
|
|
// executable itself (if it was included at build time and FPCUSEVERSIONINFO
|
|
// conditional was specified for the project)
|
|
// - but you can use this function to set any custom version number
|
|
procedure SetExecutableVersion(aMajor, aMinor, aRelease, aBuild: integer); overload;
|
|
|
|
/// initialize Executable global variable, supplying the version as text
|
|
// - e.g. SetExecutableVersion('7.1.2.512');
|
|
procedure SetExecutableVersion(const aVersionText: RawUtf8); overload;
|
|
|
|
/// return a function/method location according to the supplied code address
|
|
// - returns the address as hexadecimal by default, e.g. '004cb765'
|
|
// - if mormot.core.log.pas is defined in the project, will redirect to
|
|
// TDebugFile.FindLocationShort() method using .map/.dbg/.mab information, and
|
|
// return filename, symbol name and line number (if any) as plain text, e.g.
|
|
// '4cb765 ../src/core/mormot.core.base.pas statuscodeissuccess (11183)' on FPC
|
|
var
|
|
GetExecutableLocation: function(aAddress: pointer): ShortString;
|
|
|
|
/// try to retrieve the file name of the executable/library holding a function
|
|
// - calls dladdr() on POSIX, or GetModuleFileName() on Windows
|
|
function GetExecutableName(aAddress: pointer): TFileName;
|
|
|
|
var
|
|
/// retrieve the MAC addresses of all hardware network adapters
|
|
// - mormot.net.sock.pas will inject here its own cross-platform version
|
|
// - this unit will include a simple parser of /sys/class/net/* for Linux only
|
|
// - as used e.g. by GetComputerUuid() fallback if SMBIOS is not available
|
|
GetSystemMacAddress: function: TRawUtf8DynArray;
|
|
|
|
|
|
type
|
|
/// identify an operating system folder for GetSystemPath()
|
|
// - on Windows, spCommonData maps e.g. 'C:\ProgramData',
|
|
// spUserData points to 'C:\Users\<user>\AppData\Local',
|
|
// spCommonDocuments to 'C:\Users\Public\Documents',
|
|
// spUserDocuments to 'C:\Users\<user>\Documents',
|
|
// spTemp will call GetTempPath() or read the $TEMP environment variable,
|
|
// pointing typically to 'C:\Users\<user>\AppData\Local\Temp\',
|
|
// and spLog either to '<exepath>\log' or
|
|
// 'C:\Users\<user>\AppData\Local\<exename>-log' (the first writable)
|
|
// - on POSIX, spTemp will use $TMPDIR/$TMP environment variables,
|
|
// spCommonData, spCommonDocuments and spUserDocuments point to $HOME,
|
|
// spUserData maps $XDG_CACHE_HOME or '$HOME/.cache' or '$TMP/<user>', and
|
|
// spLog maps '/var/log/<exename>' or '<exepath>/log' or '$TMP/<exename>-log'
|
|
// - on all systems, returned spTemp, spLog and spUserData folders are always
|
|
// writable by the current user
|
|
TSystemPath = (
|
|
spCommonData,
|
|
spUserData,
|
|
spCommonDocuments,
|
|
spUserDocuments,
|
|
spTemp,
|
|
spLog);
|
|
|
|
{$ifndef PUREMORMOT2}
|
|
const
|
|
spTempFolder = spTemp;
|
|
{$endif PUREMORMOT2}
|
|
|
|
/// returns an operating system folder
|
|
// - will return the full path of a given kind of private or shared folder,
|
|
// depending on the underlying operating system
|
|
// - will use SHGetFolderPath and the corresponding CSIDL constant under Windows
|
|
// - under POSIX, will return the proper environment variable
|
|
// - spLog is a writable sub-folder specific to mORMot, always created if needed
|
|
// - returned folder name contains the trailing path delimiter (\ or /)
|
|
function GetSystemPath(kind: TSystemPath): TFileName;
|
|
|
|
/// force an operating system folder
|
|
// - if the default location is not good enough for your project
|
|
// - will just check that the directory exists, not that it is writable
|
|
function SetSystemPath(kind: TSystemPath; const path: TFileName): boolean;
|
|
|
|
type
|
|
/// identify the (Windows) system certificate stores for GetSystemStoreAsPem()
|
|
// - ignored on POSIX systems, in which the main cacert.pem file is used
|
|
// - scsCA contains known Certification Authority certificates, i.e. from
|
|
// entities entrusted to issue certificates that assert that the recipient
|
|
// individual, computer, or organization requesting the certificate fulfills
|
|
// the conditions of an established policy
|
|
// - scsMY holds certificates with associated private keys (Windows only)
|
|
// - scsRoot contains known Root certificates, i.e. self-signed CA certificates
|
|
// which are the root of the whole certificates trust tree
|
|
// - scsSpc contains Software Publisher Certificates (Windows only)
|
|
TSystemCertificateStore = (
|
|
scsCA,
|
|
scsMY,
|
|
scsRoot,
|
|
scsSpc);
|
|
TSystemCertificateStores = set of TSystemCertificateStore;
|
|
|
|
var
|
|
/// the local PEM file name to be searched by GetSystemStoreAsPem() to
|
|
// override the OS certificates store
|
|
// - a relative file name (i.e. with no included path, e.g. 'cacert.pem') will
|
|
// be searched in the Executable.ProgramFilePath folder
|
|
// - an absolute file name (e.g. 'C:\path\to\file.pem' or '/posix/path') could
|
|
// also be specified
|
|
// - set by default to '' to disable this override (for security purposes)
|
|
GetSystemStoreAsPemLocalFile: TFileName;
|
|
|
|
/// retrieve the OS certificates store as PEM text
|
|
// - first search for [Executable.ProgramFilePath+]GetSystemStoreAsPemLocalFile,
|
|
// then for a file pointed by a 'SSL_CA_CERT_FILE' environment variable - unless
|
|
// OnlySystemStore is forced to true
|
|
// - if no such file exists, or if OnlySystemStore is true, will concatenate the
|
|
// supplied CertStores values via individual GetOneSystemStoreAsPem() calls
|
|
// - return CA + ROOT certificates by default, ready to validate a certificate
|
|
// - Darwin specific API is not supported yet, and is handled as a BSD system
|
|
// - an internal cache is refreshed every 4 minutes unless FlushCache is set
|
|
function GetSystemStoreAsPem(
|
|
CertStores: TSystemCertificateStores = [scsCA, scsRoot];
|
|
FlushCache: boolean = false; OnlySystemStore: boolean = false): RawUtf8;
|
|
|
|
/// retrieve all certificates of a given system store as PEM text
|
|
// - on Windows, will use the System Crypt API
|
|
// - on POSIX, scsRoot loads the main CA file of the known system file, and
|
|
// scsCA the additional certificate files which may not be part of the main file
|
|
// - GetSystemStoreAsPemLocalFile file and 'SSL_CA_CERT_FILE' environment
|
|
// variables are ignored: call GetSystemStoreAsPem() instead for the global store
|
|
// - an internal cache is refreshed every 4 minutes unless FlushCache is set
|
|
function GetOneSystemStoreAsPem(CertStore: TSystemCertificateStore;
|
|
FlushCache: boolean = false; now: cardinal = 0): RawUtf8;
|
|
|
|
type
|
|
/// the raw SMBIOS information as filled by GetRawSmbios
|
|
// - first 4 bytes are $010003ff on POSIX if read from /var/tmp/.synopse.smb
|
|
TRawSmbiosInfo = record
|
|
/// some flag only set by GetSystemFirmwareTable() Windows API
|
|
Reserved: byte;
|
|
/// typically 2-3
|
|
SmbMajorVersion: byte;
|
|
/// typically 0-1
|
|
SmbMinorVersion: byte;
|
|
/// typically 0 for SMBIOS 2.1, 1 for SMBIOS 3.0
|
|
DmiRevision: byte;
|
|
/// the length of encoded binary in data
|
|
Length: DWORD;
|
|
/// low-level binary of the SMBIOS Structure Table
|
|
Data: RawByteString;
|
|
end;
|
|
|
|
var
|
|
/// global variable filled by GetRawSmbios from SMBIOS binary information
|
|
RawSmbios: TRawSmbiosInfo;
|
|
|
|
/// retrieve the SMBIOS raw information as a single RawSmbios gloabl binary blob
|
|
// - will try the Windows API if available, or search and parse the main system
|
|
// memory with UEFI redirection if needed - via /systab system file on Linux, or
|
|
// kenv() on FreeBSD (only fully tested to work on Windows XP+ and Linux)
|
|
// - follow DSP0134 3.6.0 System Management BIOS (SMBIOS) Reference Specification
|
|
// with both SMBIOS 2.1 (32-bit) or SMBIOS 3.0 (64-bit) entry points
|
|
// - the current user should have enough rights to read the main system memory,
|
|
// which means it should be root on most POSIX Operating Systems - so we persist
|
|
// this raw binary in /var/tmp/.synopse.smb to retrieve it from non-root user
|
|
function GetRawSmbios: boolean;
|
|
|
|
type
|
|
/// the basic SMBIOS fields supported by GetSmbios/DecodeSmbios functions
|
|
// - only include the first occurrence for board/cpu/battery types
|
|
// - see TSmbiosInfo in mormot.core.perf.pas for more complete decoding
|
|
TSmbiosBasicInfo = (
|
|
sbiUndefined,
|
|
sbiBiosVendor,
|
|
sbiBiosVersion,
|
|
sbiBiosFirmware,
|
|
sbiBiosRelease,
|
|
sbiBiosDate,
|
|
sbiManufacturer,
|
|
sbiProductName,
|
|
sbiVersion,
|
|
sbiSerial,
|
|
sbiUuid,
|
|
sbiSku,
|
|
sbiFamily,
|
|
sbiBoardManufacturer,
|
|
sbiBoardProductName,
|
|
sbiBoardVersion,
|
|
sbiBoardSerial,
|
|
sbiBoardAssetTag,
|
|
sbiBoardLocation,
|
|
sbiCpuManufacturer,
|
|
sbiCpuVersion,
|
|
sbiCpuSerial,
|
|
sbiCpuAssetTag,
|
|
sbiCpuPartNumber,
|
|
sbiBatteryLocation,
|
|
sbiBatteryManufacturer,
|
|
sbiBatteryName,
|
|
sbiBatteryVersion,
|
|
sbiBatteryChemistry,
|
|
sbiOem
|
|
);
|
|
|
|
/// the text fields stored by GetSmbios/DecodeSmbios functions
|
|
TSmbiosBasicInfos = array[TSmbiosBasicInfo] of RawUtf8;
|
|
|
|
/// decode basic SMBIOS information as text from a TRawSmbiosInfo binary blob
|
|
// - see DecodeSmbiosInfo() in mormot.core.perf.pas for a more complete decoder
|
|
// - returns the total size of DMI/SMBIOS information in raw.data (may be lower)
|
|
// - will also adjust raw.Length and truncate raw.Data to the actual useful size
|
|
function DecodeSmbios(var raw: TRawSmbiosInfo; out info: TSmbiosBasicInfos): PtrInt;
|
|
|
|
// some global definitions for proper caching and inlining of GetSmbios()
|
|
procedure ComputeGetSmbios;
|
|
procedure DecodeSmbiosUuid(src: PGuid; out dest: RawUtf8; const raw: TRawSmbiosInfo);
|
|
var
|
|
_Smbios: TSmbiosBasicInfos;
|
|
_SmbiosRetrieved: boolean;
|
|
|
|
/// customize how DecodeSmbiosUuid() handle endianess of its first bytes
|
|
// - sduDirect will directly use GUIDToString() layout (seems expected on
|
|
// Windows to match "wmic csproduct get uuid" value)
|
|
// - sduInvert will force first values inversion (mandatory on MacOS)
|
|
// - sduVersion will invert for SMBios version < 2.6 (set outside Windows)
|
|
_SmbiosDecodeUuid: (sduDirect, sduInvert, sduVersion)
|
|
{$ifdef OSDARWIN} = sduInvert {$else}
|
|
{$ifdef OSPOSIX} = sduVersion {$endif} {$endif};
|
|
|
|
/// retrieve SMBIOS information as text
|
|
// - only the main values are decoded - see GetSmbiosInfo in mormot.core.perf
|
|
// for a more complete DMI/SMBIOS decoder
|
|
// - on POSIX, requires root to access full SMBIOS information - will fallback
|
|
// reading /sys/class/dmi/id/* on Linux or kenv() on FreeBSD for most entries
|
|
// if we found no previous root-retrieved cache in local /var/tmp/.synopse.smb
|
|
// - see _SmbiosDecodeUuid global flag for UUID decoding
|
|
function GetSmbios(info: TSmbiosBasicInfo): RawUtf8;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
|
|
/// retrieve a genuine 128-bit UUID identifier for this computer
|
|
// - first try GetSmbios(sbiUuid), i.e. the SMBIOS System UUID
|
|
// - otherwise, will compute a genuine hash from known hardware information
|
|
// (CPU, Bios, MAC) and store it in a local file for the next access, e.g. into
|
|
// '/var/tmp/.synopse.uid' on POSIX
|
|
// - on Mac, include the mormot.core.os.mac unit to properly read this UUID
|
|
// - note: some BIOS have no UUID, so we fallback to our hardware hash on those
|
|
procedure GetComputerUuid(out uuid: TGuid);
|
|
|
|
|
|
{ ****************** Operating System Specific Types (e.g. TWinRegistry) }
|
|
|
|
{$ifdef OSWINDOWS}
|
|
|
|
type
|
|
TThreadID = DWORD;
|
|
TMessage = Messages.TMessage;
|
|
HWND = Windows.HWND;
|
|
BOOL = Windows.BOOL;
|
|
LARGE_INTEGER = Windows.LARGE_INTEGER;
|
|
TFileTime = Windows.FILETIME;
|
|
PFileTime = ^TFileTime;
|
|
|
|
/// the known Windows Registry Root key used by TWinRegistry.ReadOpen
|
|
TWinRegistryRoot = (
|
|
wrClasses,
|
|
wrCurrentUser,
|
|
wrLocalMachine,
|
|
wrUsers);
|
|
|
|
/// direct access to the Windows Registry
|
|
// - could be used as alternative to TRegistry, which doesn't behave the same on
|
|
// all Delphi versions, and is enhanced on FPC (e.g. which supports REG_MULTI_SZ)
|
|
// - is also Unicode ready for text, using UTF-8 conversion on all compilers
|
|
{$ifdef USERECORDWITHMETHODS}
|
|
TWinRegistry = record
|
|
{$else}
|
|
TWinRegistry = object
|
|
{$endif USERECORDWITHMETHODS}
|
|
public
|
|
/// the opened HKEY handle
|
|
key: HKEY;
|
|
/// start low-level read access to a Windows Registry node
|
|
// - on success (returned true), Close method should be eventually called
|
|
function ReadOpen(root: TWinRegistryRoot; const keyname: RawUtf8;
|
|
closefirst: boolean = false): boolean;
|
|
/// finalize low-level read access to the Windows Registry after ReadOpen()
|
|
procedure Close;
|
|
/// read a UTF-8 string from the Windows Registry after ReadOpen()
|
|
// - in respect to Delphi's TRegistry, will properly handle REG_MULTI_SZ
|
|
// (return the first value of the multi-list) - use ReadData to retrieve
|
|
// all REG_MULTI_SZ values as one blob
|
|
// - we don't use string here since it would induce a dependency to
|
|
// mormot.core.unicode
|
|
function ReadString(const entry: SynUnicode; andtrim: boolean = true): RawUtf8;
|
|
/// read a Windows Registry content after ReadOpen()
|
|
// - works with any kind of key, but was designed for REG_BINARY
|
|
function ReadData(const entry: SynUnicode): RawByteString;
|
|
/// read a Windows Registry 32-bit REG_DWORD value after ReadOpen()
|
|
function ReadDword(const entry: SynUnicode): cardinal;
|
|
/// read a Windows Registry 64-bit REG_QWORD value after ReadOpen()
|
|
function ReadQword(const entry: SynUnicode): QWord;
|
|
/// read a Windows Registry content as binary buffer after ReadOpen()
|
|
// - just a wrapper around RegQueryValueExW() API call
|
|
function ReadBuffer(const entry: SynUnicode; Data: pointer; DataLen: DWORD): boolean;
|
|
/// retrieve a Windows Registry content size as binary bytes after ReadOpen()
|
|
// - returns -1 if the entry is not found
|
|
function ReadSize(const entry: SynUnicode): integer;
|
|
/// enumeration of all sub-entries names of a Windows Registry key
|
|
function ReadEnumEntries: TRawUtf8DynArray;
|
|
end;
|
|
|
|
/// TSynWindowsPrivileges enumeration synchronized with WinAPI
|
|
// - see https://docs.microsoft.com/en-us/windows/desktop/secauthz/privilege-constants
|
|
TWinSystemPrivilege = (
|
|
wspCreateToken,
|
|
wspAssignPrimaryToken,
|
|
wspLockMemory,
|
|
wspIncreaseQuota,
|
|
wspUnsolicitedInput,
|
|
wspMachineAccount,
|
|
wspTCP,
|
|
wspSecurity,
|
|
wspTakeOwnership,
|
|
wspLoadDriver,
|
|
wspSystemProfile,
|
|
wspSystemTime,
|
|
wspProfSingleProcess,
|
|
wspIncBasePriority,
|
|
wspCreatePageFile,
|
|
wspCreatePermanent,
|
|
wspBackup,
|
|
wspRestore,
|
|
wspShutdown,
|
|
wspDebug,
|
|
wspAudit,
|
|
wspSystemEnvironment,
|
|
wspChangeNotify,
|
|
wspRemoteShutdown,
|
|
wspUndock,
|
|
wspSyncAgent,
|
|
wspEnableDelegation,
|
|
wspManageVolume,
|
|
wspImpersonate,
|
|
wspCreateGlobal,
|
|
wspTrustedCredmanAccess,
|
|
wspRelabel,
|
|
wspIncWorkingSet,
|
|
wspTimeZone,
|
|
wspCreateSymbolicLink);
|
|
|
|
/// TSynWindowsPrivileges set synchronized with WinAPI
|
|
TWinSystemPrivileges = set of TWinSystemPrivilege;
|
|
|
|
/// define which WinAPI token is to be retrieved
|
|
// - define the execution context, i.e. if the token is used for the current
|
|
// process or the current thread
|
|
// - used e.g. by TSynWindowsPrivileges or CurrentSid()
|
|
TWinTokenType = (
|
|
wttProcess,
|
|
wttThread);
|
|
|
|
/// manage available privileges on Windows platform
|
|
// - not all available privileges are active for all process
|
|
// - for usage of more advanced WinAPI, explicit enabling of privilege is
|
|
// sometimes needed
|
|
{$ifdef USERECORDWITHMETHODS}
|
|
TSynWindowsPrivileges = record
|
|
{$else}
|
|
TSynWindowsPrivileges = object
|
|
{$endif USERECORDWITHMETHODS}
|
|
private
|
|
fAvailable: TWinSystemPrivileges;
|
|
fEnabled: TWinSystemPrivileges;
|
|
fDefEnabled: TWinSystemPrivileges;
|
|
fToken: THandle;
|
|
function SetPrivilege(wsp: TWinSystemPrivilege; on: boolean): boolean;
|
|
procedure LoadPrivileges;
|
|
public
|
|
/// initialize the object dedicated to management of available privileges
|
|
// - aTokenPrivilege can be used for current process or current thread
|
|
procedure Init(aTokenPrivilege: TWinTokenType = wttProcess;
|
|
aLoadPrivileges: boolean = true);
|
|
/// finalize the object and relese Token handle
|
|
// - aRestoreInitiallyEnabled parameter can be used to restore initially
|
|
// state of enabled privileges
|
|
procedure Done(aRestoreInitiallyEnabled: boolean = true);
|
|
/// enable privilege
|
|
// - if aPrivilege is already enabled return true, if operation is not
|
|
// possible (required privilege doesn't exist or API error) return false
|
|
function Enable(aPrivilege: TWinSystemPrivilege): boolean;
|
|
/// disable privilege
|
|
// - if aPrivilege is already disabled return true, if operation is not
|
|
// possible (required privilege doesn't exist or API error) return false
|
|
function Disable(aPrivilege: TWinSystemPrivilege): boolean;
|
|
/// set of available privileges for current process/thread
|
|
property Available: TWinSystemPrivileges
|
|
read fAvailable;
|
|
/// set of enabled privileges for current process/thread
|
|
property Enabled: TWinSystemPrivileges
|
|
read fEnabled;
|
|
/// low-level access to the privileges token handle
|
|
property Token: THandle
|
|
read fToken;
|
|
end;
|
|
|
|
/// which information was returned by GetProcessInfo() overloaded functions
|
|
// - wpaiPID is set when PID was retrieved
|
|
// - wpaiBasic with ParentPID/BasePriority/ExitStatus/PEBBaseAddress/AffinityMask
|
|
// - wpaiPEB with SessionID/BeingDebugged
|
|
// - wpaiCommandLine and wpaiImagePath when CommandLine and ImagePath are set
|
|
TWinProcessAvailableInfos = set of (
|
|
wpaiPID,
|
|
wpaiBasic,
|
|
wpaiPEB,
|
|
wpaiCommandLine,
|
|
wpaiImagePath);
|
|
|
|
/// information returned by GetProcessInfo() overloaded functions
|
|
TWinProcessInfo = record
|
|
/// which information was returned within this structure
|
|
AvailableInfo: TWinProcessAvailableInfos;
|
|
/// the Process ID
|
|
PID: cardinal;
|
|
/// the Parent Process ID
|
|
ParentPID: cardinal;
|
|
/// Terminal Services session identifier associated with this process
|
|
SessionID: cardinal;
|
|
/// points to the low-level internal PEB structure
|
|
// - you can not directly access this memory, unless ReadProcessMemory()
|
|
// with proper wspDebug priviledge API is called
|
|
PEBBaseAddress: pointer;
|
|
/// GetProcessAffinityMask-like value
|
|
AffinityMask: cardinal;
|
|
/// process priority
|
|
BasePriority: integer;
|
|
/// GetExitCodeProcess-like value
|
|
ExitStatus: integer;
|
|
/// indicates whether the specified process is currently being debugged
|
|
BeingDebugged: byte;
|
|
/// command-line string passed to the process
|
|
CommandLine: SynUnicode;
|
|
/// path of the image file for the process
|
|
ImagePath: SynUnicode;
|
|
end;
|
|
|
|
PWinProcessInfo = ^TWinProcessInfo;
|
|
TWinProcessInfoDynArray = array of TWinProcessInfo;
|
|
|
|
/// the SID types, as recognized by LookupSid()
|
|
TSidType = (
|
|
stUndefined,
|
|
stTypeUser,
|
|
stTypeGroup,
|
|
stTypeDomain,
|
|
stTypeAlias,
|
|
stTypeWellKnownGroup,
|
|
stTypeDeletedAccount,
|
|
stTypeInvalid,
|
|
stTypeUnknown,
|
|
stTypeComputer,
|
|
stTypeLabel,
|
|
stTypeLogonSession);
|
|
|
|
|
|
function ToText(p: TWinSystemPrivilege): PShortString; overload;
|
|
|
|
/// calls OpenProcessToken() or OpenThreadToken() to get the current token
|
|
// - caller should then run CloseHandle() once done with the Token handle
|
|
function RawTokenOpen(wtt: TWinTokenType; access: cardinal): THandle;
|
|
|
|
/// low-level retrieveal of raw binary information for a given token
|
|
// - returns the number of bytes retrieved into buf.buf
|
|
// - caller should then run buf.Done to release the buf result memory
|
|
function RawTokenGetInfo(tok: THandle; tic: TTokenInformationClass;
|
|
var buf: TSynTempBuffer): cardinal;
|
|
|
|
/// return the SID of a given token, nil if none found
|
|
// - the returned PSid is located within buf temporary buffer
|
|
// - so caller should call buf.Done once this PSid value is not needed any more
|
|
function RawTokenSid(tok: THandle; var buf: TSynTempBuffer): PSid;
|
|
|
|
/// return the group SIDs of a given token, nil if none found
|
|
// - the returned PSid is located within buf temporary buffer
|
|
// - so caller should call buf.Done once this PSid value is not needed any more
|
|
function RawTokenGroups(tok: THandle; var buf: TSynTempBuffer): PSids;
|
|
|
|
/// return the group SIDs of a given token as text dynamic array
|
|
function TokenGroupsText(tok: THandle): TRawUtf8DynArray;
|
|
|
|
/// check if a group SID is part of a given token
|
|
function TokenHasGroup(tok: THandle; sid: PSid): boolean;
|
|
|
|
/// check if any group SID is part of a given token
|
|
function TokenHasAnyGroup(tok: THandle; const sid: RawSidDynArray): boolean;
|
|
|
|
/// return the SID of the current user, from process or thread, as text
|
|
// - e.g. 'S-1-5-21-823746769-1624905683-418753922-1000'
|
|
// - optionally returning the name and domain via LookupSid()
|
|
function CurrentSid(wtt: TWinTokenType = wttProcess;
|
|
name: PRawUtf8 = nil; domain: PRawUtf8 = nil): RawUtf8; overload;
|
|
|
|
/// return the SID of the current user, from process or thread, as raw binary
|
|
procedure CurrentRawSid(out sid: RawSid; wtt: TWinTokenType = wttProcess;
|
|
name: PRawUtf8 = nil; domain: PRawUtf8 = nil); overload;
|
|
|
|
/// return the SID of the current user groups, from process or thread, as text
|
|
function CurrentGroupsSid(wtt: TWinTokenType = wttProcess): TRawUtf8DynArray;
|
|
|
|
/// recognize the well-known SIDs from the current user, from process or thread
|
|
// - for instance, for an user with administrator rights on Windows, returns
|
|
// $ [wksWorld, wksLocal, wksConsoleLogon, wksIntegrityHigh, wksInteractive,
|
|
// $ wksAuthenticatedUser, wksThisOrganisation, wksBuiltinAdministrators,
|
|
// $ wksBuiltinUsers, wksNtlmAuthentication]
|
|
function CurrentKnownGroups(wtt: TWinTokenType = wttProcess): TWellKnownSids;
|
|
|
|
/// fast check if the current user, from process or thread, has a well-known group SID
|
|
// - e.g. CurrentUserHasGroup(wksLocalSystem) returns true for LOCAL_SYSTEM user
|
|
function CurrentUserHasGroup(wks: TWellKnownSid;
|
|
wtt: TWinTokenType = wttProcess): boolean; overload;
|
|
|
|
/// fast check if the current user, from process or thread, has a given group SID
|
|
function CurrentUserHasGroup(const sid: RawUtf8;
|
|
wtt: TWinTokenType = wttProcess): boolean; overload;
|
|
|
|
/// fast check if the current user, from process or thread, has a given group SID
|
|
function CurrentUserHasGroup(sid: PSid;
|
|
wtt: TWinTokenType = wttProcess): boolean; overload;
|
|
|
|
/// fast check if the current user, from process or thread, has any given group SID
|
|
function CurrentUserHasAnyGroup(const sid: RawSidDynArray;
|
|
wtt: TWinTokenType = wttProcess): boolean;
|
|
|
|
/// fast check if the current user, from process or thread, match a group by name
|
|
// - calls LookupSid() on each group SID of this user, and filter with name/domain
|
|
function CurrentUserHasGroup(const name, domain, server: RawUtf8;
|
|
wtt: TWinTokenType = wttProcess): boolean; overload;
|
|
|
|
/// just a wrapper around CurrentUserHasGroup(wksBuiltinAdministrators)
|
|
function CurrentUserIsAdmin: boolean;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
|
|
/// rough detection of 'c:\windows' and 'c:\program files' folders
|
|
function IsSystemFolder(const Folder: TFileName): boolean;
|
|
|
|
// check if a folder may be affected by UAC folder virtualization
|
|
// - on Win32 Vista+, detects 'c:\windows' and 'c:\program files' UAC folders
|
|
// - returns always false on Win64
|
|
function IsUacVirtualFolder(const Folder: TFileName): boolean;
|
|
{$ifdef CPU64} inline; {$endif}
|
|
|
|
/// check if UAC folder/registry virtualization is enabled for this process
|
|
// - returns always false on Win64 - by design
|
|
// - calls GetTokenInformation(TokenVirtualizationEnabled) on Win32
|
|
// - if you include {$R src\mormot.win.default.manifest.res} in your project,
|
|
// UAC virtualization is disabled and this function returns false
|
|
function IsUacVirtualizationEnabled: boolean;
|
|
{$ifdef CPU64} inline; {$endif}
|
|
|
|
/// retrieve the name and domain of a given SID
|
|
// - returns stUndefined if the SID could not be resolved by LookupAccountSid()
|
|
function LookupSid(sid: PSid; out name, domain: RawUtf8;
|
|
const server: RawUtf8 = ''): TSidType; overload;
|
|
|
|
/// retrieve the name and domain of a given SID, encoded from text
|
|
// - returns stUndefined if the SID could not be resolved by LookupAccountSid()
|
|
function LookupSid(const sid: RawUtf8; out name, domain: RawUtf8;
|
|
const server: RawUtf8 = ''): TSidType; overload;
|
|
|
|
/// retrieve the name and domain of a given Token
|
|
function LookupToken(tok: THandle; out name, domain: RawUtf8;
|
|
const server: RawUtf8 = ''): boolean; overload;
|
|
|
|
/// retrieve the 'domain\name' combined value of a given Token
|
|
function LookupToken(tok: THandle; const server: RawUtf8 = ''): RawUtf8; overload;
|
|
|
|
/// retrieve low-level process information, from the Windows API
|
|
procedure GetProcessInfo(aPid: cardinal; out aInfo: TWinProcessInfo); overload;
|
|
|
|
/// retrieve low-level process(es) information, from the Windows API
|
|
procedure GetProcessInfo(const aPidList: TCardinalDynArray;
|
|
out aInfo: TWinProcessInfoDynArray); overload;
|
|
|
|
/// quickly retrieve a Text value from Registry
|
|
// - could be used if TWinRegistry is not needed, e.g. for a single value
|
|
function ReadRegString(Key: THandle; const Path, Value: string): string;
|
|
|
|
/// convenient late-binding of any external library function
|
|
// - just wrapper around LoadLibray + GetProcAddress once over a pointer
|
|
function DelayedProc(var api; var lib: THandle;
|
|
libname: PChar; procname: PAnsiChar): boolean;
|
|
|
|
type
|
|
HCRYPTPROV = pointer;
|
|
HCRYPTKEY = pointer;
|
|
HCRYPTHASH = pointer;
|
|
HCERTSTORE = pointer;
|
|
|
|
CRYPTOAPI_BLOB = record
|
|
cbData: DWORD;
|
|
pbData: PByteArray;
|
|
end;
|
|
CRYPT_INTEGER_BLOB = CRYPTOAPI_BLOB;
|
|
CERT_NAME_BLOB = CRYPTOAPI_BLOB;
|
|
CRYPT_OBJID_BLOB = CRYPTOAPI_BLOB;
|
|
|
|
CRYPT_BIT_BLOB = record
|
|
cbData: DWORD;
|
|
pbData: PByteArray;
|
|
cUnusedBits: DWORD;
|
|
end;
|
|
|
|
CRYPT_ALGORITHM_IDENTIFIER = record
|
|
pszObjId: PAnsiChar;
|
|
Parameters: CRYPT_OBJID_BLOB;
|
|
end;
|
|
|
|
CERT_PUBLIC_KEY_INFO = record
|
|
Algorithm: CRYPT_ALGORITHM_IDENTIFIER;
|
|
PublicKey: CRYPT_BIT_BLOB;
|
|
end;
|
|
|
|
CERT_EXTENSION = record
|
|
pszObjId: PAnsiChar;
|
|
fCritical: BOOL;
|
|
Blob: CRYPT_OBJID_BLOB;
|
|
end;
|
|
PCERT_EXTENSION = ^CERT_EXTENSION;
|
|
CERT_EXTENSIONS = array[word] of CERT_EXTENSION;
|
|
PCERT_EXTENSIONS = ^CERT_EXTENSIONS;
|
|
|
|
CERT_INFO = record
|
|
dwVersion: DWORD;
|
|
SerialNumber: CRYPT_INTEGER_BLOB;
|
|
SignatureAlgorithm: CRYPT_ALGORITHM_IDENTIFIER;
|
|
Issuer: CERT_NAME_BLOB;
|
|
NotBefore: TFileTime;
|
|
NotAfter: TFileTime;
|
|
Subject: CERT_NAME_BLOB;
|
|
SubjectPublicKeyInfo: CERT_PUBLIC_KEY_INFO;
|
|
IssuerUniqueId: CRYPT_BIT_BLOB;
|
|
SubjectUniqueId: CRYPT_BIT_BLOB;
|
|
cExtension: DWORD;
|
|
rgExtension: PCERT_EXTENSIONS;
|
|
end;
|
|
PCERT_INFO = ^CERT_INFO;
|
|
|
|
CERT_CONTEXT = record
|
|
dwCertEncodingType: DWORD;
|
|
pbCertEncoded: PByte;
|
|
cbCertEncoded: DWORD;
|
|
pCertInfo: PCERT_INFO;
|
|
hCertStore: HCERTSTORE;
|
|
end;
|
|
PCCERT_CONTEXT = ^CERT_CONTEXT;
|
|
PPCCERT_CONTEXT = ^PCCERT_CONTEXT;
|
|
|
|
CRYPT_KEY_PROV_PARAM = record
|
|
dwParam: DWORD;
|
|
pbData: PByte;
|
|
cbData: DWORD;
|
|
dwFlags: DWORD;
|
|
end;
|
|
PCRYPT_KEY_PROV_PARAM = ^CRYPT_KEY_PROV_PARAM;
|
|
|
|
CRYPT_KEY_PROV_INFO = record
|
|
pwszContainerName: PWideChar;
|
|
pwszProvName: PWideChar;
|
|
dwProvType: DWORD;
|
|
dwFlags: DWORD;
|
|
cProvParam: DWORD;
|
|
rgProvParam: PCRYPT_KEY_PROV_PARAM;
|
|
dwKeySpec: DWORD;
|
|
end;
|
|
PCRYPT_KEY_PROV_INFO = ^CRYPT_KEY_PROV_INFO;
|
|
|
|
CRYPT_OID_INFO = record
|
|
cbSize: DWORD;
|
|
pszOID: PAnsiChar;
|
|
pwszName: PWideChar;
|
|
dwGroupId: DWORD;
|
|
Union: record
|
|
case integer of
|
|
0: (dwValue: DWORD);
|
|
1: (Algid: DWORD);
|
|
2: (dwLength: DWORD);
|
|
end;
|
|
ExtraInfo: CRYPTOAPI_BLOB;
|
|
end;
|
|
PCRYPT_OID_INFO = ^CRYPT_OID_INFO;
|
|
|
|
PCCRL_CONTEXT = pointer;
|
|
PPCCRL_CONTEXT = ^PCCRL_CONTEXT;
|
|
PCRYPT_ATTRIBUTE = pointer;
|
|
|
|
CRYPT_SIGN_MESSAGE_PARA = record
|
|
cbSize: DWORD;
|
|
dwMsgEncodingType: DWORD;
|
|
pSigningCert: PCCERT_CONTEXT;
|
|
HashAlgorithm: CRYPT_ALGORITHM_IDENTIFIER;
|
|
pvHashAuxInfo: Pointer;
|
|
cMsgCert: DWORD;
|
|
rgpMsgCert: PPCCERT_CONTEXT;
|
|
cMsgCrl: DWORD;
|
|
rgpMsgCrl: PPCCRL_CONTEXT;
|
|
cAuthAttr: DWORD;
|
|
rgAuthAttr: PCRYPT_ATTRIBUTE;
|
|
cUnauthAttr: DWORD;
|
|
rgUnauthAttr: PCRYPT_ATTRIBUTE;
|
|
dwFlags: DWORD;
|
|
dwInnerContentType: DWORD;
|
|
HashEncryptionAlgorithm: CRYPT_ALGORITHM_IDENTIFIER;
|
|
pvHashEncryptionAuxInfo: Pointer;
|
|
end;
|
|
|
|
PFN_CRYPT_GET_SIGNER_CERTIFICATE = function(pvGetArg: Pointer;
|
|
dwCertEncodingType: DWORD; pSignerId: PCERT_INFO;
|
|
hMsgCertStore: HCERTSTORE): PCCERT_CONTEXT; stdcall;
|
|
CRYPT_VERIFY_MESSAGE_PARA = record
|
|
cbSize: DWORD;
|
|
dwMsgAndCertEncodingType: DWORD;
|
|
hCryptProv: HCRYPTPROV;
|
|
pfnGetSignerCertificate: PFN_CRYPT_GET_SIGNER_CERTIFICATE;
|
|
pvGetArg: Pointer;
|
|
end;
|
|
|
|
/// direct access to the Windows CryptoApi
|
|
{$ifdef USERECORDWITHMETHODS}
|
|
TWinCryptoApi = record
|
|
{$else}
|
|
TWinCryptoApi = object
|
|
{$endif USERECORDWITHMETHODS}
|
|
private
|
|
/// if the presence of this API has been tested
|
|
Tested: boolean;
|
|
/// if this API has been loaded
|
|
Handle: THandle;
|
|
/// used when inlining Available method
|
|
procedure Resolve;
|
|
public
|
|
/// acquire a handle to a particular key container within a
|
|
// particular cryptographic service provider (CSP)
|
|
AcquireContextA: function(var phProv: HCRYPTPROV; pszContainer: PAnsiChar;
|
|
pszProvider: PAnsiChar; dwProvType: DWORD; dwFlags: DWORD): BOOL; stdcall;
|
|
/// releases the handle of a cryptographic service provider (CSP) and a
|
|
// key container
|
|
ReleaseContext: function(hProv: HCRYPTPROV; dwFlags: PtrUInt): BOOL; stdcall;
|
|
/// transfers a cryptographic key from a key BLOB into a cryptographic
|
|
// service provider (CSP)
|
|
ImportKey: function(hProv: HCRYPTPROV; pbData: pointer; dwDataLen: DWORD;
|
|
hPubKey: HCRYPTKEY; dwFlags: DWORD; var phKey: HCRYPTKEY): BOOL; stdcall;
|
|
/// customizes various aspects of a session key's operations
|
|
SetKeyParam: function(hKey: HCRYPTKEY; dwParam: DWORD; pbData: pointer;
|
|
dwFlags: DWORD): BOOL; stdcall;
|
|
/// releases the handle referenced by the hKey parameter
|
|
DestroyKey: function(hKey: HCRYPTKEY): BOOL; stdcall;
|
|
/// encrypt the data designated by the key held by the CSP module
|
|
// referenced by the hKey parameter
|
|
Encrypt: function(hKey: HCRYPTKEY; hHash: HCRYPTHASH; Final: BOOL;
|
|
dwFlags: DWORD; pbData: pointer; var pdwDataLen: DWORD; dwBufLen: DWORD): BOOL; stdcall;
|
|
/// decrypts data previously encrypted by using the CryptEncrypt function
|
|
Decrypt: function(hKey: HCRYPTKEY; hHash: HCRYPTHASH; Final: BOOL;
|
|
dwFlags: DWORD; pbData: pointer; var pdwDataLen: DWORD): BOOL; stdcall;
|
|
/// fills a buffer with cryptographically random bytes
|
|
// - since Windows Vista with Service Pack 1 (SP1), an AES counter-mode
|
|
// based PRNG specified in NIST Special Publication 800-90 is used
|
|
GenRandom: function(hProv: HCRYPTPROV; dwLen: DWORD; pbBuffer: Pointer): BOOL; stdcall;
|
|
/// sign a message (not resolved yet - in crypt32.dll)
|
|
SignMessage: function(var pSignPara: CRYPT_SIGN_MESSAGE_PARA;
|
|
fDetachedSignature: BOOL; cToBeSigned: DWORD; rgpbToBeSigned: pointer;
|
|
var rgcbToBeSigned: DWORD; pbSignedBlob: pointer; var pcbSignedBlob: DWORD): BOOL; stdcall;
|
|
/// verify a signed message (not resolved yet - in crypt32.dll)
|
|
VerifyMessageSignature: function(var pVerifyPara: CRYPT_VERIFY_MESSAGE_PARA;
|
|
dwSignerIndex: DWORD; pbSignedBlob: PByte; cbSignedBlob: DWORD;
|
|
pbDecoded: PByte; pcbDecoded: LPDWORD; ppSignerCert: PPCCERT_CONTEXT): BOOL; stdcall;
|
|
/// try to load the CryptoApi on this system
|
|
function Available: boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
end;
|
|
|
|
const
|
|
NO_ERROR = Windows.NO_ERROR;
|
|
|
|
ERROR_ACCESS_DENIED = Windows.ERROR_ACCESS_DENIED;
|
|
ERROR_INVALID_PARAMETER = Windows.ERROR_INVALID_PARAMETER;
|
|
ERROR_HANDLE_EOF = Windows.ERROR_HANDLE_EOF;
|
|
ERROR_ALREADY_EXISTS = Windows.ERROR_ALREADY_EXISTS;
|
|
ERROR_MORE_DATA = Windows.ERROR_MORE_DATA;
|
|
ERROR_CONNECTION_INVALID = Windows.ERROR_CONNECTION_INVALID;
|
|
ERROR_OLD_WIN_VERSION = Windows.ERROR_OLD_WIN_VERSION;
|
|
ERROR_IO_PENDING = Windows.ERROR_IO_PENDING;
|
|
ERROR_OPERATION_ABORTED = Windows.ERROR_OPERATION_ABORTED;
|
|
// see http://msdn.microsoft.com/en-us/library/windows/desktop/aa383770
|
|
ERROR_WINHTTP_TIMEOUT = 12002;
|
|
ERROR_WINHTTP_CANNOT_CONNECT = 12029;
|
|
ERROR_WINHTTP_INVALID_SERVER_RESPONSE = 12152;
|
|
ERROR_MUI_FILE_NOT_FOUND = 15100;
|
|
|
|
INVALID_HANDLE_VALUE = Windows.INVALID_HANDLE_VALUE; // = HANDLE(-1)
|
|
ENGLISH_LANGID = $0409;
|
|
|
|
PROV_RSA_FULL = 1;
|
|
PROV_RSA_AES = 24;
|
|
CRYPT_NEWKEYSET = 8;
|
|
CRYPT_VERIFYCONTEXT = DWORD($F0000000);
|
|
PLAINTEXTKEYBLOB = 8;
|
|
CUR_BLOB_VERSION = 2;
|
|
KP_IV = 1;
|
|
KP_MODE = 4;
|
|
CALG_AES_128 = $660E;
|
|
CALG_AES_192 = $660F;
|
|
CALG_AES_256 = $6610;
|
|
CRYPT_MODE_CBC = 1;
|
|
CRYPT_MODE_ECB = 2;
|
|
CRYPT_MODE_OFB = 3;
|
|
CRYPT_MODE_CFB = 4;
|
|
CRYPT_MODE_CTS = 5;
|
|
HCRYPTPROV_NOTTESTED = HCRYPTPROV(-1);
|
|
NTE_BAD_KEYSET = HRESULT($80090016);
|
|
|
|
var
|
|
CryptoApi: TWinCryptoApi;
|
|
|
|
/// protect some data for the current user, using Windows DPAPI
|
|
// - the application can specify a secret salt text, which should reflect the
|
|
// current execution context, to ensure nobody could decrypt the data without
|
|
// knowing this application-specific AppSecret value
|
|
// - will use CryptProtectData DPAPI function call under Windows
|
|
// - see https://msdn.microsoft.com/en-us/library/ms995355
|
|
// - this function is Windows-only, could be slow, and you don't know which
|
|
// algorithm is really used on your system, so using our mormot.crypt.core.pas
|
|
// CryptDataForCurrentUser() is probably a safer (and cross-platform) alternative
|
|
// - also note that DPAPI has been closely reverse engineered - see e.g.
|
|
// https://www.passcape.com/index.php?section=docsys&cmd=details&id=28
|
|
function CryptDataForCurrentUserDPAPI(const Data, AppSecret: RawByteString;
|
|
Encrypt: boolean): RawByteString;
|
|
|
|
const
|
|
WINDOWS_CERTSTORE: array[TSystemCertificateStore] of PWideChar = (
|
|
'CA', 'MY', 'ROOT', 'SPC');
|
|
|
|
/// this global procedure should be called from each thread needing to use OLE
|
|
// - it is called e.g. by TOleDBConnection.Create when an OleDb connection
|
|
// is instantiated for a new thread
|
|
// - every call of CoInit shall be followed by a call to CoUninit
|
|
// - implementation will maintain some global counting, to call the CoInitialize
|
|
// API only once per thread
|
|
// - only made public for user convenience, e.g. when using custom COM objects
|
|
procedure CoInit;
|
|
|
|
/// this global procedure should be called at thread termination
|
|
// - it is called e.g. by TOleDBConnection.Destroy, when thread associated
|
|
// to an OleDb connection is terminated
|
|
// - every call of CoInit shall be followed by a call to CoUninit
|
|
// - only made public for user convenience, e.g. when using custom COM objects
|
|
procedure CoUninit;
|
|
|
|
/// retrieves the current executable module handle, i.e. its memory load address
|
|
// - redefined in mormot.core.os to avoid dependency to the Windows unit
|
|
function GetModuleHandle(lpModuleName: PChar): HMODULE;
|
|
|
|
/// post a message to the Windows message queue
|
|
// - redefined in mormot.core.os to avoid dependency to the Windows unit
|
|
function PostMessage(hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): BOOL;
|
|
|
|
/// retrieves the current stack trace
|
|
// - only available since Windows XP
|
|
// - FramesToSkip + FramesToCapture should be <= 62
|
|
function RtlCaptureStackBackTrace(FramesToSkip, FramesToCapture: cardinal;
|
|
BackTrace, BackTraceHash: pointer): byte; stdcall;
|
|
|
|
/// retrieves the current thread ID
|
|
// - redefined in mormot.core.os to avoid dependency to the Windows unit
|
|
function GetCurrentThreadId: DWORD; stdcall;
|
|
|
|
/// retrieves the current process ID
|
|
// - redefined in mormot.core.os to avoid dependency to the Windows unit
|
|
function GetCurrentProcessId: DWORD; stdcall;
|
|
|
|
/// retrieves the current process ID
|
|
// - redefined in mormot.core.os to avoid dependency to the Windows unit
|
|
function GetCurrentProcess: THandle; stdcall;
|
|
|
|
/// redefined in mormot.core.os to avoid dependency to the Windows unit
|
|
function WaitForSingleObject(hHandle: THandle; dwMilliseconds: DWORD): DWORD; stdcall;
|
|
|
|
/// redefined in mormot.core.os to avoid dependency to the Windows unit
|
|
function GetEnvironmentStringsW: PWideChar; stdcall;
|
|
|
|
/// redefined in mormot.core.os to avoid dependency to the Windows unit
|
|
function FreeEnvironmentStringsW(EnvBlock: PWideChar): BOOL; stdcall;
|
|
|
|
/// expand any embedded environment variables, i.e %windir%
|
|
function ExpandEnvVars(const aStr: string): string;
|
|
|
|
/// try to enter a Critical Section (Lock)
|
|
// - returns 1 if the lock was acquired, or 0 if the mutex is already locked
|
|
// - redefined in mormot.core.os to avoid dependency to the Windows unit
|
|
// - under Delphi/Windows, directly call the homonymous Win32 API
|
|
function TryEnterCriticalSection(var cs: TRTLCriticalSection): integer; stdcall;
|
|
|
|
/// enter a Critical Section (Lock)
|
|
// - redefined in mormot.core.os to avoid dependency to the Windows unit
|
|
// - under Delphi/Windows, directly call the homonymous Win32 API
|
|
procedure EnterCriticalSection(var cs: TRTLCriticalSection); stdcall;
|
|
|
|
/// leave a Critical Section (UnLock)
|
|
// - redefined in mormot.core.os to avoid dependency to the Windows unit
|
|
// - under Delphi/Windows, directly call the homonymous Win32 API
|
|
procedure LeaveCriticalSection(var cs: TRTLCriticalSection); stdcall;
|
|
|
|
/// initialize Windows IOCP instance
|
|
// - renamed in mormot.core.os to avoid dependency to the Windows unit
|
|
function IocpCreate(FileHandle, ExistingCompletionPort: THandle;
|
|
CompletionKey: pointer; NumberOfConcurrentThreads: DWORD): THandle; stdcall;
|
|
|
|
/// retrieve Windows IOCP instance status
|
|
// - renamed in mormot.core.os to avoid dependency to the Windows unit
|
|
function IocpGetQueuedStatus(CompletionPort: THandle;
|
|
var lpNumberOfBytesTransferred: DWORD; var lpCompletionKey: pointer;
|
|
var lpOverlapped: pointer; dwMilliseconds: DWORD): BOOL; stdcall;
|
|
|
|
/// trigger a Windows IOCP instance
|
|
// - renamed in mormot.core.os to avoid dependency to the Windows unit
|
|
function IocpPostQueuedStatus(CompletionPort: THandle;
|
|
NumberOfBytesTransferred: DWORD; dwCompletionKey: pointer;
|
|
lpOverlapped: POverlapped): BOOL; stdcall;
|
|
|
|
/// finalize a Windows resource (e.g. IOCP instance)
|
|
// - redefined in mormot.core.os to avoid dependency to the Windows unit
|
|
function CloseHandle(hObject: THandle): BOOL; stdcall;
|
|
|
|
/// redefined here to avoid warning to include "Windows" in uses clause
|
|
// - why did Delphi define this slow RTL function as inlined in SysUtils.pas?
|
|
// - also supports aFileName longer than MAX_PATH
|
|
// - on Windows, aRights parameter is just ignored, and on POSIX aRights = 0
|
|
// will set the default octal 644 file access attributes (-rw-r-r--)
|
|
// - warning: this function replaces ALL SysUtils.FileCreate() overloads,
|
|
// putting aMode as the SECOND parameter, just like with FileOpen()
|
|
function FileCreate(const aFileName: TFileName; aMode: integer = 0;
|
|
aRights: integer = 0): THandle;
|
|
|
|
/// redefined here to call CreateFileW() on non-Unicode RTL and support
|
|
// aFileName longer than MAX_PATH
|
|
function FileOpen(const aFileName: TFileName; aMode: integer): THandle;
|
|
|
|
/// redefined here to avoid warning to include "Windows" in uses clause
|
|
// - why did Delphi define this slow RTL function as inlined in SysUtils.pas?
|
|
procedure FileClose(F: THandle); stdcall;
|
|
|
|
/// redefined here to support FileName longer than MAX_PATH
|
|
// - as our FileOpen/FileCreate redefinitions
|
|
// - CheckAsDir = true is used by DirectoryExists()
|
|
function FileExists(const FileName: TFileName; FollowLink: boolean = true;
|
|
CheckAsDir: boolean = false): boolean;
|
|
|
|
/// redefined here to support FileName longer than MAX_PATH
|
|
function DirectoryExists(const FileName: TFileName;
|
|
FollowLink: boolean = true): boolean; {$ifdef HASINLINE} inline; {$endif}
|
|
|
|
/// redefined here to avoid warning to include "Windows" in uses clause
|
|
// and support FileName longer than MAX_PATH
|
|
// - why did Delphi define this slow RTL function as inlined in SysUtils.pas?
|
|
function DeleteFile(const aFileName: TFileName): boolean;
|
|
|
|
/// redefined here to avoid warning to include "Windows" in uses clause
|
|
// and support FileName longer than MAX_PATH
|
|
// - why did Delphi define this slow RTL function as inlined in SysUtils.pas?
|
|
function RenameFile(const OldName, NewName: TFileName): boolean;
|
|
|
|
/// redirection to Windows SetFileTime() of a file name from Int64(TFileTime)
|
|
// - if any Int64 is 0, the proper value will be guess from the non-0 values
|
|
function FileSetTime(const FileName: TFileName;
|
|
const Created, Accessed, Written: Int64): boolean;
|
|
|
|
{$else}
|
|
|
|
/// faster cross-platform alternative to sysutils homonymous function
|
|
// - will directly use fpstat() so is slightly faster than default FPC RTL
|
|
function FileExists(const FileName: TFileName): boolean;
|
|
|
|
/// redefined from FPC RTL sysutils for consistency
|
|
// - warning: this function replaces ALL SysUtils.FileCreate() overloads,
|
|
// putting aMode as the SECOND parameter, just like with FileOpen()
|
|
// - on POSIX, aRights = 0 will set default octal 644 attributes (-rw-r-r--)
|
|
function FileCreate(const aFileName: TFileName; aMode: integer = 0;
|
|
aRights: integer = 0): THandle;
|
|
|
|
/// returns how many files could be opened at once on this POSIX system
|
|
// - hard=true is for the maximum allowed limit, false for the current process
|
|
// - returns -1 if the getrlimit() API call failed
|
|
function GetFileOpenLimit(hard: boolean = false): integer;
|
|
|
|
/// changes how many files could be opened at once on this POSIX system
|
|
// - hard=true is for the maximum allowed limit (requires root priviledges),
|
|
// false for the current process
|
|
// - returns the new value set (may not match the expected max value on error)
|
|
// - returns -1 if the getrlimit().setrlimit() API calls failed
|
|
// - for instance, to set the limit of the current process to its highest value:
|
|
// ! SetFileOpenLimit(GetFileOpenLimit(true));
|
|
function SetFileOpenLimit(max: integer; hard: boolean = false): integer;
|
|
|
|
/// read /proc/pid/status to ensure pid is of a real process, not a thread
|
|
function IsValidPid(pid: cardinal): boolean;
|
|
|
|
type
|
|
/// Low-level access to the ICU library installed on this system
|
|
// - "International Components for Unicode" (ICU) is an open-source set of
|
|
// libraries for Unicode support, internationalization and globalization
|
|
// - used by Unicode_CompareString, Unicode_AnsiToWide, Unicode_WideToAnsi,
|
|
// Unicode_InPlaceUpper and Unicode_InPlaceLower function from this unit
|
|
TIcuLibrary = packed object
|
|
protected
|
|
icu, icudata, icui18n: pointer;
|
|
Loaded: boolean;
|
|
procedure DoLoad(const LibName: TFileName = ''; Version: string = '');
|
|
procedure Done;
|
|
public
|
|
/// Initialize an ICU text converter for a given encoding
|
|
ucnv_open: function (converterName: PAnsiChar; var err: SizeInt): pointer; cdecl;
|
|
/// finalize the ICU text converter for a given encoding
|
|
ucnv_close: procedure (converter: pointer); cdecl;
|
|
/// customize the ICU text converter substitute char
|
|
ucnv_setSubstChars: procedure (converter: pointer;
|
|
subChars: PAnsiChar; len: byte; var err: SizeInt); cdecl;
|
|
/// enable the ICU text converter fallback
|
|
ucnv_setFallback: procedure (cnv: pointer; usesFallback: LongBool); cdecl;
|
|
/// ICU text conversion from UTF-16 to a given encoding
|
|
ucnv_fromUChars: function (cnv: pointer; dest: PAnsiChar; destCapacity: cardinal;
|
|
src: PWideChar; srcLength: cardinal; var err: SizeInt): cardinal; cdecl;
|
|
/// ICU text conversion from a given encoding to UTF-16
|
|
ucnv_toUChars: function (cnv: pointer; dest: PWideChar; destCapacity: cardinal;
|
|
src: PAnsiChar; srcLength: cardinal; var err: SizeInt): cardinal; cdecl;
|
|
/// ICU UTF-16 text conversion to uppercase
|
|
u_strToUpper: function (dest: PWideChar; destCapacity: cardinal;
|
|
src: PWideChar; srcLength: cardinal; locale: PAnsiChar;
|
|
var err: SizeInt): cardinal; cdecl;
|
|
/// ICU UTF-16 text conversion to lowercase
|
|
u_strToLower: function (dest: PWideChar; destCapacity: cardinal;
|
|
src: PWideChar; srcLength: cardinal; locale: PAnsiChar;
|
|
var err: SizeInt): cardinal; cdecl;
|
|
/// ICU UTF-16 text comparison
|
|
u_strCompare: function (s1: PWideChar; length1: cardinal;
|
|
s2: PWideChar; length2: cardinal; codePointOrder: LongBool): cardinal; cdecl;
|
|
/// ICU UTF-16 text comparison with options, e.g. for case-insensitivity
|
|
u_strCaseCompare: function (s1: PWideChar; length1: cardinal;
|
|
s2: PWideChar; length2: cardinal; options: cardinal;
|
|
var err: SizeInt): cardinal; cdecl;
|
|
/// get the ICU data folder
|
|
u_getDataDirectory: function: PAnsiChar; cdecl;
|
|
/// set the ICU data folder
|
|
u_setDataDirectory: procedure(directory: PAnsiChar); cdecl;
|
|
/// initialize the ICU library
|
|
u_init: procedure(var status: SizeInt); cdecl;
|
|
/// try to initialize a specific version of the ICU library
|
|
// - first finalize any existing loaded instance
|
|
// - returns true if was successfully loaded and setup
|
|
function ForceLoad(const LibName: TFileName; const Version: string): boolean;
|
|
/// returns TRUE if a ICU library is available on this system
|
|
// - will thread-safely load and initialize it if necessary
|
|
function IsAvailable: boolean; inline;
|
|
/// Initialize an ICU text converter for a given codepage
|
|
// - returns nil if ICU is not available on this system
|
|
// - wrapper around ucnv_open/ucnv_setSubstChars/ucnv_setFallback calls
|
|
// - caller should make ucnv_close() once done with the returned instance
|
|
function ucnv(codepage: cardinal): pointer;
|
|
end;
|
|
|
|
var
|
|
/// low-level late-binding access to any installed ICU library
|
|
// - typical use is to check icu.IsAvailable then the proper icu.*() functions
|
|
// - this unit will make icu.Done in its finalization section
|
|
icu: TIcuLibrary;
|
|
|
|
/// contains the current POSIX kernel revision, as one 24-bit integer
|
|
// - allow quick comparison mainly for kernel feature checking
|
|
// - e.g. on Linux, may equal $030d02 for 3.13.2, or $020620 for 2.6.32
|
|
KernelRevision: cardinal;
|
|
|
|
|
|
{$ifdef OSLINUX} { some Linux-specific APIs (e.g. systemd or eventfd) }
|
|
|
|
const
|
|
/// The first passed file descriptor is fd 3
|
|
SD_LISTEN_FDS_START = 3;
|
|
|
|
/// low-level libcurl library file name, depending on the running OS
|
|
LIBSYSTEMD_PATH = 'libsystemd.so.0';
|
|
|
|
ENV_INVOCATION_ID: PAnsiChar = 'INVOCATION_ID';
|
|
|
|
type
|
|
/// low-level systemd parameter to sd.journal_sendv() function
|
|
TIoVec = record
|
|
iov_base: PAnsiChar;
|
|
iov_len: PtrUInt;
|
|
end;
|
|
|
|
/// implements late-binding of the systemd library
|
|
// - about systemd: see https://www.freedesktop.org/wiki/Software/systemd
|
|
// and http://0pointer.de/blog/projects/socket-activation.html - to get headers
|
|
// on debian: `sudo apt install libsystemd-dev && cd /usr/include/systemd`
|
|
TSystemD = record
|
|
private
|
|
systemd: pointer;
|
|
tested: boolean;
|
|
procedure DoLoad;
|
|
public
|
|
/// returns how many file descriptors have been passed to process
|
|
// - if result=1 then socket for accepting connection is LISTEN_FDS_START
|
|
listen_fds: function(unset_environment: integer): integer; cdecl;
|
|
/// returns 1 if the file descriptor is an AF_UNIX socket of the specified type and path
|
|
is_socket_unix: function(fd, typr, listening: integer;
|
|
var path: TFileName; pathLength: PtrUInt): integer; cdecl;
|
|
/// systemd: submit simple, plain text log entries to the system journal
|
|
// - priority value can be obtained using integer(LOG_TO_SYSLOG[logLevel])
|
|
journal_print: function(priority: integer; args: array of const): integer; cdecl;
|
|
/// systemd: submit array of iov structures instead of the format string to the system journal.
|
|
// - each structure should reference one field of the entry to submit
|
|
// - the second argument specifies the number of structures in the array
|
|
journal_sendv: function(var iov: TIoVec; n: integer): integer; cdecl;
|
|
/// sends notification to systemd
|
|
// - see https://www.freedesktop.org/software/systemd/man/notify.html
|
|
// status notification sample: sd.notify(0, 'READY=1');
|
|
// watchdog notification: sd.notify(0, 'WATCHDOG=1');
|
|
notify: function(unset_environment: integer; state: PUtf8Char): integer; cdecl;
|
|
/// check whether the service manager expects watchdog keep-alive
|
|
// notifications from a service
|
|
// - if result > 0 then usec contains the notification interval (app should
|
|
// notify every usec/2)
|
|
watchdog_enabled: function(unset_environment: integer; usec: Puint64): integer; cdecl;
|
|
/// returns true in case the current process was started by systemd
|
|
// - For systemd v232+
|
|
function ProcessIsStartedBySystemd: boolean;
|
|
/// returns TRUE if a systemd library is available
|
|
// - will thread-safely load and initialize it if necessary
|
|
function IsAvailable: boolean; inline;
|
|
/// release the systemd library
|
|
procedure Done;
|
|
end;
|
|
|
|
var
|
|
/// low-level late-binding of the systemd library
|
|
// - typical use is to check sd.IsAvailable then the proper sd.*() functions
|
|
// - this unit will make sd.Done in its finalization section
|
|
sd: TSystemD;
|
|
|
|
/// a wrapper to the eventfd() syscall
|
|
// - returns 0 if the kernel does not support eventfd2 (before 2.6.27) or
|
|
// if the platform is not supported (only validated on Linux x86_64 by now)
|
|
// - returns a file descriptor handle on success, which should be fpclose()
|
|
function LinuxEventFD(nonblocking, semaphore: boolean): integer;
|
|
|
|
/// wrapper to read from a eventfd() file
|
|
// - return 1 and decrement the counter by 1 in semaphore mode
|
|
// - return the current counter value and set it to 0 in non-semaphor mode
|
|
// - may be blocking or not blocking, depending on how LinuxEventFD() was called
|
|
// - return -1 on error
|
|
function LinuxEventFDRead(fd: integer): Int64;
|
|
|
|
/// wrapper to write to a eventfd() file
|
|
procedure LinuxEventFDWrite(fd: integer; count: QWord);
|
|
|
|
/// wrapper to wait for a eventfd() file read
|
|
// - return true if was notified for reading, or false on timeout
|
|
function LinuxEventFDWait(fd: integer; ms: integer): boolean; inline;
|
|
|
|
{$endif OSLINUX}
|
|
|
|
var
|
|
/// allow runtime-binding of complex OS API calls
|
|
// - used e.g. by mormot.core.os.mac.pas to inject its own methods
|
|
PosixInject: record
|
|
GetSmbios: function(info: TSmbiosBasicInfo): RawUtf8;
|
|
GetSmbiosData: function: RawByteString;
|
|
end;
|
|
|
|
{$endif OSWINDOWS}
|
|
|
|
|
|
{ ****************** Unicode, Time, File, Console, Library process }
|
|
|
|
{$ifdef OSWINDOWS}
|
|
|
|
type
|
|
/// redefined as our own mormot.core.os type to avoid dependency to Windows
|
|
// - warning: do not use this type directly, but rather TSynSystemTime as
|
|
// defined in mormot.core.datetime which is really cross-platform, and has
|
|
// consistent field order (FPC POSIX/Windows fields do not match!)
|
|
TSystemTime = Windows.TSystemTime;
|
|
PSystemTime = Windows.PSystemTime;
|
|
|
|
/// system-specific type returned by FileAge(): local 32-bit bitmask on Windows
|
|
TFileAge = integer;
|
|
|
|
{$ifdef ISDELPHI}
|
|
|
|
/// redefined as our own mormot.core.os type to avoid dependency to Windows
|
|
TRTLCriticalSection = Windows.TRTLCriticalSection;
|
|
|
|
/// defined as in FPC RTL, to avoid dependency to Windows.pas unit
|
|
// - note that on POSIX, a THandle is a 32-bit integer, but library or
|
|
// resource handles are likely to map pointers, i.e. up to a 64-bit integer
|
|
TLibHandle = THandle;
|
|
|
|
{$endif ISDELPHI}
|
|
|
|
/// handle for Slim Reader/Writer (SRW) locks in exclusive mode
|
|
TOSLightMutex = pointer;
|
|
|
|
/// a wrapper around FileTimeToLocalFileTime/FileTimeToSystemTime Windows APIs
|
|
// - only used by mormot.lib.static for proper SQlite3 linking on Windows
|
|
procedure UnixTimeToLocalTime(I64: TUnixTime; out Local: TSystemTime);
|
|
|
|
/// convert an Unix seconds time to a Win32 64-bit FILETIME value
|
|
procedure UnixTimeToFileTime(I64: TUnixTime; out FT: TFileTime);
|
|
|
|
/// convert an Unix milliseconds time to a Win32 64-bit FILETIME value
|
|
procedure UnixMSTimeToFileTime(I64: TUnixMSTime; out FT: TFileTime);
|
|
|
|
/// convert a TDateTime to a Win32 64-bit FILETIME value
|
|
procedure DateTimeToFileTime(dt: TDateTime; out FT: TFileTime);
|
|
|
|
/// convert a Win32 64-bit FILETIME value into an Unix seconds time
|
|
function FileTimeToUnixTime(const FT: TFileTime): TUnixTime;
|
|
{$ifdef FPC} inline; {$endif}
|
|
|
|
/// convert a Win32 64-bit FILETIME value into a TDateTime
|
|
function FileTimeToDateTime(const FT: TFileTime): TDateTime;
|
|
|
|
/// convert a Win32 64-bit FILETIME value into an Unix milliseconds time
|
|
function FileTimeToUnixMSTime(const FT: TFileTime): TUnixMSTime;
|
|
{$ifdef FPC} inline; {$endif}
|
|
|
|
var
|
|
// Slim Reader/Writer (SRW) API exclusive mode - fallback to TLightLock on XP
|
|
InitializeSRWLock,
|
|
AcquireSRWLockExclusive,
|
|
ReleaseSRWLockExclusive: procedure(var P: TOSLightMutex); stdcall;
|
|
TryAcquireSRWLockExclusive: function (var P: TOSLightMutex): BOOL; stdcall;
|
|
|
|
{$else}
|
|
|
|
const
|
|
/// a cross-platform incorrect THandle value, as defined in Windows unit
|
|
INVALID_HANDLE_VALUE = THandle(-1);
|
|
|
|
/// allow to assign proper signed symbol table name for a libc.so.6 method
|
|
{$ifdef OSLINUXX64}
|
|
LIBC_SUFFIX = '@GLIBC_2.2.5';
|
|
{$else}
|
|
{$ifdef OSLINUXX86}
|
|
LIBC_SUFFIX = '@GLIBC_2.0';
|
|
{$else}
|
|
LIBC_SUFFIX = ''; // no suffix seems needed outside of Intel/AMD systems
|
|
{$endif OSLINUXX86}
|
|
{$endif OSLINUXX64}
|
|
|
|
type
|
|
/// system-specific type returned by FileAge(): UTC 64-bit Epoch on POSIX
|
|
TFileAge = TUnixTime;
|
|
|
|
/// system-specific structure holding a non-recursive mutex
|
|
TOSLightMutex = TRTLCriticalSection;
|
|
|
|
{$ifdef OSLINUX}
|
|
{$define OSPTHREADSLIB} // direct pthread calls were tested on Linux only
|
|
{$endif OSLINUX}
|
|
{$ifdef OSDARWIN}
|
|
{$define OSPTHREADSSTATIC} // direct pthread calls from the 'c' library
|
|
{$endif OSDARWIN}
|
|
{$ifdef OSBSD}
|
|
{$define OSPTHREADSSTATIC} // direct pthread calls from the c library
|
|
{$endif OSBSD}
|
|
|
|
// some pthread_mutex_*() API defined here for proper inlining
|
|
{$ifdef OSPTHREADSLIB}
|
|
var
|
|
{%H-}pthread: pointer; // access to pthread.so e.g. for mormot.lib.static
|
|
pthread_mutex_lock: function(mutex: pointer): integer; cdecl;
|
|
pthread_mutex_trylock: function(mutex: pointer): integer; cdecl;
|
|
pthread_mutex_unlock: function(mutex: pointer): integer; cdecl;
|
|
{$endif OSPTHREADSLIB}
|
|
{$ifdef OSPTHREADSSTATIC}
|
|
function pthread_mutex_lock(mutex: pointer): integer; cdecl;
|
|
function pthread_mutex_trylock(mutex: pointer): integer; cdecl;
|
|
function pthread_mutex_unlock(mutex: pointer): integer; cdecl;
|
|
{$endif OSPTHREADSSTATIC}
|
|
|
|
{$endif OSWINDOWS}
|
|
|
|
/// raw cross-platform library loading function
|
|
// - alternative to LoadLibrary() and SafeLoadLibrary() Windows API and RTL
|
|
// - on Windows, set the SEM_NOOPENFILEERRORBOX and SEM_FAILCRITICALERRORS flags
|
|
// to avoid unexpected message boxes (which should not happen e.g. on a service)
|
|
// - on Win32, reset the FPU flags after load as required with some libraries
|
|
// - consider inheriting TSynLibrary if you want to map a set of API functions
|
|
function LibraryOpen(const LibraryName: TFileName): TLibHandle;
|
|
|
|
/// raw cross-platform library unloading function
|
|
// - alternative to FreeLibrary() Windows API and FPC RTL
|
|
procedure LibraryClose(Lib: TLibHandle);
|
|
|
|
/// raw cross-platform library resolution function, as defined in FPC RTL
|
|
// - alternative to GetProcAddr() Windows API and FPC RTL
|
|
function LibraryResolve(Lib: TLibHandle; ProcName: PAnsiChar): pointer;
|
|
{$ifdef OSWINDOWS} stdcall; {$endif}
|
|
|
|
/// raw cross-platform library resolution error, e.g. after LibraryOpen
|
|
function LibraryError: string;
|
|
|
|
|
|
const
|
|
/// redefined here to avoid dependency to the Windows or SyncObjs units
|
|
INFINITE = cardinal(-1);
|
|
|
|
/// initialize a Critical Section (for Lock/UnLock)
|
|
// - redefined in mormot.core.os to avoid dependency to the Windows unit
|
|
// - under Delphi/Windows, directly call the homonymous Win32 API
|
|
procedure InitializeCriticalSection(var cs : TRTLCriticalSection);
|
|
{$ifdef OSWINDOWS} stdcall; {$else} inline; {$endif}
|
|
|
|
/// finalize a Critical Section (for Lock/UnLock)
|
|
// - redefined in mormot.core.os to avoid dependency to the Windows unit
|
|
// - under Delphi/Windows, directly call the homonymous Win32 API
|
|
procedure DeleteCriticalSection(var cs : TRTLCriticalSection);
|
|
{$ifdef OSWINDOWS} stdcall; {$else} inline; {$endif}
|
|
|
|
{$ifdef OSPOSIX}
|
|
|
|
{$ifndef OSLINUX} // try to stabilize MacOS/BSD pthreads API calls
|
|
{$define NODIRECTTHREADMANAGER}
|
|
{$endif OSLINUX}
|
|
|
|
{$ifdef NODIRECTTHREADMANAGER} // try to stabilize MacOS pthreads API calls
|
|
function GetCurrentThreadId: TThreadID; inline;
|
|
function TryEnterCriticalSection(var cs: TRTLCriticalSection): integer; inline;
|
|
procedure EnterCriticalSection(var cs: TRTLCriticalSection); inline;
|
|
procedure LeaveCriticalSection(var cs: TRTLCriticalSection); inline;
|
|
{$else}
|
|
|
|
/// returns the unique ID of the current running thread
|
|
// - defined in mormot.core.os for inlined FpcCurrentThreadManager call
|
|
var GetCurrentThreadId: function: TThreadID;
|
|
|
|
/// enter a Critical Section (Lock)
|
|
// - defined in mormot.core.os for inlined FpcCurrentThreadManager call
|
|
var EnterCriticalSection: procedure(var cs: TRTLCriticalSection);
|
|
|
|
/// leave a Critical Section (UnLock)
|
|
// - defined in mormot.core.os for inlined FpcCurrentThreadManager call
|
|
var LeaveCriticalSection: procedure(var cs: TRTLCriticalSection);
|
|
|
|
/// try to acquire and lock a Critical Section (Lock)
|
|
// - returns 1 if the lock was acquired, or 0 if the mutex is already locked
|
|
// - defined in mormot.core.os for inlined FpcCurrentThreadManager call
|
|
var TryEnterCriticalSection: function(var cs: TRTLCriticalSection): integer;
|
|
|
|
{$endif NODIRECTTHREADMANAGER}
|
|
|
|
{$endif OSPOSIX}
|
|
|
|
/// returns TRUE if the supplied mutex has been initialized
|
|
// - will check if the supplied mutex is void (i.e. all filled with 0 bytes)
|
|
function IsInitializedCriticalSection(var cs: TRTLCriticalSection): boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// on need initialization of a mutex, then enter the lock
|
|
// - if the supplied mutex has been initialized, do nothing
|
|
// - if the supplied mutex is void (i.e. all filled with 0), initialize it
|
|
procedure InitializeCriticalSectionIfNeededAndEnter(var cs: TRTLCriticalSection);
|
|
{$ifdef HASINLINEWINAPI}inline;{$endif}
|
|
|
|
/// on need finalization of a mutex
|
|
// - if the supplied mutex has been initialized, delete it
|
|
// - if the supplied mutex is void (i.e. all filled with 0), do nothing
|
|
procedure DeleteCriticalSectionIfNeeded(var cs: TRTLCriticalSection);
|
|
|
|
/// returns the current UTC time as TSystemTime from the OS
|
|
// - under Delphi/Windows, directly call the homonymous Win32 API
|
|
// - redefined in mormot.core.os to avoid dependency to the Windows unit
|
|
// - under Linux/POSIX, calls clock_gettime(CLOCK_REALTIME_COARSE) if available
|
|
// or fpgettimeofday() on Darwin/MacOS
|
|
// - warning: do not call this function directly, but rather mormot.core.datetime
|
|
// TSynSystemTime.FromNowUtc cross-platform method instead
|
|
procedure GetSystemTime(out result: TSystemTime);
|
|
{$ifdef OSWINDOWS} stdcall; {$endif}
|
|
|
|
/// set the current system time as UTC timestamp
|
|
// - we define two functions with diverse signature to circumvent the FPC RTL
|
|
// TSystemTime field order inconsistency
|
|
// - warning: do not call this function directly, but rather mormot.core.datetime
|
|
// TSynSystemTime.ChangeOperatingSystemTime cross-platform method instead
|
|
{$ifdef OSWINDOWS}
|
|
function SetSystemTime(const utctime: TSystemTime): boolean;
|
|
{$else}
|
|
function SetSystemTime(utctime: TUnixTime): boolean;
|
|
{$endif OSWINDOWS}
|
|
|
|
/// returns the current Local time as TSystemTime from the OS
|
|
// - under Delphi/Windows, directly call the homonymous Win32 API
|
|
// - redefined in mormot.core.os to avoid dependency to the Windows unit
|
|
// - under Linux/POSIX, calls clock_gettime(CLOCK_REALTIME_COARSE) if available
|
|
// or fpgettimeofday() on Darwin/MacOS, with FPC RTL TZSeconds adjustment (so
|
|
// will be fixed for the whole process lifetime and won't change at daylight)
|
|
// - warning: do not call this function directly, but rather mormot.core.datetime
|
|
// TSynSystemTime.FromNowLocal cross-platform method instead
|
|
procedure GetLocalTime(out result: TSystemTime);
|
|
{$ifdef OSWINDOWS} stdcall; {$endif}
|
|
|
|
/// compatibility function, wrapping Win32 API file truncate at current position
|
|
procedure SetEndOfFile(F: THandle);
|
|
{$ifdef OSWINDOWS} stdcall; {$else} inline; {$endif}
|
|
|
|
/// compatibility function, wrapping Win32 API file flush to disk
|
|
procedure FlushFileBuffers(F: THandle);
|
|
{$ifdef OSWINDOWS} stdcall; {$else} inline; {$endif}
|
|
|
|
/// compatibility function, wrapping Win32 API last error code
|
|
function GetLastError: integer;
|
|
{$ifdef OSWINDOWS} stdcall; {$else} inline; {$endif}
|
|
|
|
/// check if the last error reporting by the system is a file access violation
|
|
// - call GetLastError is no ErrorCode is supplied
|
|
function IsSharedViolation(ErrorCode: integer = 0): boolean;
|
|
|
|
/// compatibility function, wrapping Win32 API last error code
|
|
procedure SetLastError(error: integer);
|
|
{$ifdef OSWINDOWS} stdcall; {$else} inline; {$endif}
|
|
|
|
/// returns a given error code as plain text
|
|
// - redirects to WinErrorText(error, nil) on Windows, or StrError() on POSIX
|
|
function GetErrorText(error: integer): RawUtf8;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
|
|
{$ifdef OSWINDOWS}
|
|
|
|
/// return the error message of a given Module
|
|
// - first try WinErrorConstant() for system error constants (if ModuleName=nil),
|
|
// then call FormatMessage() and override the RTL function to force the
|
|
// ENGLISH_LANGID flag first
|
|
// - if ModuleName does support this Code, will try it as system error
|
|
// - replace SysErrorMessagePerModule() and SysErrorMessage() from mORMot 1
|
|
function WinErrorText(Code: cardinal; ModuleName: PChar): RawUtf8;
|
|
|
|
/// return the best known ERROR_* system error message constant texts
|
|
// - without the 'ERROR_' prefix
|
|
// - as used by WinErrorText()
|
|
function WinErrorConstant(Code: cardinal): PUtf8Char;
|
|
|
|
/// raise an EOSException from the last system error using WinErrorText()
|
|
procedure RaiseLastError(const Context: shortstring;
|
|
RaisedException: ExceptClass = nil);
|
|
|
|
/// raise an Exception from the last module error using WinErrorText()
|
|
procedure RaiseLastModuleError(ModuleName: PChar; ModuleException: ExceptClass);
|
|
|
|
{$endif OSWINDOWS}
|
|
|
|
/// compatibility function, wrapping Win32 API function
|
|
// - returns the current main Window handle on Windows, or 0 on POSIX/Linux
|
|
function GetDesktopWindow: PtrInt;
|
|
{$ifdef OSWINDOWS} stdcall; {$else} inline; {$endif}
|
|
|
|
/// returns the curent system code page for AnsiString types
|
|
// - as used to initialize CurrentAnsiConvert in mormot.core.unicode unit
|
|
// - calls GetACP() Win32 API value on Delphi, or DefaultSystemCodePage on FPC -
|
|
// i.e. GetSystemCodePage() on POSIX (likely to be UTF-8) or the value used
|
|
// by the LCL for its "string" types (also typically UTF-8 even on Windows)
|
|
function Unicode_CodePage: integer;
|
|
{$ifdef FPC} inline; {$endif}
|
|
|
|
/// compatibility function, wrapping CompareStringW() Win32 API text comparison
|
|
// - returns 1 if PW1>PW2, 2 if PW1=PW2, 3 if PW1<PW2 - so substract 2 to have
|
|
// -1,0,1 as regular StrCompW/StrICompW comparison function result
|
|
// - will compute StrLen(PW1/PW2) if L1 or L2 < 0
|
|
// - on POSIX, use the ICU library, or fallback to FPC RTL widestringmanager
|
|
// with a temporary variable - you would need to include cwstring unit
|
|
// - in practice, is seldom called, unless our proprietary WIN32CASE collation
|
|
// is used in mormot.db.raw.sqlite3
|
|
// - consider Utf8ILCompReference() from mormot.core.unicode.pas for an
|
|
// operating-system-independent Unicode 10.0 comparison function
|
|
function Unicode_CompareString(
|
|
PW1, PW2: PWideChar; L1, L2: PtrInt; IgnoreCase: boolean): integer;
|
|
|
|
/// compatibility function, wrapping MultiByteToWideChar() Win32 API call
|
|
// - returns the number of WideChar written into W^ destination buffer
|
|
// - on POSIX, use the ICU library, or fallback to FPC RTL widestringmanager
|
|
// with a temporary variable - you would need to include cwstring unit
|
|
// - raw function called by TSynAnsiConvert.AnsiBufferToUnicode from
|
|
// mormot.core.unicode unit
|
|
function Unicode_AnsiToWide(
|
|
A: PAnsiChar; W: PWideChar; LA, LW, CodePage: PtrInt): integer;
|
|
|
|
/// compatibility function, wrapping WideCharToMultiByte() Win32 API call
|
|
// - returns the number of AnsiChar written into A^ destination buffer
|
|
// - on POSIX, use the ICU library, or fallback to FPC RTL widestringmanager
|
|
// with a temporary variable - you would need to include cwstring unit
|
|
// - raw function called by TSynAnsiConvert.UnicodeBufferToAnsi from
|
|
// mormot.core.unicode unit
|
|
function Unicode_WideToAnsi(
|
|
W: PWideChar; A: PAnsiChar; LW, LA, CodePage: PtrInt): integer;
|
|
|
|
/// conversion of some UTF-16 buffer into a temporary Ansi ShortString
|
|
// - used when mormot.core.unicode is an overkill, e.g. TCrtSocket.SockSend()
|
|
procedure Unicode_WideToShort(
|
|
W: PWideChar; LW, CodePage: PtrInt; var res: ShortString);
|
|
|
|
/// compatibility function, wrapping Win32 API CharUpperBuffW()
|
|
// - on POSIX, use the ICU library, or fallback to 'a'..'z' conversion only
|
|
// - raw function called by UpperCaseUnicode() from mormot.core.unicode unit
|
|
function Unicode_InPlaceUpper(W: PWideChar; WLen: integer): integer;
|
|
{$ifdef OSWINDOWS} stdcall; {$endif}
|
|
|
|
/// compatibility function, wrapping Win32 API CharLowerBuffW()
|
|
// - on POSIX, use the ICU library, or fallback to 'A'..'Z' conversion only
|
|
// - raw function called by LowerCaseUnicode() from mormot.core.unicode unit
|
|
function Unicode_InPlaceLower(W: PWideChar; WLen: integer): integer;
|
|
{$ifdef OSWINDOWS} stdcall; {$endif}
|
|
|
|
/// returns a system-wide current monotonic timestamp as milliseconds
|
|
// - will use the corresponding native API function under Vista+, or will be
|
|
// redirected to a custom wrapper function for older Windows versions (XP)
|
|
// to avoid the 32-bit overflow/wrapping issue of GetTickCount
|
|
// - warning: FPC's SysUtils.GetTickCount64 or TThread.GetTickCount64 don't
|
|
// handle properly 49 days wrapping under XP -> always use this safe version
|
|
// - warning: FPC's SysUtils.GetTickCount64 may call fpgettimeofday() e.g.
|
|
// on Darwin, which is not monotonic -> always use this more coherent version
|
|
// - on POSIX, will call (via vDSO) the very fast CLOCK_MONOTONIC_COARSE if
|
|
// available, or the low-level mach_absolute_time() monotonic Darwin API
|
|
// - do not expect exact millisecond resolution - steps may rather be e.g.
|
|
// within the 15-16 ms range on Windows, and 4-5 ms range on Linux
|
|
{$ifdef OSWINDOWS}
|
|
var
|
|
GetTickCount64: function: Int64; stdcall;
|
|
{$else}
|
|
function GetTickCount64: Int64;
|
|
{$endif OSWINDOWS}
|
|
|
|
/// returns how many seconds the system was up, accouting for time when
|
|
// the computer is asleep
|
|
// - on Windows, computes GetTickCount64 div 1000
|
|
// - on Linux/BSD, will use CLOCK_BOOTTIME/CLOCK_UPTIME clock
|
|
// - on MacOS, will use mach_continuous_time() API
|
|
function GetUptimeSec: cardinal;
|
|
|
|
/// returns the current UTC time
|
|
// - wrap UnixMSTimeUtcFast, so use e.g. clock_gettime(CLOCK_REALTIME_COARSE)
|
|
// under Linux, or GetSystemTimeAsFileTime under Windows
|
|
function NowUtc: TDateTime;
|
|
|
|
/// returns the current UTC date/time as a second-based c-encoded time
|
|
// - i.e. current number of seconds elapsed since Unix epoch 1/1/1970
|
|
// - use e.g. fast clock_gettime(CLOCK_REALTIME_COARSE) under Linux,
|
|
// or GetSystemTimeAsFileTime under Windows
|
|
// - returns a 64-bit unsigned value, so is "Year2038bug" free
|
|
function UnixTimeUtc: TUnixTime;
|
|
|
|
/// returns the current UTC date/time as a millisecond-based c-encoded time
|
|
// - i.e. current number of milliseconds elapsed since Unix epoch 1/1/1970
|
|
// - will use e.g. fast clock_gettime(CLOCK_REALTIME_COARSE) under Linux,
|
|
// or GetSystemTimePreciseAsFileTime under Windows 8 and later
|
|
// - on Windows, is slightly more accurate, but slower than UnixMSTimeUtcFast
|
|
function UnixMSTimeUtc: TUnixMSTime;
|
|
|
|
/// returns the current UTC date/time as a millisecond-based c-encoded time
|
|
// - under Linux/POSIX, is the very same than UnixMSTimeUtc (inlined call)
|
|
// - under Windows 8+, will call GetSystemTimeAsFileTime instead of
|
|
// GetSystemTimePreciseAsFileTime, which has higher precision, but is slower
|
|
// - prefer it under Windows, if a dozen of ms resolution is enough for your task
|
|
function UnixMSTimeUtcFast: TUnixMSTime;
|
|
{$ifdef OSPOSIX} inline; {$endif}
|
|
|
|
const
|
|
/// number of days offset between the Unix Epoch (1970) and TDateTime origin
|
|
UnixDelta = 25569;
|
|
/// number of Windows TFileTime ticks (100ns) from year 1601 to 1970
|
|
UnixFileTimeDelta = 116444736000000000;
|
|
|
|
/// the number of minutes bias in respect to UTC/GMT date/time
|
|
// - as retrieved via -GetLocalTimeOffset() at startup, so may not be accurate
|
|
// after a time shift during the process execution - but any long-running
|
|
// process (like a service) should use UTC timestamps only
|
|
var
|
|
TimeZoneLocalBias: integer;
|
|
|
|
{$ifndef NOEXCEPTIONINTERCEPT}
|
|
|
|
type
|
|
/// calling context when intercepting exceptions
|
|
// - used e.g. for TSynLogExceptionToStr or RawExceptionIntercept() handlers
|
|
{$ifdef USERECORDWITHMETHODS}
|
|
TSynLogExceptionContext = record
|
|
{$else}
|
|
TSynLogExceptionContext = object
|
|
{$endif USERECORDWITHMETHODS}
|
|
public
|
|
/// the raised exception class
|
|
EClass: ExceptClass;
|
|
/// the Delphi Exception instance
|
|
// - may be nil for external/OS exceptions
|
|
EInstance: Exception;
|
|
/// the OS-level exception code
|
|
// - could be $0EEDFAE0 of $0EEDFADE for Delphi-generated exceptions
|
|
ECode: DWord;
|
|
/// = FPC's RaiseProc() FrameCount if EStack is Frame: PCodePointer
|
|
EStackCount: integer;
|
|
/// the address where the exception occurred
|
|
EAddr: PtrUInt;
|
|
/// the optional stack trace
|
|
EStack: PPtrUIntArray;
|
|
/// timestamp of this exception, as number of seconds since UNIX Epoch
|
|
// - UnixTimeUtc is faster than NowUtc or GetSystemTime
|
|
// - use UnixTimeToDateTime() to convert it into a regular TDateTime
|
|
ETimestamp: TUnixTime;
|
|
/// the logging level corresponding to this exception
|
|
// - may be either sllException or sllExceptionOS
|
|
ELevel: TSynLogLevel;
|
|
/// retrieve some extended information about a given Exception
|
|
// - on Windows, recognize most DotNet CLR Exception Names
|
|
function AdditionalInfo(out ExceptionNames: TPUtf8CharDynArray): cardinal;
|
|
end;
|
|
|
|
/// the global function signature expected by RawExceptionIntercept()
|
|
// - assigned e.g. to SynLogException() in mormot.core.log.pas
|
|
TOnRawLogException = procedure(const Ctxt: TSynLogExceptionContext);
|
|
|
|
/// setup Exception interception for the whole process
|
|
// - call RawExceptionIntercept(nil) to disable custom exception handling
|
|
procedure RawExceptionIntercept(const Handler: TOnRawLogException);
|
|
|
|
{$endif NOEXCEPTIONINTERCEPT}
|
|
|
|
/// returns a high-resolution system-wide monotonic timestamp as microseconds
|
|
// - under Linux/POSIX, has true microseconds resolution, calling e.g.
|
|
// CLOCK_MONOTONIC on Linux/BSD
|
|
// - under Windows, calls QueryPerformanceCounter / QueryPerformanceFrequency
|
|
procedure QueryPerformanceMicroSeconds(out Value: Int64);
|
|
|
|
/// cross-platform check if the supplied THandle is not invalid
|
|
function ValidHandle(Handle: THandle): boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// check for unsafe '..' '/xxx' 'c:xxx' '~/xxx' or '\\' patterns in a path
|
|
function SafePathName(const Path: TFileName): boolean;
|
|
|
|
/// check for unsafe '..' '/xxx' 'c:xxx' '~/xxx' or '\\' patterns in a RawUtf8 path
|
|
function SafePathNameU(const Path: RawUtf8): boolean;
|
|
|
|
/// check for unsafe '..' '/xxx' 'c:xxx' '~/xxx' or '\\' patterns in a filename
|
|
function SafeFileName(const FileName: TFileName): boolean;
|
|
|
|
/// check for unsafe '..' '/xxx' 'c:xxx' '~/xxx' or '\\' patterns in a RawUtf8 filename
|
|
function SafeFileNameU(const FileName: RawUtf8): boolean;
|
|
|
|
/// ensure all \ / path delimiters are normalized into the current OS expectation
|
|
// - i.e. normalize file name to use '\' on Windows, or '/' on POSIX
|
|
// - see MakePath() from mormot.core.text.pas to concatenate path items
|
|
function NormalizeFileName(const FileName: TFileName): TFileName;
|
|
|
|
/// add some " before and after if FileName has some space within
|
|
// - could be used when generating command line parameters
|
|
function QuoteFileName(const FileName: TFileName): TFileName;
|
|
|
|
/// faster cross-platform alternative to sysutils homonymous function
|
|
// - on Windows, just redirect to WindowsFileTimeToDateTime() since FileDate
|
|
// is already expected to be in local time from FileAge()
|
|
// - on POSIX, FileDate is a 64-bit UTC value as returned from OS stat API, and
|
|
// will be converted into a local TDateTime
|
|
// - note: FPC FileAge(TDateTime) is wrong and truncates 1-2 seconds on Windows
|
|
function FileDateToDateTime(const FileDate: TFileAge): TDateTime;
|
|
{$ifdef HASINLINE}{$ifdef OSWINDOWS}inline;{$endif}{$endif}
|
|
|
|
/// get a file date and time, from its name
|
|
// - returns 0 if file doesn't exist
|
|
// - returns the local file age, encoded as TDateTime
|
|
// - under Windows, will use GetFileAttributesEx fast API
|
|
function FileAgeToDateTime(const FileName: TFileName): TDateTime;
|
|
|
|
/// get a file date and time, from its name, as seconds since Unix Epoch
|
|
// - returns 0 if file (or folder if AllowDir is true) doesn't exist
|
|
// - returns the system API file age (not converted local), encoded as TUnixTime
|
|
// - under Windows, will use GetFileAttributesEx and FileTimeToUnixTime
|
|
// - under POSIX, will call directly the stat syscall
|
|
// - faster than FileAgeToDateTime() since don't convert to local time
|
|
function FileAgeToUnixTimeUtc(const FileName: TFileName;
|
|
AllowDir: boolean = false): TUnixTime;
|
|
|
|
/// get the date and time of one file into a Windows File 32-bit TimeStamp
|
|
// - this cross-system function is used e.g. by mormot.core.zip which expects
|
|
// Windows TimeStamps in its headers
|
|
function FileAgeToWindowsTime(const FileName: TFileName): integer;
|
|
|
|
/// copy the date of one file to another
|
|
// - FileSetDate(THandle, Age) is not implemented on POSIX: filename is needed
|
|
function FileSetDateFrom(const Dest: TFileName; SourceHandle: THandle): boolean; overload;
|
|
|
|
/// copy the date of one file to another
|
|
// - FileSetDate(THandle, Age) is not implemented on POSIX: filename is needed
|
|
function FileSetDateFrom(const Dest, Source: TFileName): boolean; overload;
|
|
|
|
/// copy the date of one file from a Windows File 32-bit TimeStamp
|
|
// - this cross-system function is used e.g. by mormot.core.zip which expects
|
|
// Windows TimeStamps in its headers
|
|
// - FileSetDate(THandle, Age) is not implemented on POSIX: filename is needed
|
|
function FileSetDateFromWindowsTime(const Dest: TFileName; WinTime: integer): boolean;
|
|
|
|
/// set the file date/time from a supplied UTC TUnixTime value
|
|
// - avoid any temporary conversion to local time
|
|
// - Time may come from FileAgeToUnixTimeUtc()
|
|
function FileSetDateFromUnixUtc(const Dest: TFileName; Time: TUnixTime): boolean;
|
|
|
|
/// convert a Windows API File 32-bit TimeStamp into a regular TDateTime
|
|
// - returns 0 if the conversion failed
|
|
// - used e.g. by FileSetDateFromWindowsTime() on POSIX
|
|
function WindowsFileTimeToDateTime(WinTime: integer): TDateTime;
|
|
|
|
/// convert a Windows API File 64-bit TimeStamp into a regular TUnixMSTime
|
|
// - i.e. a FILETIME value as returned by GetFileTime() Win32 API
|
|
// - some binary formats (e.g. ISO 9660 or LDAP) have such FILETIME fields
|
|
function WindowsFileTime64ToUnixMSTime(WinTime: QWord): TUnixMSTime;
|
|
|
|
/// low-level conversion of a TDateTime into a Windows File 32-bit TimeStamp
|
|
// - returns 0 if the conversion failed
|
|
function DateTimeToWindowsFileTime(DateTime: TDateTime): integer;
|
|
|
|
/// check if a file exists and can be written
|
|
// - on POSIX, call fpaccess() and check for the W_OK attribute
|
|
// - on Windows, supports aFileName longer than MAX_PATH
|
|
function FileIsWritable(const FileName: TFileName): boolean;
|
|
|
|
/// reduce the visibility of a given file, and set its read/write attributes
|
|
// - on POSIX, change attributes for the the owner, and reset group/world flags
|
|
// so that it is accessible by the current user only; under POSIX, there is
|
|
// no "hidden" file attribute, but you should define a FileName starting by '.'
|
|
// - on Windows, will set the "hidden" file attribue
|
|
procedure FileSetHidden(const FileName: TFileName; ReadOnly: boolean);
|
|
|
|
/// set the "sticky bit" on a file or directory
|
|
// - on POSIX, a "sticky" folder will ensure that its nested files will be
|
|
// deleted by their owner; and a "sticky" file will ensure e.g. that no
|
|
// /var/tmp file is deleted by systemd during its clean up phases
|
|
// - on Windows, will set the Hidden and System file attributes
|
|
procedure FileSetSticky(const FileName: TFileName);
|
|
|
|
/// get a file size, from its name
|
|
// - returns 0 if file doesn't exist, or is a directory
|
|
// - under Windows, will use GetFileAttributesEx fast API
|
|
// - on POSIX, will use efficient fpStat() single call but not FileOpen/FileClose
|
|
function FileSize(const FileName: TFileName): Int64; overload;
|
|
|
|
/// get a file size, from its handle
|
|
// - returns 0 if file doesn't exist
|
|
// - under Windows, will use the GetFileSizeEx fast API
|
|
// - on POSIX, will use efficient FpFStat() single call and no file seek
|
|
function FileSize(F: THandle): Int64; overload;
|
|
|
|
/// FileSeek() overloaded function, working with huge files
|
|
// - Delphi FileSeek() is buggy -> use this function to safely access files
|
|
// bigger than 2 GB (thanks to sanyin for the report)
|
|
function FileSeek64(Handle: THandle; const Offset: Int64;
|
|
Origin: cardinal = soFromBeginning): Int64;
|
|
|
|
/// get a file size and its UTC Unix timestamp in milliseconds resolution
|
|
// - return false if FileName was not found
|
|
// - return true and set FileSize and FileTimestampUtc if found - note that
|
|
// no local time conversion is done, so timestamp won't match FileAge()
|
|
// - use a single Operating System call, so is faster than FileSize + FileAge
|
|
function FileInfoByName(const FileName: TFileName; out FileSize: Int64;
|
|
out FileTimestampUtc: TUnixMSTime): boolean;
|
|
|
|
/// get low-level file information, in a cross-platform way
|
|
// - returns true on success
|
|
// - you can specify nil for any returned value if you don't need
|
|
// - here file write/creation time are given as TUnixMSTime values, for better
|
|
// cross-platform process - note that FileCreateDateTime may not be supported
|
|
// by most Linux file systems, so the oldest timestamp available is returned
|
|
// as failover on such systems (probably the latest file metadata writing)
|
|
function FileInfoByHandle(aFileHandle: THandle; FileId, FileSize: PInt64;
|
|
LastWriteAccess, FileCreateDateTime: PUnixMSTime): boolean;
|
|
|
|
/// check if a given file is likely to be an executable
|
|
// - will check the DOS/WinPE executable header in its first bytes on Windows
|
|
// - will call fpStat() on POSIX to check the File and Executable bits
|
|
function FileIsExecutable(const FileName: TFileName): boolean;
|
|
|
|
/// compute the size of a directory's files, optionally with nested folders
|
|
// - basic implementation using FindFirst/FindNext so won't be the fastest
|
|
// available, nor fully accurate when files are actually (hard) links
|
|
function DirectorySize(const FileName: TFileName; Recursive: boolean = false;
|
|
const Mask: TFileName = FILES_ALL): Int64;
|
|
|
|
/// copy one file to another, similar to the Windows API
|
|
function CopyFile(const Source, Target: TFileName;
|
|
FailIfExists: boolean): boolean;
|
|
|
|
/// prompt the user for an error message to notify an unexpected issue
|
|
// - in practice, text encoding is expected to be plain 7-bit ASCII
|
|
// - on Windows, will use Writeln() on a (newly allocated if needed) console
|
|
// - on POSIX, will use Writeln(StdErr)
|
|
procedure DisplayFatalError(const title, msg: RawUtf8);
|
|
|
|
/// prompt the user for an error message to notify an unexpected issue
|
|
// - redirect to DisplayFatalError() without any title
|
|
// - expects the regular Format() layout with %s %d - not the FormatUtf8() %
|
|
procedure DisplayError(const fmt: string; const args: array of const);
|
|
|
|
/// get a file date and time, from a FindFirst/FindNext search
|
|
// - the returned timestamp is in local time, not UTC
|
|
// - this method would use the F.Timestamp field available since Delphi XE2
|
|
function SearchRecToDateTime(const F: TSearchRec): TDateTime;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// get a file UTC date and time, from a FindFirst/FindNext search
|
|
// - SearchRecToDateTime(), SearchRecToWindowsTime() and F.TimeStamp, which have
|
|
// local time and require a conversion, may appear less useful on server side
|
|
// - is implemented as a wrapper around SearchRecToUnixTimeUtc()
|
|
function SearchRecToDateTimeUtc(const F: TSearchRec): TDateTime;
|
|
|
|
/// get a file UTC date and time, from a FindFirst/FindNext search, as Unix time
|
|
// - SearchRecToDateTime(), SearchRecToWindowsTime() and F.TimeStamp, which have
|
|
// local time and require a conversion, may appear less useful on server side
|
|
function SearchRecToUnixTimeUtc(const F: TSearchRec): TUnixTime;
|
|
{$ifdef OSPOSIX}inline;{$endif}
|
|
|
|
/// get a file date and time, from a FindFirst/FindNext search, as Windows time
|
|
// - this cross-system function is used e.g. by mormot.core.zip which expects
|
|
// Windows TimeStamps in its headers
|
|
function SearchRecToWindowsTime(const F: TSearchRec): integer;
|
|
|
|
/// check if a FindFirst/FindNext found instance is actually a file
|
|
function SearchRecValidFile(const F: TSearchRec): boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// check if a FindFirst/FindNext found instance is actually a folder
|
|
function SearchRecValidFolder(const F: TSearchRec): boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
type
|
|
/// FPC TFileStream miss a Create(aHandle) constructor like Delphi
|
|
TFileStreamFromHandle = class(THandleStream)
|
|
protected
|
|
fDontReleaseHandle: boolean;
|
|
public
|
|
/// explictely close the handle if needed
|
|
destructor Destroy; override;
|
|
/// Destroy calls FileClose(Handle) unless this property is true
|
|
property DontReleaseHandle: boolean
|
|
read fDontReleaseHandle write fDontReleaseHandle;
|
|
end;
|
|
|
|
/// a TFileStream replacement which supports FileName longer than MAX_PATH,
|
|
// and a proper Create(aHandle) constructor in FPC
|
|
TFileStreamEx = class(TFileStreamFromHandle)
|
|
protected
|
|
fFileName : TFileName;
|
|
function GetSize: Int64; override; // faster (1 API call instead of 3)
|
|
public
|
|
/// open or create the file from its name, depending on the supplied Mode
|
|
// - Mode is typically fmCreate / fmOpenReadShared
|
|
constructor Create(const aFileName: TFileName; Mode: cardinal);
|
|
/// can use this class from a low-level file OS handle
|
|
constructor CreateFromHandle(const aFileName: TFileName; aHandle: THandle);
|
|
/// open for writing or create a non-existing file from its name
|
|
// - use fmCreate if aFileName does not exists, or fmOpenWrite otherwise
|
|
constructor CreateWrite(const aFileName: TFileName);
|
|
/// the file name assigned to this class constructor
|
|
property FileName : TFileName
|
|
read fFilename;
|
|
end;
|
|
|
|
/// file stream which ignores I/O write errors
|
|
// - in case disk space is exhausted, TFileStreamNoWriteError.WriteBuffer
|
|
// won't throw any exception, so application will continue to work
|
|
// - used e.g. by TSynLog to let the application continue with no exception,
|
|
// even in case of a disk/partition full of logs
|
|
TFileStreamNoWriteError = class(TFileStreamEx)
|
|
public
|
|
/// open for writing, potentially with alternate unlocked file names
|
|
// - use fmCreate if aFileName does not exists, or fmOpenWrite otherwise
|
|
// - on error, will try up to aAliases alternate '<filename>-locked<#>.<ext>'
|
|
constructor CreateAndRenameIfLocked(
|
|
var aFileName: TFileName; aAliases: integer = 3);
|
|
/// this overriden function returns Count, as if it was always successful
|
|
function Write(const Buffer; Count: Longint): Longint; override;
|
|
end;
|
|
|
|
/// a wrapper around FileRead() to ensure a whole memory buffer is retrieved
|
|
// - expects Size to be up to 2GB (seems like a big enough memory buffer)
|
|
// - on Windows, will read by 16MB chunks to avoid ERROR_NO_SYSTEM_RESOURCES
|
|
// - will call FileRead() and retry up to Size bytes are filled in the buffer
|
|
// - return true if all memory buffer has been read, or false on error
|
|
function FileReadAll(F: THandle; Buffer: pointer; Size: PtrInt): boolean;
|
|
|
|
/// a wrapper around FileWrite() to ensure a whole memory buffer is retrieved
|
|
// - will call FileWrite() and retry up to Size bytes are written from the buffer
|
|
// - return true if all memory buffer has been written, or false on error
|
|
function FileWriteAll(F: THandle; Buffer: pointer; Size: PtrInt): boolean;
|
|
|
|
/// overloaded function optimized for one pass reading of a (huge) file
|
|
// - will use e.g. the FILE_FLAG_SEQUENTIAL_SCAN flag under Windows, as stated
|
|
// by http://blogs.msdn.com/b/oldnewthing/archive/2012/01/20/10258690.aspx
|
|
// - on POSIX, calls fpOpen(pointer(FileName),O_RDONLY) with no fpFlock() call
|
|
// - is used e.g. by StringFromFile() or HashFile() functions
|
|
// - note: you could better use FileReadAll() to retrieve a whole data buffer
|
|
function FileOpenSequentialRead(const FileName: TFileName): integer;
|
|
|
|
/// returns a TFileStreamFromHandle optimized for one pass file reading
|
|
// - will use FileOpenSequentialRead(), i.e. FILE_FLAG_SEQUENTIAL_SCAN on Windows
|
|
// - on POSIX, calls fpOpen(pointer(FileName),O_RDONLY) with no fpFlock() call
|
|
// - is used e.g. by TRestOrmServerFullMemory and TAlgoCompress
|
|
function FileStreamSequentialRead(const FileName: TFileName): THandleStream;
|
|
|
|
/// try to open the file from its name, as fmOpenReadShared
|
|
// - on Windows, calls CreateFileW(aFileName,GENERIC_READ) then CloseHandle
|
|
// - on POSIX, calls fpOpen(pointer(aFileName),O_RDONLY) with no fpFlock() call
|
|
function FileIsReadable(const aFileName: TFileName): boolean;
|
|
|
|
/// copy all Source content into Dest from current position
|
|
// - on Delphi, Dest.CopyFrom(Source, 0) uses GetSize and ReadBuffer which is
|
|
// not compatible e.g. with TAesPkcs7Reader padding - and has a small buffer
|
|
// - returns the number of bytes copied from Source to Dest
|
|
function StreamCopyUntilEnd(Source, Dest: TStream): Int64;
|
|
|
|
/// read a File content into a string
|
|
// - content can be binary or text
|
|
// - returns '' if file was not found or any read error occurred
|
|
// - wil use GetFileSize() API by default, unless HasNoSize is defined,
|
|
// and read will be done using a buffer (required e.g. for POSIX char files)
|
|
// - uses RawByteString for byte storage, whatever the codepage is
|
|
function StringFromFile(const FileName: TFileName;
|
|
HasNoSize: boolean = false): RawByteString;
|
|
|
|
/// read a File content from a list of potential files
|
|
// - returns '' if no file was found, or the first matching FileName[] content
|
|
function StringFromFirstFile(const FileName: array of TFileName): RawByteString;
|
|
|
|
/// read all Files content from a list of file names
|
|
// - returns '' if no FileName[] file was found, or the read content
|
|
function StringFromFiles(const FileName: array of TFileName): TRawByteStringDynArray;
|
|
|
|
/// read all Files content from a list of folders names
|
|
// - returns the content of every file contained in the supplied Folders[]
|
|
// - with optionally the FileNames[] corresponding to each result[] content
|
|
function StringFromFolders(const Folders: array of TFileName;
|
|
const Mask: TFileName = FILES_ALL;
|
|
FileNames: PFileNameDynArray = nil): TRawByteStringDynArray;
|
|
|
|
/// create a File from a string content
|
|
// - uses RawByteString for byte storage, whatever the codepage is
|
|
// - can optionaly force flush all write buffers to disk
|
|
function FileFromString(const Content: RawByteString; const FileName: TFileName;
|
|
FlushOnDisk: boolean = false): boolean;
|
|
|
|
/// create a File from a memory buffer content
|
|
function FileFromBuffer(Buf: pointer; Len: PtrInt; const FileName: TFileName): boolean;
|
|
|
|
/// create or append a string content to a File
|
|
// - can optionally rotate the file to a FileName+'.bak' over a specific size
|
|
function AppendToFile(const Content: RawUtf8; const FileName: TFileName;
|
|
BackupOverMaxSize: Int64 = 0): boolean;
|
|
|
|
/// compute an unique temporary file name
|
|
// - following 'exename_123.tmp' pattern, in the system temporary folder
|
|
function TemporaryFileName: TFileName;
|
|
|
|
/// extract a path from a file name like ExtractFilePath function
|
|
// - but cross-platform, i.e. detect both '\' and '/' on all platforms
|
|
function ExtractPath(const FileName: TFileName): TFileName;
|
|
|
|
/// extract a path from a RawUtf8 file name like ExtractFilePath function
|
|
// - but cross-platform, i.e. detect both '\' and '/' on all platforms
|
|
function ExtractPathU(const FileName: RawUtf8): RawUtf8;
|
|
|
|
/// extract a name from a file name like ExtractFileName function
|
|
// - but cross-platform, i.e. detect both '\' and '/' on all platforms
|
|
function ExtractName(const FileName: TFileName): TFileName;
|
|
|
|
/// extract a name from a file name like ExtractFileName function
|
|
// - but cross-platform, i.e. detect both '\' and '/' on all platforms
|
|
function ExtractNameU(const FileName: RawUtf8): RawUtf8;
|
|
|
|
/// extract an extension from a file name like ExtractFileExt function
|
|
// - but cross-platform, i.e. detect both '\' and '/' on all platforms
|
|
function ExtractExt(const FileName: TFileName; WithoutDot: boolean = false): TFileName;
|
|
|
|
// defined here for proper ExtractExtP() inlining
|
|
function GetLastDelimU(const FileName: RawUtf8; OtherDelim: AnsiChar): PtrInt;
|
|
|
|
/// extract an extension from a file name like ExtractFileExt function
|
|
// - but cross-platform, i.e. detect both '\' and '/' on all platforms
|
|
function ExtractExtU(const FileName: RawUtf8; WithoutDot: boolean = false): RawUtf8;
|
|
|
|
/// extract an extension from a file name like ExtractFileExt function
|
|
// - but cross-platform, i.e. detect both '\' and '/' on all platforms
|
|
function ExtractExtP(const FileName: RawUtf8; WithoutDot: boolean = false): PUtf8Char;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
|
|
/// compute the file name, including its path if supplied, but without its extension
|
|
// - e.g. GetFileNameWithoutExt('/var/toto.ext') = '/var/toto'
|
|
// - may optionally return the extracted extension, as '.ext'
|
|
// - will be cross-platform, i.e. detect both '\' and '/' on all platforms
|
|
function GetFileNameWithoutExt(const FileName: TFileName;
|
|
Extension: PFileName = nil): TFileName;
|
|
|
|
/// extract the file name without any path nor extension, as UTF-8
|
|
// - e.g. GetFileNameWithoutExt('/var/toto.ext') = 'toto'
|
|
// - used e.g. to compute Executable.ProgramName
|
|
function GetFileNameWithoutExtOrPath(const FileName: TFileName): RawUtf8;
|
|
|
|
/// compare two "array of TFileName" elements, grouped by file extension
|
|
// - i.e. with no case sensitivity on Windows
|
|
// - the expected string type is the RTL string, i.e. TFileName
|
|
// - calls internally GetFileNameWithoutExt() and AnsiCompareFileName()
|
|
function SortDynArrayFileName(const A, B): integer;
|
|
|
|
{$ifdef ISDELPHI20062007}
|
|
/// compatibility function defined to avoid hints on buggy Delphi 2006/2007
|
|
function AnsiCompareFileName(const S1, S2 : TFileName): integer;
|
|
{$endif ISDELPHI20062007}
|
|
|
|
/// creates a directory if not already existing
|
|
// - returns the full expanded directory name, including trailing path delimiter
|
|
// - returns '' on error, unless RaiseExceptionOnCreationFailure is set
|
|
function EnsureDirectoryExists(const Directory: TFileName;
|
|
RaiseExceptionOnCreationFailure: ExceptionClass = nil): TFileName;
|
|
|
|
/// just a wrapper around EnsureDirectoryExists(NormalizeFileName(Directory))
|
|
function NormalizeDirectoryExists(const Directory: TFileName;
|
|
RaiseExceptionOnCreationFailure: ExceptionClass = nil): TFileName;
|
|
|
|
/// delete the content of a specified directory
|
|
// - only one level of file is deleted within the folder: no recursive deletion
|
|
// is processed by this function (for safety)
|
|
// - if DeleteOnlyFilesNotDirectory is TRUE, it won't remove the folder itself,
|
|
// but just the files found in it
|
|
function DirectoryDelete(const Directory: TFileName;
|
|
const Mask: TFileName = FILES_ALL; DeleteOnlyFilesNotDirectory: boolean = false;
|
|
DeletedCount: PInteger = nil): boolean;
|
|
|
|
/// delete the files older than a given age in a specified directory
|
|
// - for instance, to delete all files older than one day:
|
|
// ! DirectoryDeleteOlderFiles(FolderName, 1);
|
|
// - only one level of file is deleted within the folder: no recursive deletion
|
|
// is processed by this function, unless Recursive is TRUE
|
|
// - if Recursive=true, caller should set TotalSize^=0 to have an accurate value
|
|
// - return false if any deprecated DeleteFile() did fail during the process
|
|
function DirectoryDeleteOlderFiles(const Directory: TFileName;
|
|
TimePeriod: TDateTime; const Mask: TFileName = FILES_ALL;
|
|
Recursive: boolean = false; TotalSize: PInt64 = nil): boolean;
|
|
|
|
type
|
|
/// defines how IsDirectoryWritable() verifies a folder
|
|
// - on Win32 Vista+, idwExcludeWinUac will check IsUacVirtualFolder()
|
|
// - on Windows, idwExcludeWinSys will check IsSystemFolder()
|
|
// - on Windows, idwTryWinExeFile will try to generate a 'xxxxx.exe' file
|
|
// - idwWriteSomeContent will also try to write some bytes into the file
|
|
TIsDirectoryWritable = set of (
|
|
idwExcludeWinUac,
|
|
idwExcludeWinSys,
|
|
idwTryWinExeFile,
|
|
idwWriteSomeContent);
|
|
|
|
/// check if the directory is writable for the current user
|
|
// - try to write and delete a void file with a random name in this folder
|
|
function IsDirectoryWritable(const Directory: TFileName;
|
|
Flags: TIsDirectoryWritable = []): boolean;
|
|
|
|
type
|
|
/// cross-platform memory mapping of a file content
|
|
{$ifdef USERECORDWITHMETHODS}
|
|
TMemoryMap = record
|
|
{$else}
|
|
TMemoryMap = object
|
|
{$endif USERECORDWITHMETHODS}
|
|
private
|
|
fBuf: PAnsiChar;
|
|
fBufSize: PtrUInt;
|
|
fFile: THandle;
|
|
{$ifdef OSWINDOWS}
|
|
fMap: THandle;
|
|
{$endif OSWINDOWS}
|
|
fFileSize: Int64;
|
|
fFileLocal, fLoadedNotMapped: boolean;
|
|
function DoMap(aCustomOffset: Int64): boolean;
|
|
procedure DoUnMap;
|
|
public
|
|
/// map the corresponding file handle
|
|
// - if aCustomSize and aCustomOffset are specified, the corresponding
|
|
// map view if created (by default, will map whole file)
|
|
function Map(aFile: THandle; aCustomSize: PtrUInt = 0;
|
|
aCustomOffset: Int64 = 0; aFileOwned: boolean = false;
|
|
aFileSize: Int64 = -1): boolean; overload;
|
|
/// map the file specified by its name
|
|
// - file will be closed when UnMap will be called
|
|
function Map(const aFileName: TFileName): boolean; overload;
|
|
/// set a fixed buffer for the content
|
|
// - emulates memory-mapping over an existing buffer
|
|
procedure Map(aBuffer: pointer; aBufferSize: PtrUInt); overload;
|
|
/// unmap the file
|
|
procedure UnMap;
|
|
/// retrieve the memory buffer mapped to the file content
|
|
property Buffer: PAnsiChar
|
|
read fBuf;
|
|
/// retrieve the buffer size
|
|
property Size: PtrUInt
|
|
read fBufSize;
|
|
/// retrieve the mapped file size
|
|
property FileSize: Int64
|
|
read fFileSize;
|
|
/// access to the low-level associated File handle (if any)
|
|
property FileHandle: THandle
|
|
read fFile;
|
|
end;
|
|
|
|
/// a TStream created from a file content, using fast memory mapping
|
|
TSynMemoryStreamMapped = class(TSynMemoryStream)
|
|
protected
|
|
fMap: TMemoryMap;
|
|
fFileStream: THandleStream;
|
|
fFileName: TFileName;
|
|
public
|
|
/// create a TStream from a file content using fast memory mapping
|
|
// - if aCustomSize and aCustomOffset are specified, the corresponding
|
|
// map view if created (by default, will map whole file)
|
|
constructor Create(const aFileName: TFileName;
|
|
aCustomSize: PtrUInt = 0; aCustomOffset: Int64 = 0); overload;
|
|
/// create a TStream from a file content using fast memory mapping
|
|
// - if aCustomSize and aCustomOffset are specified, the corresponding
|
|
// map view if created (by default, will map whole file)
|
|
constructor Create(aFile: THandle;
|
|
aCustomSize: PtrUInt = 0; aCustomOffset: Int64 = 0); overload;
|
|
/// release any internal mapped file instance
|
|
destructor Destroy; override;
|
|
/// the file name, if created from such Create(aFileName) constructor
|
|
property FileName: TFileName
|
|
read fFileName;
|
|
end;
|
|
|
|
/// low-level access to a resource bound to the executable
|
|
// - so that Windows is not required in your unit uses clause
|
|
{$ifdef USERECORDWITHMETHODS}
|
|
TExecutableResource = record
|
|
{$else}
|
|
TExecutableResource = object
|
|
{$endif USERECORDWITHMETHODS}
|
|
private
|
|
// note: we can't use THandle which is 32-bit on 64-bit POSIX
|
|
HResInfo: TLibHandle;
|
|
HGlobal: TLibHandle;
|
|
public
|
|
/// the resource memory pointer, after successful Open()
|
|
Buffer: pointer;
|
|
/// the resource memory size in bytes, after successful Open()
|
|
Size: PtrInt;
|
|
/// locate and lock a resource
|
|
// - use the current executable if Instance is left to its 0 default value
|
|
// - returns TRUE if the resource has been found, and Buffer/Size are set
|
|
function Open(const ResourceName: string; ResType: PChar;
|
|
Instance: TLibHandle = 0): boolean;
|
|
/// unlock and finalize a resource
|
|
procedure Close;
|
|
end;
|
|
|
|
|
|
type
|
|
/// store CPU and RAM usage for a given process
|
|
// - as used by TSystemUse class
|
|
TSystemUseData = packed record
|
|
/// when the data has been sampled
|
|
Timestamp: TDateTime;
|
|
/// percent of current Kernel-space CPU usage for this process
|
|
Kernel: single;
|
|
/// percent of current User-space CPU usage for this process
|
|
User: single;
|
|
/// how many KB of working memory are used by this process
|
|
WorkKB: cardinal;
|
|
/// how many KB of virtual memory are used by this process
|
|
VirtualKB: cardinal;
|
|
end;
|
|
|
|
/// store CPU and RAM usage history for a given process
|
|
// - as returned by TSystemUse.History
|
|
TSystemUseDataDynArray = array of TSystemUseData;
|
|
|
|
/// low-level structure used to compute process memory and CPU usage
|
|
{$ifdef USERECORDWITHMETHODS}
|
|
TProcessInfo = record
|
|
{$else}
|
|
TProcessInfo = object
|
|
{$endif USERECORDWITHMETHODS}
|
|
private
|
|
{$ifdef OSWINDOWS}
|
|
fSysPrevIdle, fSysPrevKernel, fSysPrevUser,
|
|
fDiffIdle, fDiffKernel, fDiffUser, fDiffTotal: Int64;
|
|
{$endif OSWINDOWS}
|
|
public
|
|
/// initialize the system/process resource tracking
|
|
function Init: boolean;
|
|
/// to be called before PerSystem() or PerProcess() iteration
|
|
function Start: boolean;
|
|
/// percent of current Idle/Kernel/User CPU usage for all processes
|
|
function PerSystem(out Idle, Kernel, User: single): boolean;
|
|
/// retrieve CPU and RAM usage for a given process
|
|
function PerProcess(PID: cardinal; Now: PDateTime;
|
|
out Data: TSystemUseData; var PrevKernel, PrevUser: Int64): boolean;
|
|
end;
|
|
|
|
/// hold low-level information about current memory usage
|
|
// - as filled by GetMemoryInfo()
|
|
TMemoryInfo = record
|
|
memtotal, memfree, filetotal, filefree,
|
|
vmtotal, vmfree, allocreserved, allocused: QWord;
|
|
percent: integer;
|
|
end;
|
|
|
|
/// stores information about a disk partition
|
|
TDiskPartition = packed record
|
|
/// the name of this partition
|
|
// - is the Volume name under Windows, or the Device name under POSIX
|
|
name: RawUtf8;
|
|
/// where this partition has been mounted
|
|
// - e.g. 'C:' or '/home'
|
|
// - you can use GetDiskInfo(mounted) to retrieve current space information
|
|
mounted: TFileName;
|
|
/// total size (in bytes) of this partition
|
|
size: QWord;
|
|
end;
|
|
|
|
/// stores information about several disk partitions
|
|
TDiskPartitions = array of TDiskPartition;
|
|
|
|
|
|
{$ifdef CPUARM}
|
|
var
|
|
/// internal wrapper address for ReserveExecutableMemory()
|
|
// - set to @TInterfacedObjectFake.ArmFakeStub by mormot.core.interfaces.pas
|
|
ArmFakeStubAddr: pointer;
|
|
{$endif CPUARM}
|
|
|
|
|
|
/// cross-platform reserve some executable memory
|
|
// - using PAGE_EXECUTE_READWRITE flags on Windows, and PROT_READ or PROT_WRITE
|
|
// or PROT_EXEC on POSIX
|
|
// - this function maintain an internal list of 64KB memory pages for efficiency
|
|
// - memory blocks can not be released (don't try to use fremeem on them) and
|
|
// will be returned to the system at process finalization
|
|
function ReserveExecutableMemory(size: cardinal): pointer;
|
|
|
|
/// to be called after ReserveExecutableMemory() when you want to actually write
|
|
// the memory blocks
|
|
// - affect the mapping flags of the first memory page (4KB) of the Reserved
|
|
// buffer, so its size should be < 4KB
|
|
// - do nothing on Windows and Linux, but may be needed on OpenBSD
|
|
procedure ReserveExecutableMemoryPageAccess(Reserved: pointer; Exec: boolean);
|
|
|
|
/// check if the supplied pointer is actually pointing to some memory page
|
|
// - will call slow but safe VirtualQuery API on Windows, or try a fpaccess()
|
|
// syscall on POSIX systems (validated on Linux only)
|
|
function SeemsRealPointer(p: pointer): boolean;
|
|
|
|
/// fill a buffer with a copy of some low-level system memory
|
|
// - used e.g. by GetRawSmbios on XP or Linux/POSIX
|
|
// - will allow to read up to 4MB of memory
|
|
// - use low-level ntdll.dll API on Windows, or reading /dev/mem on POSIX - so
|
|
// expect sudo/root rights on most systems
|
|
function ReadSystemMemory(address, size: PtrUInt): RawByteString;
|
|
|
|
/// return the PIDs of all running processes
|
|
// - under Windows, is a wrapper around EnumProcesses() PsAPI call
|
|
// - on Linux, will enumerate /proc/* pseudo-files
|
|
function EnumAllProcesses: TCardinalDynArray;
|
|
|
|
/// return the process name of a given process ID
|
|
// - under Windows, is a wrapper around
|
|
// QueryFullProcessImageNameW/GetModuleFileNameEx PsAPI call
|
|
// - on Linux, will query /proc/[pid]/exe or /proc/[pid]/cmdline pseudo-file
|
|
function EnumProcessName(PID: cardinal): RawUtf8;
|
|
|
|
/// return the process ID of the parent of a given PID
|
|
// - by default (PID = 0), will search for the parent of the current process
|
|
// - returns 0 if the PID was not found
|
|
function GetParentProcess(PID: cardinal = 0): cardinal;
|
|
|
|
/// check if this process is currently running into the debugger
|
|
// - redirect to the homonymous WinAPI function on Windows, or check if the
|
|
// /proc/self/status "TracerPid:" value is non zero on Linux, or search if
|
|
// "lazarus" is part of the parent process name for BSD
|
|
{$ifdef OSWINDOWS}
|
|
function IsDebuggerPresent: BOOL; stdcall;
|
|
{$else}
|
|
function IsDebuggerPresent: boolean;
|
|
{$endif ODWINDOWS}
|
|
|
|
/// return the time and memory usage information about a given process
|
|
// - under Windows, is a wrapper around GetProcessTimes/GetProcessMemoryInfo
|
|
function RetrieveProcessInfo(PID: cardinal; out KernelTime, UserTime: Int64;
|
|
out WorkKB, VirtualKB: cardinal): boolean;
|
|
|
|
/// return the system-wide time usage information
|
|
// - under Windows, is a wrapper around GetSystemTimes() kernel API call
|
|
// - return false on POSIX system - call RetrieveLoadAvg() instead
|
|
function RetrieveSystemTimes(out IdleTime, KernelTime, UserTime: Int64): boolean;
|
|
|
|
/// return the system-wide time usage information
|
|
// - on LINUX, retrieve /proc/loadavg or on OSX/BSD call libc getloadavg()
|
|
// - return '' on Windows - call RetrieveSystemTimes() instead
|
|
function RetrieveLoadAvg: RawUtf8;
|
|
|
|
/// retrieve low-level information about current memory usage
|
|
// - as used by TSynMonitorMemory
|
|
// - under BSD, only memtotal/memfree/percent are properly returned
|
|
// - allocreserved and allocused are set only if withalloc is TRUE
|
|
function GetMemoryInfo(out info: TMemoryInfo; withalloc: boolean): boolean;
|
|
|
|
/// retrieve some human-readable text from GetMemoryInfo
|
|
// - numbers are rounded up to a single GB number with no decimals
|
|
// - returns e.g. 'used 6GB/16GB (35% free)' text
|
|
function GetMemoryInfoText: RawUtf8;
|
|
|
|
/// retrieve some human-readable text about the current system in several lines
|
|
// - includes UTC timestamp, memory and disk availability, and exe/OS/CPU info
|
|
function GetSystemInfoText: RawUtf8;
|
|
|
|
/// retrieve low-level information about a given disk partition
|
|
// - as used by TSynMonitorDisk and GetDiskPartitionsText()
|
|
// - aDriveFolderOrFile is a directory on disk (no need to specify a raw drive
|
|
// name like 'c:\' on Windows)
|
|
// - warning: aDriveFolderOrFile may be modified at input
|
|
// - only under Windows the Quotas are applied separately to aAvailableBytes
|
|
// in respect to global aFreeBytes
|
|
function GetDiskInfo(var aDriveFolderOrFile: TFileName;
|
|
out aAvailableBytes, aFreeBytes, aTotalBytes: QWord
|
|
{$ifdef OSWINDOWS}; aVolumeName: PSynUnicode = nil{$endif}): boolean;
|
|
|
|
/// retrieve how many bytes are currently available on a given folder
|
|
// - returns 0 if the function fails
|
|
function GetDiskAvailable(aDriveFolderOrFile: TFileName): QWord;
|
|
|
|
/// retrieve low-level information about all mounted disk partitions of the system
|
|
// - returned partitions array is sorted by "mounted" ascending order
|
|
function GetDiskPartitions: TDiskPartitions;
|
|
|
|
/// call several Operating System APIs to gather 512-bit of entropy information
|
|
procedure XorOSEntropy(var e: THash512Rec);
|
|
|
|
/// low-level function returning some random binary from the Operating System
|
|
// - will call /dev/urandom or /dev/random under POSIX, and CryptGenRandom API
|
|
// on Windows then return TRUE, or fallback to mormot.core.base gsl_rng_taus2
|
|
// generator and return FALSE if the system API failed
|
|
// - on POSIX, only up to 32 bytes (256 bits) bits are retrieved from /dev/urandom
|
|
// or /dev/random as stated by "man urandom" Usage - then RandomBytes() padded
|
|
// - so you may consider that the output Buffer is always filled with random
|
|
// - you should not have to call this procedure, but faster and safer TAesPrng
|
|
// from mormot.crypt.core - also consider the TSystemPrng class
|
|
function FillSystemRandom(Buffer: PByteArray; Len: integer;
|
|
AllowBlocking: boolean): boolean;
|
|
|
|
type
|
|
/// available console colors
|
|
TConsoleColor = (
|
|
ccBlack,
|
|
ccBlue,
|
|
ccGreen,
|
|
ccCyan,
|
|
ccRed,
|
|
ccMagenta,
|
|
ccBrown,
|
|
ccLightGray,
|
|
ccDarkGray,
|
|
ccLightBlue,
|
|
ccLightGreen,
|
|
ccLightCyan,
|
|
ccLightRed,
|
|
ccLightMagenta,
|
|
ccYellow,
|
|
ccWhite);
|
|
|
|
var
|
|
/// low-level handle used for console writing
|
|
// - may be overriden when console is redirected
|
|
// - on Windows, is initialized when AllocConsole or TextColor() are called
|
|
StdOut: THandle;
|
|
|
|
{$ifdef OSPOSIX}
|
|
/// set at initialization if StdOut has the TTY flag and env has a known TERM
|
|
StdOutIsTTY: boolean;
|
|
{$endif OSPOSIX}
|
|
|
|
/// global flag to modify the code behavior at runtime when run from TSynTests
|
|
// - e.g. TSynDaemon.AfterCreate won't overwrite TSynTests.RunAsConsole logs
|
|
RunFromSynTests: boolean;
|
|
|
|
/// similar to Windows AllocConsole API call, to be truly cross-platform
|
|
// - do nothing on Linux/POSIX, but set StdOut propertly from StdOutputHandle
|
|
// - on Windows, will call the corresponding API, and set StdOut global variable
|
|
procedure AllocConsole;
|
|
|
|
/// change the console text writing color
|
|
procedure TextColor(Color: TConsoleColor);
|
|
|
|
/// change the console text background color
|
|
procedure TextBackground(Color: TConsoleColor);
|
|
|
|
/// write some text to the console using a given color
|
|
// - this method is protected by its own CriticalSection for output consistency
|
|
procedure ConsoleWrite(const Text: RawUtf8; Color: TConsoleColor = ccLightGray;
|
|
NoLineFeed: boolean = false; NoColor: boolean = false); overload;
|
|
|
|
/// will wait for the ENTER key to be pressed, with all needed waiting process
|
|
// - on the main thread, will call Synchronize() for proper work e.g. with
|
|
// interface-based service implemented as optExecInMainThread
|
|
// - on Windows, from a non-main Thread, respond to PostThreadMessage(WM_QUIT)
|
|
// - on Windows, also properly respond to Ctrl-C or closing console events
|
|
// - on POSIX, will call SynDaemonIntercept first, so that Ctrl-C or SIG_QUIT
|
|
// will also be intercepted and let this procedure return
|
|
procedure ConsoleWaitForEnterKey;
|
|
|
|
/// read all available content from stdin
|
|
// - could be used to retrieve some file piped to the command line
|
|
// - the content is not converted, so will follow the encoding used for storage
|
|
function ConsoleReadBody: RawByteString;
|
|
|
|
{$ifdef OSWINDOWS}
|
|
|
|
/// low-level access to the keyboard state of a given key
|
|
function ConsoleKeyPressed(ExpectedKey: Word): boolean;
|
|
|
|
/// local RTL wrapper function to avoid linking mormot.core.unicode.pas
|
|
procedure Win32PWideCharToUtf8(P: PWideChar; Len: PtrInt;
|
|
out res: RawUtf8); overload;
|
|
|
|
/// local RTL wrapper function to avoid linking mormot.core.unicode.pas
|
|
procedure Win32PWideCharToUtf8(P: PWideChar; out res: RawUtf8); overload;
|
|
|
|
/// local RTL wrapper function to avoid linking mormot.core.unicode.pas
|
|
// - returns dest.buf as PWideChar result, and dest.len as length
|
|
// - caller should always call dest.Done to release (unlikely) temporary memory
|
|
function Utf8ToWin32PWideChar(const Text: RawUtf8;
|
|
var dest: TSynTempBuffer): PWideChar;
|
|
|
|
/// ask the Operating System to convert a file URL to a local file path
|
|
// - only Windows has a such a PathCreateFromUrl() API
|
|
// - POSIX define this in mormot.net.http.pas, where TUri is available
|
|
// - used e.g. by TNetClientProtocolFile to implement the 'file://' protocol
|
|
function GetFileNameFromUrl(const Uri: string): TFileName;
|
|
|
|
{$else}
|
|
|
|
/// internal function to avoid linking mormot.core.buffers.pas
|
|
function PosixParseHex32(p: PAnsiChar): integer;
|
|
|
|
/// internal function to avoid linking mormot.core.buffers.pas
|
|
procedure ParseHex(p: PAnsiChar; b: PByte; n: integer);
|
|
|
|
/// internal function just wrapping fppoll(POLLIN or POLLPRI)
|
|
function WaitReadPending(fd, timeout: integer): boolean;
|
|
|
|
/// POSIX-only function calling directly getdents/getdents64 syscall
|
|
// - could be used when FindFirst/FindNext are an overkill, e.g. to quickly
|
|
// cache all file names of a folder in memory, optionally with its sub-folders
|
|
// - used e.g. by TPosixFileCaseInsensitive from mormot.core.unicode
|
|
// - warning: the file system has to support d_type (e.g. btrfs, ext2-ext4) so
|
|
// that Recursive is handled and only DT_REG files are retrieved; non-compliant
|
|
// file systems (or Linux Kernel older than 2.6.4) won't support the Recursive
|
|
// search, and may return some false positives, like symlinks or nested folders
|
|
function PosixFileNames(const Folder: TFileName; Recursive: boolean): TRawUtf8DynArray;
|
|
|
|
{$endif OSWINDOWS}
|
|
|
|
/// internal function to avoid linking mormot.core.buffers.pas
|
|
// - will output the value as one number with one decimal and KB/MB/GB/TB suffix
|
|
function _oskb(Size: QWord): shortstring;
|
|
|
|
/// direct conversion of a UTF-8 encoded string into a console OEM-encoded string
|
|
// - under Windows, will use the CP_OEM encoding
|
|
// - under Linux, will expect the console to be defined with UTF-8 encoding
|
|
// - we don't propose any ConsoleToUtf8() function because Windows depends on
|
|
// the running program itself: most should generates CP_OEM (e.g. 850) as expected,
|
|
// but some could use the system code page or even UTF-16 binary with BOM (!) -
|
|
// so you may consider using AnsiToUtf8() with the proper code page
|
|
function Utf8ToConsole(const S: RawUtf8): RawByteString;
|
|
|
|
|
|
type
|
|
/// encapsulate cross-platform loading of library files
|
|
// - this generic class can be used for any external library (.dll/.so)
|
|
TSynLibrary = class
|
|
protected
|
|
fHandle: TLibHandle;
|
|
fLibraryPath: TFileName;
|
|
fTryFromExecutableFolder: boolean;
|
|
{$ifdef OSPOSIX}
|
|
fLibraryPathTested: boolean;
|
|
{$endif OSPOSIX}
|
|
public
|
|
/// cross-platform resolution of a function entry in this library
|
|
// - if RaiseExceptionOnFailure is set, missing entry will call FreeLib then raise it
|
|
// - ProcName can be a space-separated list of procedure names, to try
|
|
// alternate API names (e.g. for OpenSSL 1.1.1/3.x compatibility)
|
|
// - if ProcName starts with '?' then RaiseExceptionOnFailure = nil is set
|
|
function Resolve(const Prefix, ProcName: RawUtf8; Entry: PPointer;
|
|
RaiseExceptionOnFailure: ExceptionClass = nil): boolean;
|
|
/// cross-platform resolution of all function entries in this library
|
|
// - will search and fill Entry^ for all ProcName^ until ProcName^=nil
|
|
// - return true on success, false and call FreeLib if any entry is missing
|
|
function ResolveAll(ProcName: PPAnsiChar; Entry: PPointer): boolean;
|
|
/// cross-platform call to FreeLibrary() + set fHandle := 0
|
|
// - as called by Destroy, but you can use it directly to reload the library
|
|
procedure FreeLib;
|
|
/// same as SafeLoadLibrary() but setting fLibraryPath and cwd on Windows
|
|
function TryLoadLibrary(const aLibrary: array of TFileName;
|
|
aRaiseExceptionOnFailure: ExceptionClass): boolean; virtual;
|
|
/// release associated memory and linked library
|
|
destructor Destroy; override;
|
|
/// return TRUE if the library and all procedures were found
|
|
function Exists: boolean;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
/// the associated library handle
|
|
property Handle: TLibHandle
|
|
read fHandle write fHandle;
|
|
/// the loaded library path
|
|
// - on POSIX, contains the full path (via dladdr) once Resolve() is called
|
|
property LibraryPath: TFileName
|
|
read fLibraryPath;
|
|
/// if set, and no path is specified, will try from Executable.ProgramFilePath
|
|
property TryFromExecutableFolder: boolean
|
|
read fTryFromExecutableFolder write fTryFromExecutableFolder;
|
|
end;
|
|
|
|
|
|
{ *************** Per Class Properties O(1) Lookup via vmtAutoTable Slot }
|
|
|
|
/// self-modifying code - change some memory buffer in the code segment
|
|
// - if Backup is not nil, it should point to a Size array of bytes, ready
|
|
// to contain the overridden code buffer, for further hook disabling
|
|
// - some systems do forbid such live patching: consider setting NOPATCHVMT
|
|
// and NOPATCHRTL conditionals for such projects
|
|
procedure PatchCode(Old, New: pointer; Size: PtrInt; Backup: pointer = nil;
|
|
LeaveUnprotected: boolean = false);
|
|
|
|
/// self-modifying code - change one PtrUInt in the code segment
|
|
procedure PatchCodePtrUInt(Code: PPtrUInt; Value: PtrUInt;
|
|
LeaveUnprotected: boolean = false);
|
|
|
|
{$ifdef CPUINTEL}
|
|
/// low-level i386/x86_64 asm routine patch and redirection
|
|
procedure RedirectCode(Func, RedirectFunc: Pointer);
|
|
{$endif CPUINTEL}
|
|
|
|
|
|
{ ************** Cross-Platform Charset and CodePage Support }
|
|
|
|
{$ifdef OSPOSIX}
|
|
const
|
|
ANSI_CHARSET = 0;
|
|
DEFAULT_CHARSET = 1;
|
|
SYMBOL_CHARSET = 2;
|
|
SHIFTJIS_CHARSET = $80;
|
|
HANGEUL_CHARSET = 129;
|
|
GB2312_CHARSET = 134;
|
|
CHINESEBIG5_CHARSET = 136;
|
|
OEM_CHARSET = 255;
|
|
JOHAB_CHARSET = 130;
|
|
HEBREW_CHARSET = 177;
|
|
ARABIC_CHARSET = 178;
|
|
GREEK_CHARSET = 161;
|
|
TURKISH_CHARSET = 162;
|
|
VIETNAMESE_CHARSET = 163;
|
|
THAI_CHARSET = 222;
|
|
EASTEUROPE_CHARSET = 238;
|
|
RUSSIAN_CHARSET = 204;
|
|
BALTIC_CHARSET = 186;
|
|
{$else}
|
|
{$ifdef FPC} // a missing declaration
|
|
const
|
|
VIETNAMESE_CHARSET = 163;
|
|
{$endif FPC}
|
|
{$endif OSPOSIX}
|
|
|
|
/// convert a char set to a code page
|
|
function CharSetToCodePage(CharSet: integer): cardinal;
|
|
|
|
/// convert a code page to a char set
|
|
function CodePageToCharSet(CodePage: cardinal): integer;
|
|
|
|
|
|
{ **************** TSynLocker/TSynLocked and Low-Level Threading Features }
|
|
|
|
type
|
|
/// a lightweight exclusive non-rentrant lock, stored in a PtrUInt value
|
|
// - calls SwitchToThread after some spinning, but don't use any R/W OS API
|
|
// - warning: methods are non rentrant, i.e. calling Lock twice in a raw would
|
|
// deadlock: use TRWLock or TSynLocker/TOSLock for reentrant methods
|
|
// - several lightlocks, each protecting a few variables (e.g. a list), may
|
|
// be more efficient than a more global TOSLock/TRWLock
|
|
// - our light locks are expected to be kept a very small amount of time (some
|
|
// CPU cycles): use TOSLightLock if the lock may block too long
|
|
// - TryLock/UnLock can be used to thread-safely acquire a shared resource
|
|
// - only consume 4 bytes on CPU32, 8 bytes on CPU64
|
|
{$ifdef USERECORDWITHMETHODS}
|
|
TLightLock = record
|
|
{$else}
|
|
TLightLock = object
|
|
{$endif USERECORDWITHMETHODS}
|
|
private
|
|
Flags: PtrUInt;
|
|
// low-level function called by the Lock method when inlined
|
|
procedure LockSpin;
|
|
public
|
|
/// to be called if the instance has not been filled with 0
|
|
// - e.g. not needed if TLightLock is defined as a class field
|
|
procedure Init;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
/// could be called to finalize the instance as a TOSLock
|
|
// - does nothing - just for compatibility with TOSLock
|
|
procedure Done;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
/// enter an exclusive non-rentrant lock
|
|
procedure Lock;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
/// try to enter an exclusive non-rentrant lock
|
|
// - if returned true, caller should eventually call UnLock()
|
|
// - could also be used to thread-safely acquire a shared resource
|
|
function TryLock: boolean;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
/// check if the non-rentrant lock has been acquired
|
|
function IsLocked: boolean;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
/// leave an exclusive non-rentrant lock
|
|
procedure UnLock;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
end;
|
|
|
|
/// a lightweight multiple Reads / exclusive Write non-upgradable lock
|
|
// - calls SwitchToThread after some spinning, but don't use any R/W OS API
|
|
// - warning: ReadLocks are reentrant and allow concurrent acccess, but calling
|
|
// WriteLock within a ReadLock, or within another WriteLock, would deadlock
|
|
// - consider TRWLock if you need an upgradable lock - but for mostly reads,
|
|
// TRWLightLock.ReadLock/ReadUnLock/WriteLock pattern is faster than upgrading
|
|
// - our light locks are expected to be kept a very small amount of time (some
|
|
// CPU cycles): use TSynLocker or TOSLock if the lock may block too long
|
|
// - several lightlocks, each protecting a few variables (e.g. a list), may
|
|
// be more efficient than a more global TOSLock/TRWLock
|
|
// - only consume 4 bytes on CPU32, 8 bytes on CPU64
|
|
{$ifdef USERECORDWITHMETHODS}
|
|
TRWLightLock = record
|
|
{$else}
|
|
TRWLightLock = object
|
|
{$endif USERECORDWITHMETHODS}
|
|
private
|
|
Flags: PtrUInt; // bit 0 = WriteLock, >0 = ReadLock
|
|
// low-level functions called by the Lock methods when inlined
|
|
procedure ReadLockSpin;
|
|
procedure WriteLockSpin;
|
|
public
|
|
/// to be called if the instance has not been filled with 0
|
|
// - e.g. not needed if TRWLightLock is defined as a class field
|
|
procedure Init;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
/// enter a non-upgradable multiple reads lock
|
|
// - read locks maintain a thread-safe counter, so are reentrant and non blocking
|
|
// - warning: nested WriteLock call after a ReadLock would deadlock
|
|
procedure ReadLock;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
/// try to enter a non-upgradable multiple reads lock
|
|
// - if returned true, caller should eventually call ReadUnLock
|
|
// - read locks maintain a thread-safe counter, so are reentrant and non blocking
|
|
// - warning: nested WriteLock call after a ReadLock would deadlock
|
|
function TryReadLock: boolean;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
/// leave a non-upgradable multiple reads lock
|
|
procedure ReadUnLock;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
/// enter a non-rentrant non-upgradable exclusive write lock
|
|
// - warning: nested WriteLock call after a ReadLock or another WriteLock
|
|
// would deadlock
|
|
procedure WriteLock;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
/// try to enter a non-rentrant non-upgradable exclusive write lock
|
|
// - if returned true, caller should eventually call WriteUnLock
|
|
// - warning: nested TryWriteLock call after a ReadLock or another WriteLock
|
|
// would deadlock
|
|
function TryWriteLock: boolean;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
/// leave a non-rentrant non-upgradable exclusive write lock
|
|
procedure WriteUnLock;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
end;
|
|
|
|
type
|
|
/// how TRWLock.Lock and TRWLock.UnLock high-level wrapper methods are called
|
|
TRWLockContext = (
|
|
cReadOnly,
|
|
cReadWrite,
|
|
cWrite);
|
|
|
|
/// a lightweight multiple Reads / exclusive Write reentrant lock
|
|
// - calls SwitchToThread after some spinning, but don't use any R/W OS API
|
|
// - our light locks are expected to be kept a very small amount of time (some
|
|
// CPU cycles): use TSynLocker or TOSLock if the lock may block too long
|
|
// - warning: all methods are reentrant, but WriteLock/ReadWriteLock would
|
|
// deadlock if called after a ReadOnlyLock
|
|
{$ifdef USERECORDWITHMETHODS}
|
|
TRWLock = record
|
|
{$else}
|
|
TRWLock = object
|
|
{$endif USERECORDWITHMETHODS}
|
|
private
|
|
Flags: PtrUInt; // bit 0 = WriteLock, 1 = ReadWriteLock, >1 = ReadOnlyLock
|
|
LastReadWriteLockThread, LastWriteLockThread: TThreadID; // to be reentrant
|
|
LastReadWriteLockCount, LastWriteLockCount: cardinal;
|
|
{$ifndef FPC_ASMX64}
|
|
procedure ReadOnlyLockSpin;
|
|
{$endif FPC_ASMX64}
|
|
public
|
|
/// initialize the R/W lock
|
|
// - not needed if TRWLock is part of a class - i.e. if was filled with 0
|
|
procedure Init;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
/// could be called at shutdown to ensure that the R/W lock is in neutral state
|
|
procedure AssertDone;
|
|
/// wait for the lock to be available for reading, but not upgradable to write
|
|
// - several readers could acquire the lock simultaneously
|
|
// - ReadOnlyLock is reentrant since there is a thread-safe internal counter
|
|
// - warning: calling ReadWriteLock/WriteLock after ReadOnlyLock would deadlock
|
|
// - typical usage is the following:
|
|
// ! rwlock.ReadOnlyLock; // won't block concurrent ReadOnlyLock
|
|
// ! try
|
|
// ! result := Exists(value);
|
|
// ! finally
|
|
// ! rwlock.ReadOnlyUnLock;
|
|
// ! end;
|
|
procedure ReadOnlyLock;
|
|
{$ifdef HASINLINE} {$ifndef FPC_ASMX64} inline; {$endif} {$endif}
|
|
/// release a previous ReadOnlyLock call
|
|
procedure ReadOnlyUnLock;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
/// wait for the lock to be accessible for reading - later upgradable to write
|
|
// - will mark the lock with the current thread so that a nested WriteLock
|
|
// would be possible, but won't block concurrent ReadOnlyLock
|
|
// - several readers could acquire ReadOnlyLock simultaneously, but only a
|
|
// single thread could acquire a ReadWriteLock
|
|
// - reentrant method, and nested WriteLock is allowed
|
|
// - typical usage is the following:
|
|
// ! rwlock.ReadWriteLock; // won't block concurrent ReadOnlyLock
|
|
// ! try // but block other ReadWriteLock/WriteLock
|
|
// ! result := Exists(value);
|
|
// ! if not result then
|
|
// ! begin
|
|
// ! rwlock.WriteLock; // block any ReadOnlyLock/ReadWriteLock/WriteLock
|
|
// ! try
|
|
// ! Add(value);
|
|
// ! finally
|
|
// ! rwlock.WriteUnLock;
|
|
// ! end;
|
|
// ! end;
|
|
// ! finally
|
|
// ! rwlock.ReadWriteUnLock;
|
|
// ! end;
|
|
procedure ReadWriteLock;
|
|
/// release a previous ReadWriteLock call
|
|
procedure ReadWriteUnLock;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
/// wait for the lock to be accessible for writing
|
|
// - the write lock is exclusive
|
|
// - calling WriteLock within a ReadWriteLock is allowed and won't block
|
|
// - but calling WriteLock within a ReadOnlyLock would deaadlock
|
|
// - this method is rentrant from a single thread
|
|
// - typical usage is the following:
|
|
// ! rwlock.WriteLock; // block any ReadOnlyLock/ReadWriteLock/WriteLock
|
|
// ! try
|
|
// ! Add(value);
|
|
// ! finally
|
|
// ! rwlock.WriteUnLock;
|
|
// ! end;
|
|
procedure WriteLock;
|
|
/// release a previous WriteLock call
|
|
procedure WriteUnlock;
|
|
{$ifdef FPC_OR_DELPHIXE4} inline; {$endif} // circumvent weird Delphi bug
|
|
/// a high-level wrapper over ReadOnlyLock/ReadWriteLock/WriteLock methods
|
|
procedure Lock(context: TRWLockContext {$ifndef PUREMORMOT2} = cWrite {$endif});
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
/// a high-level wrapper over ReadOnlyUnLock/ReadWriteUnLock/WriteUnLock methods
|
|
procedure UnLock(context: TRWLockContext {$ifndef PUREMORMOT2} = cWrite {$endif});
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
end;
|
|
PRWLock = ^TRWLock;
|
|
|
|
/// the standard rentrant lock supplied by the Operating System
|
|
// - maps TRTLCriticalSection, i.e. calls Win32 API or pthreads library
|
|
// - don't forget to call Init and Done to properly initialize the structure
|
|
// - if you do require a non-rentrant/recursive lock, consider TOSLightLock
|
|
// - same signature as TLightLock/TOSLightLock, usable as compile time alternatives
|
|
{$ifdef USERECORDWITHMETHODS}
|
|
TOSLock = record
|
|
{$else}
|
|
TOSLock = object
|
|
{$endif USERECORDWITHMETHODS}
|
|
private
|
|
CS: TRTLCriticalSection;
|
|
public
|
|
/// to be called to setup the instance
|
|
// - mandatory in all cases, even if TOSLock is part of a class
|
|
procedure Init;
|
|
/// to be called to finalize the instance
|
|
procedure Done;
|
|
/// enter an OS lock
|
|
// - notice: this method IS reentrant/recursive
|
|
procedure Lock;
|
|
{$ifdef FPC} inline; {$endif}
|
|
/// try to enter an OS lock
|
|
// - if returned true, caller should eventually call UnLock()
|
|
function TryLock: boolean;
|
|
{$ifdef FPC} inline; {$endif}
|
|
/// leave an OS lock
|
|
procedure UnLock;
|
|
{$ifdef FPC} inline; {$endif}
|
|
end;
|
|
|
|
/// the fastest non-rentrant lock supplied by the Operating System
|
|
// - calls Slim Reader/Writer (SRW) Win32 API in exclusive mode or directly
|
|
// the pthread_mutex_*() library calls in non-recursive/fast mode on Linux
|
|
// - on XP, where SRW are not available, fallback to a TLightLock
|
|
// - on non-Linux POSIX, fallback to regular cthreads/TRTLCriticalSection
|
|
// - don't forget to call Init and Done to properly initialize the structure
|
|
// - to protect a very small code section of a few CPU cycles with no Init/Done
|
|
// needed, and a lower footprint, you may consider our TLightLock
|
|
// - same signature as TOSLock/TLightLock, usable as compile time alternatives
|
|
// - warning: non-rentrant, i.e. nested Lock calls would block, as TLightLock
|
|
// - no TryLock is defined on Windows, because TryAcquireSRWLockExclusive()
|
|
// raised some unexpected EExternalException C000026 NT_STATUS_RESOURCE_NOT_OWNED
|
|
// ("Attempt to release mutex not owned by caller") during testing
|
|
{$ifdef USERECORDWITHMETHODS}
|
|
TOSLightLock = record
|
|
{$else}
|
|
TOSLightLock = object
|
|
{$endif USERECORDWITHMETHODS}
|
|
private
|
|
fMutex: TOSLightMutex;
|
|
public
|
|
/// to be called to setup the instance
|
|
// - mandatory in all cases, even if TOSLock is part of a class
|
|
procedure Init;
|
|
/// to be called to finalize the instance
|
|
procedure Done;
|
|
/// enter an OS lock
|
|
// - warning: this method is NOT reentrant/recursive, so any nested call
|
|
// would deadlock
|
|
procedure Lock;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
{$ifdef OSPOSIX}
|
|
/// access to raw pthread_mutex_trylock() method
|
|
// - TryAcquireSRWLockExclusive() seems not stable on all Windows revisions
|
|
function TryLock: boolean;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
{$endif OSPOSIX}
|
|
/// leave an OS lock
|
|
procedure UnLock;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
end;
|
|
|
|
/// points to one data entry in TLockedList
|
|
PLockedListOne = ^TLockedListOne;
|
|
/// abstract parent of one data entry in TLockedList, storing two PLockedListOne
|
|
// - TLockedList should store unmanaged records starting with those fields
|
|
// - sequence field contains an incremental random-seeded 30-bit integer > 65535,
|
|
// to avoid ABA problems when instances are recycled
|
|
TLockedListOne = record
|
|
next, prev: pointer;
|
|
sequence: PtrUInt;
|
|
end;
|
|
/// optional callback event to finalize one TLockedListOne instance
|
|
TOnLockedListOne = procedure(one: PLockedListOne) of object;
|
|
|
|
/// thread-safe dual-linked list of TLockedListOne descendants with recycling
|
|
{$ifdef USERECORDWITHMETHODS}
|
|
TLockedList = record
|
|
{$else}
|
|
TLockedList = object
|
|
{$endif USERECORDWITHMETHODS}
|
|
private
|
|
fHead, fBin: pointer;
|
|
fSize: integer;
|
|
fSequence: PtrUInt;
|
|
fOnFree: TOnLockedListOne;
|
|
public
|
|
/// thread-safe access to the list
|
|
Safe: TLightLock;
|
|
/// how many TLockedListOne instances are currently stored in this list
|
|
// - excluding the instances in the recycle bin
|
|
Count: integer;
|
|
/// initialize the storage for an inherited TLockedListOne size
|
|
procedure Init(onesize: PtrUInt; const onefree: TOnLockedListOne = nil);
|
|
/// release all stored memory
|
|
procedure Done;
|
|
/// allocate a new PLockedListOne data instance in threadsafe O(1) process
|
|
function New: pointer;
|
|
/// release one PLockedListOne used data instance in threadsafe O(1) process
|
|
function Free(one: pointer): boolean;
|
|
/// release all TLockedListOne instances currently stored in this list
|
|
// - without moving any of those instances into the internal recycle bin
|
|
procedure Clear;
|
|
/// release all to-be-recycled items available in the internal bin
|
|
// - returns how many items have been released from the internal collector
|
|
function EmptyBin: integer;
|
|
/// raw access to the stored items as PLockedListOne dual-linked list
|
|
property Head: pointer
|
|
read fHead;
|
|
/// the size of one stored instance, including its TLockedListOne header
|
|
property Size: integer
|
|
read fSize;
|
|
end;
|
|
|
|
type
|
|
/// how TSynLocker handles its thread processing
|
|
// - by default, uSharedLock will use the main TRTLCriticalSection
|
|
// - you may set uRWLock and call overloaded RWLock/RWUnLock() to use our
|
|
// lighter TRWLock - but be aware that cReadOnly followed by cReadWrite/cWrite
|
|
// would deadlock - regular Lock/UnLock will use cWrite exclusive lock
|
|
// - uNoLock will disable the whole locking mechanism
|
|
TSynLockerUse = (
|
|
uSharedLock,
|
|
uRWLock,
|
|
uNoLock);
|
|
|
|
/// allow to add cross-platform locking methods to any class instance
|
|
// - typical use is to define a Safe: TSynLocker property, call Safe.Init
|
|
// and Safe.Done in constructor/destructor methods, and use Safe.Lock/UnLock
|
|
// methods in a try ... finally section
|
|
// - in respect to the TCriticalSection class, fix a potential CPU cache line
|
|
// conflict which may degrade the multi-threading performance, as reported by
|
|
// @http://www.delphitools.info/2011/11/30/fixing-tcriticalsection
|
|
// - internal padding is used to safely store up to 7 values protected
|
|
// from concurrent access with a mutex, so that SizeOf(TSynLocker)>128
|
|
// - for object-level locking, see TSynPersistentLock which owns one such
|
|
// instance, or call low-level fSafe := NewSynLocker in your constructor,
|
|
// then fSafe^.DoneAndFreemem in your destructor
|
|
// - RWUse property could replace the TRTLCriticalSection by a lighter TRWLock
|
|
// - see also TRWLock and TSynPersistentRWLock if the multiple read / exclusive
|
|
// write lock is better (only if the locked process does not take too much time)
|
|
{$ifdef USERECORDWITHMETHODS}
|
|
TSynLocker = record
|
|
{$else}
|
|
TSynLocker = object
|
|
{$endif USERECORDWITHMETHODS}
|
|
private
|
|
fSection: TRTLCriticalSection;
|
|
fRW: TRWLock;
|
|
fPaddingUsedCount: byte;
|
|
fInitialized: boolean;
|
|
fRWUse: TSynLockerUse;
|
|
fLockCount: integer;
|
|
function GetVariant(Index: integer): Variant;
|
|
procedure SetVariant(Index: integer; const Value: Variant);
|
|
function GetInt64(Index: integer): Int64;
|
|
procedure SetInt64(Index: integer; const Value: Int64);
|
|
function GetBool(Index: integer): boolean;
|
|
procedure SetBool(Index: integer; const Value: boolean);
|
|
function GetUnlockedInt64(Index: integer): Int64;
|
|
procedure SetUnlockedInt64(Index: integer; const Value: Int64);
|
|
function GetPointer(Index: integer): Pointer;
|
|
procedure SetPointer(Index: integer; const Value: Pointer);
|
|
function GetUtf8(Index: integer): RawUtf8;
|
|
procedure SetUtf8(Index: integer; const Value: RawUtf8);
|
|
function GetIsLocked: boolean;
|
|
// - if RWUse=uSharedLock, calls EnterCriticalSection (no parallel readings)
|
|
// - warning: if RWUse=uRWLock, this method will use the internal TRWLock
|
|
// - defined in protected section for better inlining and to fix a Delphi
|
|
// compiler bug about warning a missing Windows unit in the uses classes
|
|
procedure RWLock(context: TRWLockContext);
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
procedure RWUnLock(context: TRWLockContext);
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
public
|
|
/// internal padding data, also used to store up to 7 variant values
|
|
// - this memory buffer will ensure no CPU cache line mixup occurs
|
|
// - you should not use this field directly, but rather the Locked[],
|
|
// LockedInt64[], LockedUtf8[] or LockedPointer[] methods
|
|
// - if you want to access those array values, ensure you protect them
|
|
// using a Safe.Lock; try ... Padding[n] ... finally Safe.Unlock structure,
|
|
// and maintain the PaddingUsedCount property accurately
|
|
Padding: array[0..6] of TVarData;
|
|
/// initialize the mutex
|
|
// - calling this method is mandatory (e.g. in the class constructor owning
|
|
// the TSynLocker instance), otherwise you may encounter unexpected
|
|
// behavior, like access violations or memory leaks
|
|
procedure Init;
|
|
/// finalize the mutex
|
|
// - calling this method is mandatory (e.g. in the class destructor owning
|
|
// the TSynLocker instance), otherwise you may encounter unexpected
|
|
// behavior, like access violations or memory leaks
|
|
procedure Done;
|
|
/// finalize the mutex, and call FreeMem() on the pointer of this instance
|
|
// - should have been initiazed with a NewSynLocker call
|
|
procedure DoneAndFreeMem;
|
|
/// low-level acquisition of the lock, as RWLock(cReadOnly)
|
|
// - if RWUse=uSharedLock, calls EnterCriticalSection (no parallel readings)
|
|
// - warning: with RWUse=uRWLock, a nested Lock call would deadlock, but not
|
|
// nested ReadLock calls
|
|
procedure ReadLock;
|
|
/// low-level release of the lock, as RWUnLock(cReadOnly)
|
|
procedure ReadUnLock;
|
|
/// low-level acquisition of the lock, as RWLock(cReadWrite)
|
|
// - if RWUse=uSharedLock, calls EnterCriticalSection (no parallel readings)
|
|
// - with RWUse=uRWLock, a nested Lock call would not deadlock
|
|
procedure ReadWriteLock;
|
|
/// low-level release of the lock, as RWUnLock(cReadWrite)
|
|
procedure ReadWriteUnLock;
|
|
/// lock the instance for exclusive access, as RWLock(cWrite)
|
|
// - is re-entrant from the same thread i.e. you can nest Lock/UnLock calls
|
|
// - warning: with RWUse=uRWLock, would deadlock after a nested ReadLock,
|
|
// but not after ReadWriteLock
|
|
// - use as such to avoid race condition (from a Safe: TSynLocker property):
|
|
// ! Safe.Lock;
|
|
// ! try
|
|
// ! ...
|
|
// ! finally
|
|
// ! Safe.Unlock;
|
|
// ! end;
|
|
procedure Lock;
|
|
/// will try to acquire the mutex
|
|
// - do nothing and return false if RWUse is not the default uSharedLock
|
|
// - use as such to avoid race condition (from a Safe: TSynLocker property):
|
|
// ! if Safe.TryLock then
|
|
// ! try
|
|
// ! ...
|
|
// ! finally
|
|
// ! Safe.Unlock;
|
|
// ! end;
|
|
function TryLock: boolean;
|
|
/// will try to acquire the mutex for a given time
|
|
// - just wait and return false if RWUse is not the default uSharedLock
|
|
// - use as such to avoid race condition (from a Safe: TSynLocker property):
|
|
// ! if Safe.TryLockMS(100) then
|
|
// ! try
|
|
// ! ...
|
|
// ! finally
|
|
// ! Safe.Unlock;
|
|
// ! end;
|
|
function TryLockMS(retryms: integer; terminated: PBoolean = nil): boolean;
|
|
/// release the instance for exclusive access, as RWUnLock(cWrite)
|
|
// - each Lock/TryLock should have its exact UnLock opposite, so a
|
|
// try..finally block is mandatory for safe code
|
|
procedure UnLock; overload;
|
|
/// will enter the mutex until the IUnknown reference is released
|
|
// - could be used as such under Delphi:
|
|
// !begin
|
|
// ! ... // unsafe code
|
|
// ! Safe.ProtectMethod;
|
|
// ! ... // thread-safe code
|
|
// !end; // local hidden IUnknown will release the lock for the method
|
|
// - warning: under FPC, you should assign its result to a local variable -
|
|
// see bug http://bugs.freepascal.org/view.php?id=26602
|
|
// !var
|
|
// ! LockFPC: IUnknown;
|
|
// !begin
|
|
// ! ... // unsafe code
|
|
// ! LockFPC := Safe.ProtectMethod;
|
|
// ! ... // thread-safe code
|
|
// !end; // LockFPC will release the lock for the method
|
|
// or
|
|
// !begin
|
|
// ! ... // unsafe code
|
|
// ! with Safe.ProtectMethod do
|
|
// ! begin
|
|
// ! ... // thread-safe code
|
|
// ! end; // local hidden IUnknown will release the lock for the method
|
|
// !end;
|
|
function ProtectMethod: IUnknown;
|
|
/// number of values stored in the internal Padding[] array
|
|
// - equals 0 if no value is actually stored, or a 1..7 number otherwise
|
|
// - you should not have to use this field, but for optimized low-level
|
|
// direct access to Padding[] values, within a Lock/UnLock safe block
|
|
property PaddingUsedCount: byte
|
|
read fPaddingUsedCount write fPaddingUsedCount;
|
|
/// returns true if the mutex is currently locked by another thread
|
|
// - with RWUse=uRWLock, any lock (even ReadOnlyLock) would return true
|
|
property IsLocked: boolean
|
|
read GetIsLocked;
|
|
/// returns true if the Init method has been called for this mutex
|
|
// - is only relevant if the whole object has been previously filled with 0,
|
|
// i.e. as part of a class or as global variable, but won't be accurate
|
|
// when allocated on stack
|
|
property IsInitialized: boolean
|
|
read fInitialized;
|
|
/// safe locked access to a Variant value
|
|
// - you may store up to 7 variables, using an 0..6 index, shared with
|
|
// LockedBool, LockedInt64, LockedPointer and LockedUtf8 array properties
|
|
// - returns null if the Index is out of range
|
|
// - allow concurrent thread reading if RWUse was set to uRWLock
|
|
property Locked[Index: integer]: Variant
|
|
read GetVariant write SetVariant;
|
|
/// safe locked access to a Int64 value
|
|
// - you may store up to 7 variables, using an 0..6 index, shared with
|
|
// Locked and LockedUtf8 array properties
|
|
// - Int64s will be stored internally as a varInt64 variant
|
|
// - returns nil if the Index is out of range, or does not store a Int64
|
|
// - allow concurrent thread reading if RWUse was set to uRWLock
|
|
property LockedInt64[Index: integer]: Int64
|
|
read GetInt64 write SetInt64;
|
|
/// safe locked access to a boolean value
|
|
// - you may store up to 7 variables, using an 0..6 index, shared with
|
|
// Locked, LockedInt64, LockedPointer and LockedUtf8 array properties
|
|
// - value will be stored internally as a varboolean variant
|
|
// - returns nil if the Index is out of range, or does not store a boolean
|
|
// - allow concurrent thread reading if RWUse was set to uRWLock
|
|
property LockedBool[Index: integer]: boolean
|
|
read GetBool write SetBool;
|
|
/// safe locked access to a pointer/TObject value
|
|
// - you may store up to 7 variables, using an 0..6 index, shared with
|
|
// Locked, LockedBool, LockedInt64 and LockedUtf8 array properties
|
|
// - pointers will be stored internally as a varUnknown variant
|
|
// - returns nil if the Index is out of range, or does not store a pointer
|
|
// - allow concurrent thread reading if RWUse was set to uRWLock
|
|
property LockedPointer[Index: integer]: Pointer
|
|
read GetPointer write SetPointer;
|
|
/// safe locked access to an UTF-8 string value
|
|
// - you may store up to 7 variables, using an 0..6 index, shared with
|
|
// Locked and LockedPointer array properties
|
|
// - UTF-8 string will be stored internally as a varString variant
|
|
// - returns '' if the Index is out of range, or does not store a string
|
|
// - allow concurrent thread reading if RWUse was set to uRWLock
|
|
property LockedUtf8[Index: integer]: RawUtf8
|
|
read GetUtf8 write SetUtf8;
|
|
/// safe locked in-place increment to an Int64 value
|
|
// - you may store up to 7 variables, using an 0..6 index, shared with
|
|
// Locked and LockedUtf8 array properties
|
|
// - Int64s will be stored internally as a varInt64 variant
|
|
// - returns the newly stored value
|
|
// - if the internal value is not defined yet, would use 0 as default value
|
|
function LockedInt64Increment(Index: integer; const Increment: Int64): Int64;
|
|
/// safe locked in-place exchange of a Variant value
|
|
// - you may store up to 7 variables, using an 0..6 index, shared with
|
|
// Locked and LockedUtf8 array properties
|
|
// - returns the previous stored value, or null if the Index is out of range
|
|
function LockedExchange(Index: integer; const Value: variant): variant;
|
|
/// safe locked in-place exchange of a pointer/TObject value
|
|
// - you may store up to 7 variables, using an 0..6 index, shared with
|
|
// Locked and LockedUtf8 array properties
|
|
// - pointers will be stored internally as a varUnknown variant
|
|
// - returns the previous stored value, nil if the Index is out of range,
|
|
// or does not store a pointer
|
|
function LockedPointerExchange(Index: integer; Value: pointer): pointer;
|
|
/// unsafe access to a Int64 value
|
|
// - you may store up to 7 variables, using an 0..6 index, shared with
|
|
// Locked and LockedUtf8 array properties
|
|
// - Int64s will be stored internally as a varInt64 variant
|
|
// - returns nil if the Index is out of range, or does not store a Int64
|
|
// - you should rather call LockedInt64[] property, or use this property
|
|
// with a Lock; try ... finally UnLock block
|
|
property UnlockedInt64[Index: integer]: Int64
|
|
read GetUnlockedInt64 write SetUnlockedInt64;
|
|
/// how RWLock/RWUnLock would be processed
|
|
property RWUse: TSynLockerUse
|
|
read fRWUse write fRWUse;
|
|
end;
|
|
|
|
/// a pointer to a TSynLocker mutex instance
|
|
// - see also NewSynLocker and TSynLocker.DoneAndFreemem functions
|
|
PSynLocker = ^TSynLocker;
|
|
|
|
/// raw class used by TAutoLocker.ProtectMethod and TSynLocker.ProtectMethod
|
|
// - defined here for use by TAutoLocker in mormot.core.data.pas
|
|
TAutoLock = class(TInterfacedObject)
|
|
protected
|
|
fLock: PSynLocker;
|
|
public
|
|
constructor Create(aLock: PSynLocker);
|
|
destructor Destroy; override;
|
|
end;
|
|
|
|
/// our lightweight cross-platform TEvent-like component
|
|
// - on Windows, calls directly the CreateEvent/ResetEvent/SetEvent API
|
|
// - on Linux, will use eventfd() in blocking and non-semaphore mode
|
|
// - on other POSIX, will use PRTLEvent which is lighter than TEvent BasicEvent
|
|
// - only limitation is that we don't know if WaitFor is signaled or timeout,
|
|
// but this is not a real problem in practice since most code don't need this
|
|
// information or has already its own flag in its implementation logic
|
|
TSynEvent = class
|
|
protected
|
|
fHandle: pointer; // Windows THandle or FPC PRTLEvent
|
|
fFD: integer; // for eventfd()
|
|
public
|
|
/// initialize an instance of cross-platform event
|
|
constructor Create;
|
|
/// finalize this instance of cross-platform event
|
|
destructor Destroy; override;
|
|
/// ignore any pending events, so that WaitFor will be set on next SetEvent
|
|
procedure ResetEvent;
|
|
{$ifdef OSPOSIX} inline; {$endif}
|
|
/// trigger any pending event, releasing the WaitFor/WaitForEver methods
|
|
procedure SetEvent;
|
|
{$ifdef OSPOSIX} inline; {$endif}
|
|
/// wait until SetEvent is called from another thread, with a maximum time
|
|
// - does not return if it was signaled or timeout
|
|
// - WARNING: you should wait from a single thread at once
|
|
procedure WaitFor(TimeoutMS: integer);
|
|
{$ifdef OSPOSIX} inline; {$endif}
|
|
/// wait until SetEvent is called from another thread, with no maximum time
|
|
procedure WaitForEver;
|
|
{$ifdef OSPOSIX} inline; {$endif}
|
|
/// calls SleepHiRes() in steps while checking terminated flag and this event
|
|
function SleepStep(var start: Int64; terminated: PBoolean): Int64;
|
|
/// could be used to tune your algorithm if the eventfd() API is used
|
|
function IsEventFD: boolean;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
end;
|
|
|
|
|
|
/// initialize a TSynLocker instance from heap
|
|
// - call DoneandFreeMem to release the associated memory and OS mutex
|
|
// - is used e.g. in TSynPersistentLock to reduce class instance size
|
|
function NewSynLocker: PSynLocker;
|
|
|
|
type
|
|
{$M+}
|
|
|
|
/// a persistent-agnostic alternative to TSynPersistentLock
|
|
// - can be used as base class when custom JSON persistence is not needed
|
|
// - consider a TRWLock field as a lighter multi read / exclusive write option
|
|
TSynLocked = class
|
|
protected
|
|
fSafe: PSynLocker; // TSynLocker would increase inherited fields offset
|
|
public
|
|
/// initialize the instance, and its associated lock
|
|
// - is defined as virtual, just like TObjectWithCustomCreate/TSynPersistent
|
|
constructor Create; virtual;
|
|
/// finalize the instance, and its associated lock
|
|
destructor Destroy; override;
|
|
/// access to the associated instance critical section
|
|
// - call Safe.Lock/UnLock to protect multi-thread access on this storage
|
|
property Safe: PSynLocker
|
|
read fSafe;
|
|
end;
|
|
|
|
{$M-}
|
|
|
|
/// meta-class definition of the TSynLocked hierarchy
|
|
TSynLockedClass = class of TSynLocked;
|
|
|
|
/// a thread-safe Pierre L'Ecuyer software random generator
|
|
// - just wrap TLecuyer with a TLighLock
|
|
// - should not be used, unless may be slightly faster than a threadvar
|
|
{$ifdef USERECORDWITHMETHODS}
|
|
TLecuyerThreadSafe = record
|
|
{$else}
|
|
TLecuyerThreadSafe = object
|
|
{$endif USERECORDWITHMETHODS}
|
|
public
|
|
Safe: TLightLock;
|
|
Generator: TLecuyer;
|
|
/// compute the next 32-bit generated value
|
|
function Next: cardinal; overload;
|
|
/// compute a 64-bit floating point value
|
|
function NextDouble: double;
|
|
/// XOR some memory buffer with random bytes
|
|
procedure Fill(dest: pointer; count: integer);
|
|
/// fill some string[31] with 7-bit ASCII random text
|
|
procedure FillShort31(var dest: TShort31);
|
|
end;
|
|
|
|
TThreadIDDynArray = array of TThreadID;
|
|
|
|
var
|
|
/// a global thread-safe Pierre L'Ecuyer software random generator
|
|
// - should not be used, unless may be slightly faster than a threadvar
|
|
SharedRandom: TLecuyerThreadSafe;
|
|
|
|
{$ifdef OSPOSIX}
|
|
/// could be set to TRUE to force SleepHiRes(0) to call the POSIX sched_yield
|
|
// - in practice, it has been reported as buggy under POSIX systems
|
|
// - even Linus Torvald himself raged against its usage - see e.g.
|
|
// https://www.realworldtech.com/forum/?threadid=189711&curpostid=189752
|
|
// - you may tempt the devil and try it by yourself
|
|
SleepHiRes0Yield: boolean = false;
|
|
{$endif OSPOSIX}
|
|
|
|
/// similar to Windows sleep() API call, to be truly cross-platform
|
|
// - using millisecond resolution
|
|
// - SleepHiRes(0) calls ThreadSwitch on Windows, but POSIX version will
|
|
// wait 10 microsecond unless SleepHiRes0Yield is forced to true (bad idea)
|
|
// - in respect to RTL's Sleep() function, it will return on ESysEINTR if was
|
|
// interrupted by any OS signal
|
|
// - warning: wait typically for the next system timer interrupt on Windows,
|
|
// which is every 16ms by default; as a consequence, never rely on the ms
|
|
// supplied value to guess the elapsed time, but call GetTickCount64
|
|
procedure SleepHiRes(ms: cardinal); overload;
|
|
|
|
/// similar to Windows sleep() API call, but truly cross-platform and checking
|
|
// the Terminated flag during its wait for quick abort response
|
|
// - returns true if terminated^ was set to true (terminatedvalue)
|
|
function SleepHiRes(ms: cardinal; var terminated: boolean;
|
|
terminatedvalue: boolean = true): boolean; overload;
|
|
|
|
/// call SleepHiRes() taking count of the activity, in 0/1/5/50/120-250 ms steps
|
|
// - range is agressively designed burning some CPU in favor of responsiveness
|
|
// - should reset start := 0 when some activity occurred, or start := -1 on
|
|
// Windows to avoid any SleepHiRes(0) = SwitchToThread call
|
|
// - would optionally return if terminated^ is set, or event is signaled
|
|
// - returns the current GetTickCount64 value
|
|
function SleepStep(var start: Int64; terminated: PBoolean = nil): Int64;
|
|
|
|
/// compute optimal sleep time as 0/1/5/50 then 120-250 ms steps
|
|
// - is agressively designed burning some CPU in favor of responsiveness
|
|
function SleepDelay(elapsed: PtrInt): PtrInt;
|
|
|
|
/// compute optimal sleep time as SleepStep, in 0/1/5/50/120-250 ms steps
|
|
// - is agressively designed burning some CPU in favor of responsiveness
|
|
// - start=0 would fill its value with tix; start<0 would fill its value with
|
|
// tix-50 so that SleepDelay() would never call SleepHiRes(0)
|
|
function SleepStepTime(var start, tix: Int64; endtix: PInt64 = nil): PtrInt;
|
|
|
|
/// similar to Windows SwitchToThread API call, to be truly cross-platform
|
|
// - call fpnanosleep(10) on POSIX systems, or the homonymous API on Windows
|
|
procedure SwitchToThread;
|
|
{$ifdef OSWINDOWS} stdcall; {$endif}
|
|
|
|
/// try LockedExc() in a loop, calling SwitchToThread after some spinning
|
|
procedure SpinExc(var Target: PtrUInt; NewValue, Comperand: PtrUInt);
|
|
|
|
/// wrapper to implement a thread-safe T*ObjArray dynamic array storage
|
|
function ObjArrayAdd(var aObjArray; aItem: TObject;
|
|
var aSafe: TLightLock; aCount: PInteger = nil): PtrInt; overload;
|
|
|
|
/// wrapper to implement a thread-safe pointer dynamic array storage
|
|
function PtrArrayDelete(var aPtrArray; aItem: pointer; var aSafe: TLightLock;
|
|
aCount: PInteger = nil): PtrInt; overload;
|
|
|
|
/// try to kill/cancel a thread
|
|
// - on Windows, calls the TerminateThread() API
|
|
// - under Linux/FPC, calls pthread_cancel() API which is asynchronous
|
|
function RawKillThread(Thread: TThread): boolean;
|
|
|
|
type
|
|
/// store a bitmask of logical CPU cores, as used by SetThreadMaskAffinity
|
|
// - has 32/64-bit pointer-size on Windows, or 1024 bits on POSIX
|
|
TCpuSet = {$ifdef OSWINDOWS} PtrUInt {$else} array[0..127] of byte {$endif};
|
|
var
|
|
/// low-level bitmasks of logical CPU cores hosted on each hardware CPU socket
|
|
// - filled at process startup as CpuSocketsMask[0 .. CpuSockets - 1] range
|
|
CpuSocketsMask: array of TCpuSet;
|
|
|
|
/// fill a bitmask of CPU cores with zeros
|
|
procedure ResetCpuSet(out CpuSet: TCpuSet);
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
|
|
/// set a particular bit in a mask of CPU cores
|
|
function SetCpuSet(var CpuSet: TCpuSet; CpuIndex: cardinal): boolean;
|
|
|
|
/// retrieve the current CPU cores masks available of the system
|
|
// - the current process may have been tuned to use only a sub-set of the cores
|
|
// e.g. via "taskset -c" on Linux
|
|
// - return the number of accessible CPU cores - i.e. GetBitsCount(CpuSet) or
|
|
// 0 if the function failed
|
|
function CurrentCpuSet(out CpuSet: TCpuSet): integer;
|
|
|
|
/// try to assign a given thread to a specific set of logical CPU core(s)
|
|
// - on Windows, calls the SetThreadAffinityMask() API
|
|
// - under Linux/FPC, calls pthread_setaffinity_np() API
|
|
function SetThreadMaskAffinity(Thread: TThread; const Mask: TCpuSet): boolean;
|
|
|
|
/// try to assign a given thread to a specific logical CPU core
|
|
// - CpuIndex should be in 0 .. SystemInfo.dwNumberOfProcessors - 1 range
|
|
function SetThreadCpuAffinity(Thread: TThread; CpuIndex: cardinal): boolean;
|
|
|
|
/// try to assign a given thread to a specific hardware CPU socket
|
|
// - SocketIndex should be in 0 .. CpuSockets - 1 range, and will use the
|
|
// CpuSocketsMask[] information retrieved during process startup
|
|
function SetThreadSocketAffinity(Thread: TThread; SocketIndex: cardinal): boolean;
|
|
|
|
/// low-level naming of a thread
|
|
// - on Windows, will raise a standard "fake" exception to notify the thread name
|
|
// - under Linux/FPC, calls pthread_setname_np() API which truncates to 16 chars
|
|
procedure RawSetThreadName(ThreadID: TThreadID; const Name: RawUtf8);
|
|
|
|
/// name the current thread so that it would be easily identified in the IDE debugger
|
|
// - could then be retrieved by CurrentThreadNameShort/GetCurrentThreadName
|
|
// - just a wrapper around SetThreadName(GetCurrentThreadId, ...)
|
|
procedure SetCurrentThreadName(const Format: RawUtf8; const Args: array of const); overload;
|
|
|
|
/// name the current thread so that it would be easily identified in the IDE debugger
|
|
// - could also be retrieved by CurrentThreadNameShort/GetCurrentThreadName
|
|
// - just a wrapper around SetThreadName(GetCurrentThreadId, ...)
|
|
procedure SetCurrentThreadName(const Name: RawUtf8); overload;
|
|
|
|
var
|
|
/// name a thread so that it would be easily identified in the IDE debugger
|
|
// - default implementation does nothing, unless mormot.core.log is included
|
|
// - you can force this function to do nothing by setting the NOSETTHREADNAME
|
|
// conditional, if you have issues with this feature when debugging your app
|
|
// - most meaningless patterns (like 'TSql') are trimmed to reduce the
|
|
// resulting length - which is convenient e.g. with POSIX truncation to 16 chars
|
|
// - you can retrieve the name later on using CurrentThreadNameShort
|
|
// - this method will register TSynLog.LogThreadName(), so threads calling it
|
|
// should also call TSynLogFamily.OnThreadEnded/TSynLog.NotifyThreadEnded
|
|
SetThreadName: procedure(ThreadID: TThreadID; const Format: RawUtf8;
|
|
const Args: array of const);
|
|
|
|
/// low-level access to the thread name, as set by SetThreadName()
|
|
// - since threadvar can't contain managed strings, it is defined as TShort31,
|
|
// so is limited to 31 chars, which is enough since POSIX truncates to 16 chars
|
|
// and SetThreadName does trim meaningless patterns
|
|
function CurrentThreadNameShort: PShortString;
|
|
|
|
/// retrieve the thread name, as set by SetThreadName()
|
|
// - if possible, direct CurrentThreadNameShort function is slightly faster
|
|
// - will return the CurrentThreadNameShort^ threadvar 31 chars value
|
|
function GetCurrentThreadName: RawUtf8;
|
|
|
|
/// returns the thread id and the thread name as a ShortString
|
|
// - returns e.g. 'Thread 0001abcd [shortthreadname]'
|
|
// - for convenient use when logging or raising an exception
|
|
function GetCurrentThreadInfo: ShortString;
|
|
|
|
/// enter a process-wide giant lock for thread-safe shared process
|
|
// - shall be protected as such:
|
|
// ! GlobalLock;
|
|
// ! try
|
|
// ! .... do something thread-safe but as short as possible
|
|
// ! finally
|
|
// ! GlobalUnLock;
|
|
// ! end;
|
|
// - you should better not use such a giant-lock, but an instance-dedicated
|
|
// critical section/TSynLocker or TRWLock - these functions are just here to be
|
|
// convenient, for non time-critical process (e.g. singleton initialization
|
|
// of external libraries, or before RegisterGlobalShutdownRelease() which will
|
|
// use it anyway)
|
|
procedure GlobalLock;
|
|
|
|
/// release the giant lock for thread-safe shared process
|
|
procedure GlobalUnLock;
|
|
|
|
/// framework will register here some instances to be released eventually
|
|
// - better in this root unit than in each finalization section
|
|
// - its use is protected by the GlobalLock
|
|
function RegisterGlobalShutdownRelease(Instance: TObject;
|
|
SearchExisting: boolean = false): pointer;
|
|
|
|
|
|
{ ****************** Unix Daemon and Windows Service Support }
|
|
|
|
type
|
|
/// all possible states of a Windows service
|
|
// - on POSIX, will identify only if the daemon is ssRunning or ssStopped
|
|
TServiceState = (
|
|
ssNotInstalled,
|
|
ssStopped,
|
|
ssStarting,
|
|
ssStopping,
|
|
ssRunning,
|
|
ssResuming,
|
|
ssPausing,
|
|
ssPaused,
|
|
ssFailed,
|
|
ssErrorRetrievingState);
|
|
PServiceState = ^TServiceState;
|
|
TServiceStateDynArray = array of TServiceState;
|
|
|
|
/// return the ready to be displayed text of a TServiceState value
|
|
function ToText(st: TServiceState): PShortString; overload;
|
|
|
|
const
|
|
/// could be used with ConsoleWrite() to notify a Windows service state
|
|
SERVICESTATE_COLOR: array[TServiceState] of TConsoleColor = (
|
|
ccBlue, // NotInstalled
|
|
ccLightRed, // Stopped
|
|
ccGreen, // Starting
|
|
ccRed, // Stopping
|
|
ccLightGreen, // Running
|
|
ccGreen, // Resuming
|
|
ccBrown, // Pausing
|
|
ccWhite, // Paused
|
|
ccMagenta, // Failed
|
|
ccYellow); // ErrorRetrievingState
|
|
|
|
|
|
{$ifdef OSWINDOWS}
|
|
|
|
{ *** some minimal Windows API definitions, replacing WinSvc.pas missing for FPC }
|
|
|
|
const
|
|
SERVICE_QUERY_CONFIG = $0001;
|
|
SERVICE_CHANGE_CONFIG = $0002;
|
|
SERVICE_QUERY_STATUS = $0004;
|
|
SERVICE_ENUMERATE_DEPENDENTS = $0008;
|
|
SERVICE_START = $0010;
|
|
SERVICE_STOP = $0020;
|
|
SERVICE_PAUSE_CONTINUE = $0040;
|
|
SERVICE_INTERROGATE = $0080;
|
|
SERVICE_USER_DEFINED_CONTROL = $0100;
|
|
SERVICE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED or
|
|
SERVICE_QUERY_CONFIG or
|
|
SERVICE_CHANGE_CONFIG or
|
|
SERVICE_QUERY_STATUS or
|
|
SERVICE_ENUMERATE_DEPENDENTS or
|
|
SERVICE_START or
|
|
SERVICE_STOP or
|
|
SERVICE_PAUSE_CONTINUE or
|
|
SERVICE_INTERROGATE or
|
|
SERVICE_USER_DEFINED_CONTROL;
|
|
|
|
SC_MANAGER_CONNECT = $0001;
|
|
SC_MANAGER_CREATE_SERVICE = $0002;
|
|
SC_MANAGER_ENUMERATE_SERVICE = $0004;
|
|
SC_MANAGER_LOCK = $0008;
|
|
SC_MANAGER_QUERY_LOCK_STATUS = $0010;
|
|
SC_MANAGER_MODIFY_BOOT_CONFIG = $0020;
|
|
SC_MANAGER_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED or
|
|
SC_MANAGER_CONNECT or
|
|
SC_MANAGER_CREATE_SERVICE or
|
|
SC_MANAGER_ENUMERATE_SERVICE or
|
|
SC_MANAGER_LOCK or
|
|
SC_MANAGER_QUERY_LOCK_STATUS or
|
|
SC_MANAGER_MODIFY_BOOT_CONFIG;
|
|
|
|
SERVICE_CONFIG_DESCRIPTION = $0001;
|
|
|
|
SERVICE_WIN32_OWN_PROCESS = $00000010;
|
|
SERVICE_WIN32_SHARE_PROCESS = $00000020;
|
|
SERVICE_INTERACTIVE_PROCESS = $00000100;
|
|
|
|
SERVICE_BOOT_START = $00000000;
|
|
SERVICE_SYSTEM_START = $00000001;
|
|
SERVICE_AUTO_START = $00000002;
|
|
SERVICE_DEMAND_START = $00000003;
|
|
SERVICE_DISABLED = $00000004;
|
|
SERVICE_ERROR_IGNORE = $00000000;
|
|
SERVICE_ERROR_NORMAL = $00000001;
|
|
SERVICE_ERROR_SEVERE = $00000002;
|
|
SERVICE_ERROR_CRITICAL = $00000003;
|
|
|
|
SERVICE_CONTROL_STOP = $00000001;
|
|
SERVICE_CONTROL_PAUSE = $00000002;
|
|
SERVICE_CONTROL_CONTINUE = $00000003;
|
|
SERVICE_CONTROL_INTERROGATE = $00000004;
|
|
SERVICE_CONTROL_SHUTDOWN = $00000005;
|
|
|
|
SERVICE_STOPPED = $00000001;
|
|
SERVICE_START_PENDING = $00000002;
|
|
SERVICE_STOP_PENDING = $00000003;
|
|
SERVICE_RUNNING = $00000004;
|
|
SERVICE_CONTINUE_PENDING = $00000005;
|
|
SERVICE_PAUSE_PENDING = $00000006;
|
|
SERVICE_PAUSED = $00000007;
|
|
|
|
ERROR_FAILED_SERVICE_CONTROLLER_CONNECT = 1063;
|
|
|
|
type
|
|
PServiceStatus = ^TServiceStatus;
|
|
TServiceStatus = record
|
|
dwServiceType: cardinal;
|
|
dwCurrentState: cardinal;
|
|
dwControlsAccepted: cardinal;
|
|
dwWin32ExitCode: cardinal;
|
|
dwServiceSpecificExitCode: cardinal;
|
|
dwCheckPoint: cardinal;
|
|
dwWaitHint: cardinal;
|
|
end;
|
|
|
|
PServiceStatusProcess = ^TServiceStatusProcess;
|
|
TServiceStatusProcess = record
|
|
Service: TServiceStatus;
|
|
dwProcessId: cardinal;
|
|
dwServiceFlags: cardinal;
|
|
end;
|
|
|
|
SC_HANDLE = THandle;
|
|
SERVICE_STATUS_HANDLE = THandle;
|
|
TServiceTableEntry = record
|
|
lpServiceName: PWideChar;
|
|
lpServiceProc: procedure(ArgCount: cardinal; Args: PPWideChar); stdcall;
|
|
end;
|
|
PServiceTableEntry = ^TServiceTableEntry;
|
|
TServiceDescription = record
|
|
lpDestription: PWideChar;
|
|
end;
|
|
|
|
{$Z4}
|
|
SC_STATUS_TYPE = (SC_STATUS_PROCESS_INFO);
|
|
{$Z1}
|
|
|
|
function OpenSCManagerW(lpMachineName, lpDatabaseName: PWideChar;
|
|
dwDesiredAccess: cardinal): SC_HANDLE; stdcall; external advapi32;
|
|
function ChangeServiceConfig2W(hService: SC_HANDLE; dwsInfoLevel: cardinal;
|
|
lpInfo: Pointer): BOOL; stdcall; external advapi32;
|
|
function StartServiceW(hService: SC_HANDLE; dwNumServiceArgs: cardinal;
|
|
lpServiceArgVectors: PPWideChar): BOOL; stdcall; external advapi32;
|
|
function CreateServiceW(hSCManager: SC_HANDLE;
|
|
lpServiceName, lpDisplayName: PWideChar;
|
|
dwDesiredAccess, dwServiceType, dwStartType, dwErrorControl: cardinal;
|
|
lpBinaryPathName, lpLoadOrderGroup: PWideChar; lpdwTagId: LPDWORD; lpDependencies,
|
|
lpServiceStartName, lpPassword: PWideChar): SC_HANDLE; stdcall; external advapi32;
|
|
function OpenServiceW(hSCManager: SC_HANDLE; lpServiceName: PWideChar;
|
|
dwDesiredAccess: cardinal): SC_HANDLE; stdcall; external advapi32;
|
|
function DeleteService(hService: SC_HANDLE): BOOL; stdcall; external advapi32;
|
|
function CloseServiceHandle(hSCObject: SC_HANDLE): BOOL; stdcall; external advapi32;
|
|
function QueryServiceStatus(hService: SC_HANDLE;
|
|
var lpServiceStatus: TServiceStatus): BOOL; stdcall; external advapi32;
|
|
function QueryServiceStatusEx(hService: SC_HANDLE;
|
|
InfoLevel: SC_STATUS_TYPE; lpBuffer: Pointer; cbBufSize: cardinal;
|
|
var pcbBytesNeeded: cardinal): BOOL; stdcall; external advapi32;
|
|
function ControlService(hService: SC_HANDLE; dwControl: cardinal;
|
|
var lpServiceStatus: TServiceStatus): BOOL; stdcall; external advapi32;
|
|
function SetServiceStatus(hServiceStatus: SERVICE_STATUS_HANDLE;
|
|
var lpServiceStatus: TServiceStatus): BOOL; stdcall; external advapi32;
|
|
function RegisterServiceCtrlHandlerW(lpServiceName: PWideChar;
|
|
lpHandlerProc: TFarProc): SERVICE_STATUS_HANDLE; stdcall; external advapi32;
|
|
function StartServiceCtrlDispatcherW(
|
|
lpServiceStartTable: PServiceTableEntry): BOOL; stdcall; external advapi32;
|
|
|
|
function OpenServiceManager(const TargetComputer, DatabaseName: RawUtf8;
|
|
dwDesiredAccess: cardinal): SC_HANDLE;
|
|
function OpenServiceInstance(hSCManager: SC_HANDLE; const ServiceName: RawUtf8;
|
|
dwDesiredAccess: cardinal): SC_HANDLE;
|
|
|
|
|
|
{ *** high level classes to define and manage Windows Services }
|
|
|
|
var
|
|
/// can be assigned from TSynLog.DoLog class method for
|
|
// TServiceController/TService logging
|
|
// - default is nil, i.e. disabling logging, since it may interfere with the
|
|
// logging process of the Windows Service itself
|
|
WindowsServiceLog: TSynLogProc;
|
|
|
|
type
|
|
/// TServiceControler class is intended to create a new Windows Service instance
|
|
// or to maintain (that is start, stop, pause, resume...) an existing Service
|
|
// - to provide the service itself, use the TService class
|
|
TServiceController = class
|
|
protected
|
|
fSCHandle: THandle;
|
|
fHandle: THandle;
|
|
fStatus: TServiceStatus;
|
|
fName: RawUtf8;
|
|
protected
|
|
function GetStatus: TServiceStatus;
|
|
function GetState: TServiceState;
|
|
public
|
|
/// create a new Windows Service and control it and/or its configuration
|
|
// - TargetComputer - set it to empty string if local computer is the target.
|
|
// - DatabaseName - set it to empty string if the default database is supposed
|
|
// ('ServicesActive').
|
|
// - Name - name of a service.
|
|
// - DisplayName - display name of a service.
|
|
// - Path - a path to binary (executable) of the service created.
|
|
// - OrderGroup - an order group name (unnecessary)
|
|
// - Dependencies - string containing a list with names of services, which must
|
|
// start before this service (every name should be separated with ';' or an
|
|
// empty string can be passed if there is no dependency).
|
|
// - Username - login name. For service type SERVICE_WIN32_OWN_PROCESS, the
|
|
// account name in the form of "DomainName\Username"; If the account
|
|
// belongs to the built-in domain, ".\Username" can be specified;
|
|
// Services of type SERVICE_WIN32_SHARE_PROCESS are not allowed to
|
|
// specify an account other than LocalSystem. If '' is specified, the
|
|
// service will be logged on as the 'LocalSystem' account, in which
|
|
// case, the Password parameter must be empty too.
|
|
// - Password - a password for login name. If the service type is
|
|
// SERVICE_KERNEL_DRIVER or SERVICE_FILE_SYSTEM_DRIVER,
|
|
// this parameter is ignored.
|
|
// - DesiredAccess - a combination of following flags:
|
|
// SERVICE_ALL_ACCESS (default value), SERVICE_CHANGE_CONFIG,
|
|
// SERVICE_ENUMERATE_DEPENDENTS, SERVICE_INTERROGATE, SERVICE_PAUSE_CONTINUE,
|
|
// SERVICE_QUERY_CONFIG, SERVICE_QUERY_STATUS, SERVICE_START, SERVICE_STOP,
|
|
// SERVICE_USER_DEFINED_CONTROL
|
|
// - ServiceType - a set of following flags:
|
|
// SERVICE_WIN32_OWN_PROCESS (default value, which specifies a Win32 service
|
|
// that runs in its own process), SERVICE_WIN32_SHARE_PROCESS,
|
|
// SERVICE_KERNEL_DRIVER, SERVICE_FILE_SYSTEM_DRIVER,
|
|
// SERVICE_INTERACTIVE_PROCESS (default value, which enables a Win32 service
|
|
// process to interact with the desktop)
|
|
// - StartType - one of following values:
|
|
// SERVICE_BOOT_START, SERVICE_SYSTEM_START,
|
|
// SERVICE_AUTO_START (which specifies a device driver or service started by
|
|
// the service control manager automatically during system startup),
|
|
// SERVICE_DEMAND_START (default value, which specifies a service started by
|
|
// a service control manager when a process calls the StartService function,
|
|
// that is the TServiceController.Start method), SERVICE_DISABLED
|
|
// - ErrorControl - one of following:
|
|
// SERVICE_ERROR_IGNORE, SERVICE_ERROR_NORMAL (default value, by which
|
|
// the startup program logs the error and displays a message but continues
|
|
// the startup operation), SERVICE_ERROR_SEVERE,
|
|
// SERVICE_ERROR_CRITICAL
|
|
constructor CreateNewService(
|
|
const TargetComputer, DatabaseName, Name, DisplayName: RawUtf8;
|
|
const Path: TFileName;
|
|
const OrderGroup: RawUtf8 = ''; const Dependencies: RawUtf8 = '';
|
|
const Username: RawUtf8 = ''; const Password: RawUtf8 = '';
|
|
DesiredAccess: cardinal = SERVICE_ALL_ACCESS;
|
|
ServiceType: cardinal = SERVICE_WIN32_OWN_PROCESS or SERVICE_INTERACTIVE_PROCESS;
|
|
StartType: cardinal = SERVICE_DEMAND_START;
|
|
ErrorControl: cardinal = SERVICE_ERROR_NORMAL);
|
|
/// wrapper around CreateNewService() to install the current executable as service
|
|
class function Install(const Name, DisplayName, Description: RawUtf8;
|
|
AutoStart: boolean; ExeName: TFileName = '';
|
|
const Dependencies: RawUtf8 = ''; const UserName: RawUtf8 = '';
|
|
const Password: RawUtf8 = ''): TServiceState;
|
|
/// wrapper around CreateOpenService(SERVICE_QUERY_STATUS) and GetState
|
|
class function CurrentState(const Name: RawUtf8): TServiceState;
|
|
/// open an existing service, in order to control it or its configuration
|
|
// from your application
|
|
// - TargetComputer - set it to empty string if local computer is the target.
|
|
// - DatabaseName - set it to empty string if the default database is supposed
|
|
// ('ServicesActive').
|
|
// - Name - name of a service.
|
|
// - DesiredAccess - a combination of following flags:
|
|
// SERVICE_ALL_ACCESS, SERVICE_CHANGE_CONFIG, SERVICE_ENUMERATE_DEPENDENTS,
|
|
// SERVICE_INTERROGATE, SERVICE_PAUSE_CONTINUE, SERVICE_QUERY_CONFIG,
|
|
// SERVICE_QUERY_STATUS, SERVICE_START, SERVICE_STOP, SERVICE_USER_DEFINED_CONTROL
|
|
constructor CreateOpenService(
|
|
const TargetComputer, DataBaseName, Name: RawUtf8;
|
|
DesiredAccess: cardinal = SERVICE_ALL_ACCESS);
|
|
/// release memory and handles
|
|
destructor Destroy; override;
|
|
/// Handle of SC manager
|
|
property SCHandle: THandle
|
|
read fSCHandle;
|
|
/// Handle of service opened or created
|
|
// - its value is 0 if something failed in any Create*() method
|
|
property Handle: THandle
|
|
read fHandle;
|
|
/// Retrieve the Current status of the service
|
|
property Status: TServiceStatus
|
|
read GetStatus;
|
|
/// Retrieve the Current state of the service
|
|
property State: TServiceState
|
|
read GetState;
|
|
/// Requests the service to stop
|
|
function Stop: boolean;
|
|
/// Requests the service to pause
|
|
function Pause: boolean;
|
|
/// Requests the paused service to resume
|
|
function Resume: boolean;
|
|
/// Requests the service to update immediately its current status information
|
|
// to the service control manager
|
|
function Refresh: boolean;
|
|
/// Request the service to shutdown
|
|
// - this function always return false
|
|
function Shutdown: boolean;
|
|
/// Removes service from the system, i.e. close the Service
|
|
function Delete: boolean;
|
|
/// starts the execution of a service with some specified arguments
|
|
// - this version expect PWideChar pointers, i.e. UTF-16 strings
|
|
function Start(const Args: array of PWideChar): boolean;
|
|
/// try to define the description text of this service
|
|
function SetDescription(const Description: RawUtf8): boolean;
|
|
/// this class method will check the command line parameters, and will let
|
|
// control the service according to it
|
|
// - MyServiceSetup.exe /install will install the service
|
|
// - MyServiceSetup.exe /start will start the service
|
|
// - MyServiceSetup.exe /stop will stop the service
|
|
// - MyServiceSetup.exe /uninstall will uninstall the service
|
|
// - so that you can write in the main block of your .dpr:
|
|
// !CheckParameters('MyService.exe',HTTPSERVICENAME,HTTPSERVICEDISPLAYNAME);
|
|
// - if ExeFileName='', it will install the current executable
|
|
// - optional Description and Dependencies text may be specified
|
|
class procedure CheckParameters(const ExeFileName: TFileName;
|
|
const ServiceName, DisplayName, Description: RawUtf8;
|
|
const Dependencies: RawUtf8 = '');
|
|
end;
|
|
|
|
{$M+}
|
|
TService = class;
|
|
{$M-}
|
|
|
|
/// callback procedure for Windows Service Controller
|
|
TServiceControlHandler = procedure(CtrlCode: cardinal); stdcall;
|
|
|
|
/// event triggered for Control handler
|
|
TServiceControlEvent = procedure(Sender: TService; Code: cardinal) of object;
|
|
|
|
/// event triggered to implement the Service functionality
|
|
TServiceEvent = procedure(Sender: TService) of object;
|
|
|
|
/// abstract class to let an executable implement a Windows Service
|
|
// - do not use this class directly, but TServiceSingle
|
|
TService = class
|
|
protected
|
|
fServiceName: RawUtf8;
|
|
fDisplayName: RawUtf8;
|
|
fStartType: cardinal;
|
|
fServiceType: cardinal;
|
|
fData: cardinal;
|
|
fControlHandler: TServiceControlHandler;
|
|
fOnControl: TServiceControlEvent;
|
|
fOnInterrogate: TServiceEvent;
|
|
fOnPause: TServiceEvent;
|
|
fOnShutdown: TServiceEvent;
|
|
fOnStart: TServiceEvent;
|
|
fOnExecute: TServiceEvent;
|
|
fOnResume: TServiceEvent;
|
|
fOnStop: TServiceEvent;
|
|
fStatusRec: TServiceStatus;
|
|
fArgsList: TRawUtf8DynArray;
|
|
fStatusHandle: THandle;
|
|
function GetArgCount: Integer;
|
|
function GetArgs(Idx: Integer): RawUtf8;
|
|
function GetInstalled: boolean;
|
|
procedure SetStatus(const Value: TServiceStatus);
|
|
procedure CtrlHandle(Code: cardinal);
|
|
function GetControlHandler: TServiceControlHandler;
|
|
procedure SetControlHandler(const Value: TServiceControlHandler);
|
|
procedure ServiceProc(ArgCount: integer; Args: PPWideChar);
|
|
public
|
|
/// internal method redirecting to WindowsServiceLog global variable
|
|
class procedure DoLog(Level: TSynLogLevel; const Fmt: RawUtf8;
|
|
const Args: array of const; Instance: TObject);
|
|
/// Creates the service
|
|
// - the service is added to the internal registered services
|
|
// - main application must instantiate the TServiceSingle class, then call
|
|
// the global ServiceSingleRun procedure to actually start the services
|
|
// - caller must free the TService instance when it's no longer used
|
|
constructor Create(const aServiceName, aDisplayName: RawUtf8); reintroduce; virtual;
|
|
/// this method is the main service entrance, from the OS point of view
|
|
// - it will call OnControl/OnStop/OnPause/OnResume/OnShutdown events
|
|
// - and report the service status to the system (via ReportStatus method)
|
|
procedure DoCtrlHandle(Code: cardinal); virtual;
|
|
/// Reports new status to the system
|
|
function ReportStatus(dwState, dwExitCode, dwWait: cardinal): BOOL;
|
|
/// Installs the service in the database
|
|
// - return true on success
|
|
// - create a local TServiceController with the current executable file,
|
|
// with the supplied command line parameters
|
|
// - you can optionally append some parameters, which will be appended
|
|
// to the
|
|
function Install(const Params: TFileName = ''): boolean;
|
|
/// Removes the service from database
|
|
// - uses a local TServiceController with the current Service Name
|
|
procedure Remove;
|
|
/// Starts the service
|
|
// - uses a local TServiceController with the current Service Name
|
|
procedure Start;
|
|
/// Stops the service
|
|
// - uses a local TServiceController with the current Service Name
|
|
procedure Stop;
|
|
/// this is the main method, in which the Service should implement its run
|
|
procedure Execute; virtual;
|
|
|
|
/// Number of arguments passed to the service by the service controler
|
|
property ArgCount: Integer
|
|
read GetArgCount;
|
|
/// List of arguments passed to the service by the service controler
|
|
// - Idx is in range 0..ArgCount - 1
|
|
property Args[Idx: Integer]: RawUtf8
|
|
read GetArgs;
|
|
/// Any data You wish to associate with the service object
|
|
property Data: cardinal
|
|
read FData write FData;
|
|
/// Whether service is installed in DataBase
|
|
// - uses a local TServiceController to check if the current Service Name exists
|
|
property Installed: boolean
|
|
read GetInstalled;
|
|
/// Current service status
|
|
// - To report new status to the system, assign another
|
|
// value to this record, or use ReportStatus method (preferred)
|
|
property Status: TServiceStatus
|
|
read fStatusRec write SetStatus;
|
|
/// Callback handler for Windows Service Controller
|
|
// - if handler is not set, then auto generated handler calls DoCtrlHandle
|
|
// (note that this auto-generated stubb is... not working yet - so you should
|
|
// either set your own procedure to this property, or use TServiceSingle)
|
|
// - a typical control handler may be defined as such:
|
|
// ! var MyGlobalService: TService;
|
|
// !
|
|
// ! procedure MyServiceControlHandler(Opcode: LongWord); stdcall;
|
|
// ! begin
|
|
// ! if MyGlobalService<>nil then
|
|
// ! MyGlobalService.DoCtrlHandle(Opcode);
|
|
// ! end;
|
|
// !
|
|
// ! ...
|
|
// ! MyGlobalService := TService.Create(...
|
|
// ! MyGlobalService.ControlHandler := MyServiceControlHandler;
|
|
property ControlHandler: TServiceControlHandler
|
|
read GetControlHandler write SetControlHandler;
|
|
/// Start event is executed before the main service thread (i.e. in the Execute method)
|
|
property OnStart: TServiceEvent
|
|
read fOnStart write fOnStart;
|
|
/// custom Execute event
|
|
// - launched in the main service thread (i.e. in the Execute method)
|
|
property OnExecute: TServiceEvent
|
|
read fOnExecute write fOnExecute;
|
|
/// custom event triggered when a Control Code is received from Windows
|
|
property OnControl: TServiceControlEvent
|
|
read fOnControl write fOnControl;
|
|
/// custom event triggered when the service is stopped
|
|
property OnStop: TServiceEvent
|
|
read fOnStop write fOnStop;
|
|
/// custom event triggered when the service is paused
|
|
property OnPause: TServiceEvent
|
|
read fOnPause write fOnPause;
|
|
/// custom event triggered when the service is resumed
|
|
property OnResume: TServiceEvent
|
|
read fOnResume write fOnResume;
|
|
/// custom event triggered when the service receive an Interrogate command
|
|
// - could call ReportStatus() e.g. to notify a problem
|
|
property OnInterrogate: TServiceEvent
|
|
read fOnInterrogate write fOnInterrogate;
|
|
/// custom event triggered when the service is shut down
|
|
property OnShutdown: TServiceEvent
|
|
read fOnShutdown write fOnShutdown;
|
|
published
|
|
/// Name of the service. Must be unique
|
|
property ServiceName: RawUtf8
|
|
read fServiceName;
|
|
/// Display name of the service
|
|
property DisplayName: RawUtf8
|
|
read fDisplayName write fDisplayName;
|
|
/// Type of service
|
|
property ServiceType: cardinal
|
|
read fServiceType write fServiceType;
|
|
/// Type of start of service
|
|
property StartType: cardinal
|
|
read fStartType write fStartType;
|
|
end;
|
|
|
|
/// inherit from this class if your application has a single Windows Service
|
|
// - note that only this single-service implementation is available by now
|
|
// - the regular way of executing services is to instantiate a TServiceSingle
|
|
// instance (which will fill the ServiceSingle variable) and its methods,
|
|
// then eventually call ServiceSingleRun
|
|
TServiceSingle = class(TService)
|
|
public
|
|
/// will set a global function as service controller
|
|
constructor Create(const aServiceName, aDisplayName: RawUtf8); override;
|
|
/// will release the global service controller
|
|
destructor Destroy; override;
|
|
end;
|
|
|
|
var
|
|
/// the main TServiceSingle instance running in the current executable
|
|
// - the regular way of executing services is to instantiate a TServiceSingle
|
|
// instance (which will fill this ServiceSingle variable) and its methods,
|
|
// then eventually call ServiceSingleRun
|
|
ServiceSingle: TServiceSingle = nil;
|
|
|
|
/// launch the registered Service execution
|
|
// - ServiceSingle provided by this application (most probably from
|
|
// TServiceSingle.Create) is sent to the operating system
|
|
// - returns TRUE on success
|
|
// - returns FALSE on error (to get extended information, call GetLastError)
|
|
function ServiceSingleRun: boolean;
|
|
|
|
/// convert the Control Code retrieved from Windows into a service state
|
|
// enumeration item
|
|
function CurrentStateToServiceState(CurrentState: cardinal): TServiceState;
|
|
|
|
/// return the ProcessID of a given service, by name
|
|
function GetServicePid(const aServiceName: RawUtf8;
|
|
aServiceState: PServiceState = nil): cardinal;
|
|
|
|
/// try to gently stop a given Windows console app from its ProcessID
|
|
// - will send a Ctrl-C event (acquiring the process console)
|
|
function CancelProcess(pid: cardinal; waitseconds: integer): boolean;
|
|
|
|
/// try to gently quit a Windows process from its ProcessID
|
|
// - will send a WM_QUIT message to all its threads
|
|
function QuitProcess(pid: cardinal; waitseconds: integer): boolean;
|
|
|
|
/// forcibly terminate a Windows process from its ProcessID
|
|
// - call TerminateProcess() and wait for its ending
|
|
function KillProcess(pid: cardinal; waitseconds: integer = 30): boolean;
|
|
|
|
/// install a Windows event handler for Ctrl+C pressed on the Console
|
|
function HandleCtrlC(const OnClose: TThreadMethod): boolean;
|
|
|
|
/// define a Windows Job to close associated processes together
|
|
// - warning: main process should include the CREATE_BREAKAWAY_FROM_JOB flag
|
|
// - you should later call CloseHandle() on the returned handle, if not 0
|
|
function CreateJobToClose(parentpid: cardinal): THandle;
|
|
|
|
/// associate a process to a Windows Job created by CreateJobToClose()
|
|
function AssignJobToProcess(job, process: THandle; const ctxt: ShortString): boolean;
|
|
|
|
{$else}
|
|
|
|
/// low-level function able to properly run or fork the current process
|
|
// then execute the start/stop methods of a TSynDaemon / TDDDDaemon instance
|
|
// - fork will create a local /run/[ProgramName]-[ProgramPathHash].pid file name
|
|
// - onLog can be assigned from TSynLog.DoLog for proper logging
|
|
procedure RunUntilSigTerminated(daemon: TObject; dofork: boolean;
|
|
const start, stop: TThreadMethod; const onlog: TSynLogProc = nil;
|
|
const servicename: string = '');
|
|
|
|
/// kill a process previously created by RunUntilSigTerminated(dofork=true)
|
|
// - will lookup a local /run/[ProgramName]-[ProgramPathHash].pid file name to
|
|
// retrieve the actual PID to be killed, then send a SIGTERM, and wait
|
|
// waitseconds for the .pid file to disapear
|
|
// - returns true on success, false on error (e.g. no valid .pid file or
|
|
// the file didn't disappear, which may mean that the daemon is broken)
|
|
function RunUntilSigTerminatedForKill(waitseconds: integer = 30): boolean;
|
|
|
|
var
|
|
/// optional folder where the .pid is created
|
|
// - should include a trailing '/' character
|
|
// - to be used if the current executable folder is read/only
|
|
RunUntilSigTerminatedPidFilePath: TFileName;
|
|
|
|
/// local .pid file name as created by RunUntilSigTerminated(dofork=true)
|
|
function RunUntilSigTerminatedPidFile(ensureWritable: boolean = false): TFileName;
|
|
|
|
/// check the local .pid file to return either ssRunning or ssStopped
|
|
function RunUntilSigTerminatedState: TServiceState;
|
|
|
|
var
|
|
/// once SynDaemonIntercept has been called, this global variable
|
|
// contains the SIGQUIT / SIGTERM / SIGINT received signal
|
|
SynDaemonTerminated: integer;
|
|
|
|
/// enable low-level interception of executable stop signals
|
|
// - any SIGQUIT / SIGTERM / SIGINT signal will set appropriately the global
|
|
// SynDaemonTerminated variable, with an optional logged entry to log
|
|
// - called e.g. by RunUntilSigTerminated() or ConsoleWaitForEnterKey()
|
|
// - you can call this method several times with no issue
|
|
// - onLog can be assigned from TSynLog.DoLog for proper logging
|
|
procedure SynDaemonIntercept(const onlog: TSynLogProc = nil);
|
|
|
|
/// disable SIGPIPE signal for the current process
|
|
// - is called e.g. by NewOpenSslNetTls since the OpenSsl TLS layer does not
|
|
// (yet) use MSG_NOSIGNAL when accessing the socket
|
|
procedure SigPipeIntercept;
|
|
|
|
{$endif OSWINDOWS}
|
|
|
|
/// change the current UID/GID to another user, by name
|
|
// - only implemented on POSIX by now
|
|
function DropPriviledges(const UserName: RawUtf8 = 'nobody'): boolean;
|
|
|
|
/// changes the root directory of the calling process
|
|
// - only implemented on POSIX by now
|
|
function ChangeRoot(const FolderName: RawUtf8): boolean;
|
|
|
|
type
|
|
/// command line patterns recognized by ParseCommandArgs()
|
|
TParseCommand = (
|
|
pcHasRedirection,
|
|
pcHasSubCommand,
|
|
pcHasParenthesis,
|
|
pcHasJobControl,
|
|
pcHasWildcard,
|
|
pcHasShellVariable,
|
|
pcUnbalancedSingleQuote,
|
|
pcUnbalancedDoubleQuote,
|
|
pcTooManyArguments,
|
|
pcInvalidCommand,
|
|
pcHasEndingBackSlash);
|
|
|
|
TParseCommands = set of TParseCommand;
|
|
PParseCommands = ^TParseCommands;
|
|
|
|
/// used to store references of arguments recognized by ParseCommandArgs()
|
|
TParseCommandsArgs = array[0..31] of PAnsiChar;
|
|
PParseCommandsArgs = ^TParseCommandsArgs;
|
|
|
|
const
|
|
/// identifies some bash-specific processing
|
|
PARSECOMMAND_BASH =
|
|
[pcHasRedirection .. pcHasShellVariable];
|
|
|
|
/// identifies obvious invalid content
|
|
PARSECOMMAND_ERROR =
|
|
[pcUnbalancedSingleQuote .. pcHasEndingBackSlash];
|
|
|
|
PARSCOMMAND_POSIX = {$ifdef OSWINDOWS} false {$else} true {$endif};
|
|
|
|
/// low-level parsing of a RunCommand() execution command
|
|
// - parse and fills argv^[0..argc^-1] with corresponding arguments, after
|
|
// un-escaping and un-quoting if applicable, using temp^ to store the content
|
|
// - if argv=nil, do only the parsing, not the argument extraction - could be
|
|
// used for fast validation of the command line syntax
|
|
// - you can force arguments OS flavor using the posix parameter - note that
|
|
// Windows parsing is not consistent by itself (e.g. double quoting or
|
|
// escaping depends on the actual executable called) so returned flags
|
|
// should be considered as indicative only with posix=false
|
|
function ParseCommandArgs(const cmd: RawUtf8; argv: PParseCommandsArgs = nil;
|
|
argc: PInteger = nil; temp: PRawUtf8 = nil;
|
|
posix: boolean = PARSCOMMAND_POSIX): TParseCommands;
|
|
|
|
/// low-level extration of the executable of a RunCommand() execution command
|
|
// - returns the first parameter returned by ParseCommandArgs()
|
|
function ExtractExecutableName(const cmd: RawUtf8;
|
|
posix: boolean = PARSCOMMAND_POSIX): RawUtf8;
|
|
|
|
type
|
|
/// callback used by RunRedirect() to notify of console output at runtime
|
|
// - newly console output text is given as raw bytes sent by the application,
|
|
// with no conversion: on POSIX, it is likely to be UTF-8 but on Windows it
|
|
// depends on the actual program so is likely to be CP_OEM but others could
|
|
// use the system code page or even UTF-16 binary with BOM (!) - so you
|
|
// may consider using AnsiToUtf8() with the proper code page
|
|
// - should return true to stop the execution, or false to continue
|
|
// - is called once when the process is started, with text='', ignoring its return
|
|
// - on idle state (each 200ms), is called with text='' to allow execution abort
|
|
// - the raw process ID (dword on Windows, cint on POSIX) is also supplied
|
|
TOnRedirect = function(const text: RawByteString; pid: cardinal): boolean of object;
|
|
|
|
/// define how RunCommand() and RunRedirect() run their sub-process
|
|
// - roEnvAddExisting is used when the env pairs should be added to the
|
|
// existing system environment variable
|
|
// - roWinJobCloseChildren will setup a Windows Job to close any child
|
|
// process(es) when the created process quits
|
|
// - roWinNoProcessDetach will avoid creating a Windows sub-process and group
|
|
TRunOptions = set of (
|
|
roEnvAddExisting,
|
|
roWinJobCloseChildren,
|
|
roWinNoProcessDetach);
|
|
|
|
/// like SysUtils.ExecuteProcess, but allowing not to wait for the process to finish
|
|
// - optional env value follows 'n1=v1'#0'n2=v2'#0'n3=v3'#0#0 Windows layout
|
|
function RunProcess(const path, arg1: TFileName; waitfor: boolean;
|
|
const arg2: TFileName = ''; const arg3: TFileName = '';
|
|
const arg4: TFileName = ''; const arg5: TFileName = '';
|
|
const env: TFileName = ''; options: TRunOptions = []): integer;
|
|
|
|
/// like fpSystem, but cross-platform
|
|
// - under POSIX, calls bash only if needed, after ParseCommandArgs() analysis
|
|
// - under Windows (especially Windows 10), creating a process can be dead slow
|
|
// https://randomascii.wordpress.com/2019/04/21/on2-in-createprocess
|
|
// - waitfordelayms/processhandle/redirected/onoutput exist on Windows only -
|
|
// and redirected is the raw byte output, which may be OEM, WinAnsi or UTF-16
|
|
// depending on the program itself
|
|
// - parsed is implemented on POSIX only
|
|
// - optional env should be encoded as 'n1=v1'#0'n2=v2'#0#0 pairs
|
|
function RunCommand(const cmd: TFileName; waitfor: boolean;
|
|
const env: TFileName = ''; options: TRunOptions = [];
|
|
{$ifdef OSWINDOWS}
|
|
waitfordelayms: cardinal = INFINITE; processhandle: PHandle = nil;
|
|
redirected: PRawByteString = nil; const onoutput: TOnRedirect = nil;
|
|
const wrkdir: TFileName = ''
|
|
{$else}
|
|
parsed: PParseCommands = nil
|
|
{$endif OSWINDOWS}): integer;
|
|
|
|
/// execute a command, returning its output console as UTF-8 text
|
|
// - calling CreateProcessW on Windows (i.e. our RunCommand), and FPC RTL
|
|
// popen/pclose on POSIX
|
|
// - return '' on cmd execution error, or the whole output console content
|
|
// with no conversion: on POSIX, it is likely to be UTF-8 but on Windows it
|
|
// depends on the actual program so is likely to be CP_OEM but others could
|
|
// use the system code page or even UTF-16 binary with BOM (!) - so you
|
|
// may consider using AnsiToUtf8() with the proper code page
|
|
// - will optionally call onoutput() to notify the new output state
|
|
// - aborts if onoutput() callback returns true, or waitfordelayms expires
|
|
// - optional env is Windows only, (FPC popen does not support it), and should
|
|
// be encoded as name=value#0 pairs
|
|
// - you can specify a wrkdir if the path specified by cmd is not good enough
|
|
function RunRedirect(const cmd: TFileName; exitcode: PInteger = nil;
|
|
const onoutput: TOnRedirect = nil; waitfordelayms: cardinal = INFINITE;
|
|
setresult: boolean = true; const env: TFileName = '';
|
|
const wrkdir: TFileName = ''; options: TRunOptions = []): RawByteString;
|
|
|
|
var
|
|
/// how many seconds we should wait for gracefull termination of a process
|
|
// in RunRedirect() - or RunCommand() on Windows
|
|
// - set 0 to disable gracefull exit, and force hard SIGKILL/TerminateProcess
|
|
RunAbortTimeoutSecs: integer = 5;
|
|
|
|
{$ifdef OSWINDOWS}
|
|
type
|
|
/// how RunRedirect() or RunCommand() should try to gracefully terminate
|
|
// - ramCtrlC calls CancelProcess(), i.e. send CTRL_C_EVENT
|
|
// - ramQuit calls QuitProcess(), i.e. send WM_QUIT on all the process threads
|
|
// - note that TerminateProcess is always called after RunAbortTimeoutSecs
|
|
// timeout, or if this set of methods is void
|
|
TRunAbortMethods = set of (ramCtrlC, ramQuit);
|
|
var
|
|
/// RunRedirect/RunCommand methods to gracefully terminate before TerminateProcess
|
|
RunAbortMethods: TRunAbortMethods = [ramCtrlC, ramQuit];
|
|
{$else}
|
|
type
|
|
/// how RunRedirect() should try to gracefully terminate
|
|
// - ramSigTerm send a fpkill(pid, SIGTERM) to the process
|
|
// - note that SIGKILL is always sent after RunAbortTimeoutSecs timeout,
|
|
// or if ramSigTerm was not supplied
|
|
TRunAbortMethods = set of (ramSigTerm);
|
|
var
|
|
/// RunRedirect() methods to gracefully terminate before SIGKILL
|
|
RunAbortMethods: TRunAbortMethods = [ramSigTerm];
|
|
{$endif OSWINDOWS}
|
|
|
|
|
|
implementation
|
|
|
|
// those include files hold all OS-specific functions
|
|
// note: the *.inc files start with their own "uses" clause, so both $include
|
|
// should remain here, just after the "implementation" clause
|
|
|
|
{$ifdef OSPOSIX}
|
|
{$include mormot.core.os.posix.inc}
|
|
{$endif OSPOSIX}
|
|
|
|
{$ifdef OSWINDOWS}
|
|
{$include mormot.core.os.windows.inc}
|
|
{$endif OSWINDOWS}
|
|
|
|
|
|
{ ****************** Some Cross-System Type and Constant Definitions }
|
|
|
|
const
|
|
// StatusCodeToReason() StatusCodeToText() table to avoid memory allocations
|
|
// - roughly sorted by actual usage order for WordScanIndex()
|
|
HTTP_REASON: array[0..43] of RawUtf8 = (
|
|
'OK', // HTTP_SUCCESS - should be first
|
|
'No Content', // HTTP_NOCONTENT
|
|
'Temporary Redirect', // HTTP_TEMPORARYREDIRECT
|
|
'Permanent Redirect', // HTTP_PERMANENTREDIRECT
|
|
'Moved Permanently', // HTTP_MOVEDPERMANENTLY
|
|
'Bad Request', // HTTP_BADREQUEST
|
|
'Unauthorized', // HTTP_UNAUTHORIZED
|
|
'Forbidden', // HTTP_FORBIDDEN
|
|
'Not Found', // HTTP_NOTFOUND
|
|
'Method Not Allowed', // HTTP_NOTALLOWED
|
|
'Not Modified', // HTTP_NOTMODIFIED
|
|
'Not Acceptable', // HTTP_NOTACCEPTABLE
|
|
'Partial Content', // HTTP_PARTIALCONTENT
|
|
'Payload Too Large', // HTTP_PAYLOADTOOLARGE
|
|
'Created', // HTTP_CREATED
|
|
'See Other', // HTTP_SEEOTHER
|
|
'Continue', // HTTP_CONTINUE
|
|
'Switching Protocols', // HTTP_SWITCHINGPROTOCOLS
|
|
'Accepted', // HTTP_ACCEPTED
|
|
'Non-Authoritative Information', // HTTP_NONAUTHORIZEDINFO
|
|
'Reset Content', // HTTP_RESETCONTENT
|
|
'Multi-Status', // 207
|
|
'Multiple Choices', // HTTP_MULTIPLECHOICES
|
|
'Found', // HTTP_FOUND
|
|
'Use Proxy', // HTTP_USEPROXY
|
|
'Proxy Authentication Required', // HTTP_PROXYAUTHREQUIRED
|
|
'Request Timeout', // HTTP_TIMEOUT
|
|
'Conflict', // HTTP_CONFLICT
|
|
'Gone', // 410
|
|
'Length Required', // 411
|
|
'Precondition Failed', // 412
|
|
'URI Too Long', // 414
|
|
'Unsupported Media Type', // 415
|
|
'Requested Range Not Satisfiable', // HTTP_RANGENOTSATISFIABLE
|
|
'I''m a teapot', // HTTP_TEAPOT
|
|
'Upgrade Required', // 426
|
|
'Internal Server Error', // HTTP_SERVERERROR
|
|
'Not Implemented', // HTTP_NOTIMPLEMENTED
|
|
'Bad Gateway', // HTTP_BADGATEWAY
|
|
'Service Unavailable', // HTTP_UNAVAILABLE
|
|
'Gateway Timeout', // HTTP_GATEWAYTIMEOUT
|
|
'HTTP Version Not Supported', // HTTP_HTTPVERSIONNONSUPPORTED
|
|
'Network Authentication Required', // 511
|
|
'Invalid Request'); // 513 - should be last
|
|
|
|
HTTP_CODE: array[0..43] of word = (
|
|
HTTP_SUCCESS,
|
|
HTTP_NOCONTENT,
|
|
HTTP_TEMPORARYREDIRECT,
|
|
HTTP_PERMANENTREDIRECT,
|
|
HTTP_MOVEDPERMANENTLY,
|
|
HTTP_BADREQUEST,
|
|
HTTP_UNAUTHORIZED,
|
|
HTTP_FORBIDDEN,
|
|
HTTP_NOTFOUND,
|
|
HTTP_NOTALLOWED,
|
|
HTTP_NOTMODIFIED,
|
|
HTTP_NOTACCEPTABLE,
|
|
HTTP_PARTIALCONTENT,
|
|
HTTP_PAYLOADTOOLARGE,
|
|
HTTP_CREATED,
|
|
HTTP_SEEOTHER,
|
|
HTTP_CONTINUE,
|
|
HTTP_SWITCHINGPROTOCOLS,
|
|
HTTP_ACCEPTED,
|
|
HTTP_NONAUTHORIZEDINFO,
|
|
HTTP_RESETCONTENT,
|
|
207,
|
|
HTTP_MULTIPLECHOICES,
|
|
HTTP_FOUND,
|
|
HTTP_USEPROXY,
|
|
HTTP_PROXYAUTHREQUIRED,
|
|
HTTP_TIMEOUT,
|
|
HTTP_CONFLICT,
|
|
410,
|
|
411,
|
|
412,
|
|
414,
|
|
415,
|
|
HTTP_RANGENOTSATISFIABLE,
|
|
HTTP_TEAPOT,
|
|
426,
|
|
HTTP_SERVERERROR,
|
|
HTTP_NOTIMPLEMENTED,
|
|
HTTP_BADGATEWAY,
|
|
HTTP_UNAVAILABLE,
|
|
HTTP_GATEWAYTIMEOUT,
|
|
HTTP_HTTPVERSIONNONSUPPORTED,
|
|
511,
|
|
513);
|
|
|
|
function StatusCodeToText(Code: cardinal): PRawUtf8;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
if Code <> 200 then // optimistic approach :)
|
|
if (Code < 513) and
|
|
(Code >= 100) then
|
|
begin
|
|
i := WordScanIndex(@HTTP_CODE, length(HTTP_CODE), Code); // may use SSE2
|
|
if i < 0 then
|
|
i := high(HTTP_CODE); // returns cached 513 'Invalid Request'
|
|
end
|
|
else
|
|
i := high(HTTP_CODE)
|
|
else
|
|
i := 0;
|
|
result := @HTTP_REASON[i];
|
|
end;
|
|
|
|
procedure StatusCodeToReason(Code: cardinal; var Reason: RawUtf8);
|
|
begin
|
|
Reason := StatusCodeToText(Code)^;
|
|
end;
|
|
|
|
function StatusCodeToShort(Code: cardinal): TShort47;
|
|
begin
|
|
if Code > 599 then
|
|
Code := 999; // ensure stay in TShort47
|
|
result[0] := #0;
|
|
AppendShortCardinal(Code, result);
|
|
AppendShortChar(' ', result);
|
|
AppendShortAnsi7String(StatusCodeToText(Code)^, result);
|
|
end;
|
|
|
|
function StatusCodeIsSuccess(Code: integer): boolean;
|
|
begin
|
|
result := (Code >= HTTP_SUCCESS) and
|
|
(Code < HTTP_BADREQUEST); // 200..399
|
|
end;
|
|
|
|
function IsInvalidHttpHeader(head: PUtf8Char; headlen: PtrInt): boolean;
|
|
var
|
|
i: PtrInt;
|
|
c: cardinal;
|
|
begin
|
|
result := true;
|
|
for i := 0 to headlen - 3 do
|
|
begin
|
|
c := PCardinal(head + i)^;
|
|
if (c = $0a0d0a0d) or
|
|
(Word(c) = $0d0d) or
|
|
(Word(c) = $0a0a) then
|
|
exit;
|
|
end;
|
|
result := false;
|
|
end;
|
|
|
|
function _oskb(Size: QWord): shortstring;
|
|
const
|
|
_U: array[0..3] of AnsiChar = 'TGMK';
|
|
var
|
|
u: PtrInt;
|
|
b: QWord;
|
|
begin
|
|
u := 0;
|
|
b := Qword(1) shl 40;
|
|
repeat
|
|
if Size > b shr 1 then
|
|
break;
|
|
b := b shr 10;
|
|
inc(u);
|
|
until u = high(_u);
|
|
str(Size / b : 1 : 1, result); // let the FPU + RTL do the conversion for us
|
|
if (result[0] <= #2) or
|
|
(result[ord(result[0]) - 1] <> '.') or
|
|
(result[ord(result[0])] <> '0') then
|
|
inc(result[0], 2);
|
|
result[ord(result[0]) - 1] := _U[u];
|
|
result[ord(result[0])] := 'B';
|
|
end;
|
|
|
|
function SidLength(sid: PSid): PtrInt;
|
|
begin
|
|
if sid = nil then
|
|
result := 0
|
|
else
|
|
result := integer(sid^.SubAuthorityCount) shl 2 + 8;
|
|
end;
|
|
|
|
function SidCompare(a, b: PSid): integer;
|
|
var
|
|
l: PtrInt;
|
|
begin
|
|
l := SidLength(a);
|
|
result := l - SidLength(b);
|
|
if result = 0 then
|
|
result := MemCmp(pointer(a), pointer(b), l);
|
|
end;
|
|
|
|
procedure ToRawSid(sid: PSid; out result: RawSid);
|
|
begin
|
|
if sid <> nil then
|
|
FastSetRawByteString(RawByteString(result), sid, SidLength(sid));
|
|
end;
|
|
|
|
procedure SidToTextShort(sid: PSid; var result: shortstring);
|
|
var
|
|
a: PSidAuth;
|
|
i: PtrInt;
|
|
begin // faster than ConvertSidToStringSidA(), and cross-platform
|
|
if (sid = nil ) or
|
|
(sid^.Revision <> 1) then
|
|
begin
|
|
result[0] := #0; // invalid SID
|
|
exit;
|
|
end;
|
|
a := @sid^.IdentifierAuthority;
|
|
if (a^[0] <> 0) or
|
|
(a^[1] <> 0) then
|
|
begin
|
|
result := 'S-1-0x';
|
|
for i := 0 to 5 do
|
|
AppendShortByteHex(a^[i], result)
|
|
end
|
|
else
|
|
begin
|
|
result := 'S-1-';
|
|
AppendShortCardinal(bswap32(PCardinal(@a^[2])^), result);
|
|
end;
|
|
for i := 0 to integer(sid^.SubAuthorityCount) - 1 do
|
|
begin
|
|
AppendShortChar('-', result);
|
|
AppendShortCardinal(sid^.SubAuthority[i], result);
|
|
end;
|
|
end;
|
|
|
|
function SidToText(sid: PSid): RawUtf8;
|
|
var
|
|
tmp: shortstring;
|
|
begin
|
|
SidToTextShort(sid, tmp);
|
|
FastSetString(result, @tmp[1], ord(tmp[0]));
|
|
end;
|
|
|
|
function SidsToText(sids: PSids): TRawUtf8DynArray;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
result := nil;
|
|
SetLength(result, length(sids));
|
|
for i := 0 to length(sids) - 1 do
|
|
result[i] := SidToText(sids[i]);
|
|
end;
|
|
|
|
function IsValidRawSid(const sid: RawSid): boolean;
|
|
var
|
|
l: PtrInt;
|
|
begin
|
|
l := length(sid);
|
|
result := (l >= SizeOf(TSidAuth) + 2) and
|
|
(SidLength(pointer(sid)) = l)
|
|
end;
|
|
|
|
function HasSid(const sids: PSids; sid: PSid): boolean;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
result := true;
|
|
if sid <> nil then
|
|
for i := 0 to length(sids) - 1 do
|
|
if SidCompare(sid, sids[i]) = 0 then
|
|
exit;
|
|
result := false;
|
|
end;
|
|
|
|
function HasAnySid(const sids: PSids; const sid: RawSidDynArray): boolean;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
result := true;
|
|
for i := 0 to length(sid) - 1 do
|
|
if HasSid(sids, pointer(sid[i])) then
|
|
exit;
|
|
result := false;
|
|
end;
|
|
|
|
procedure AddRawSid(var sids: RawSidDynArray; sid: PSid);
|
|
var
|
|
n: PtrInt;
|
|
begin
|
|
if sid = nil then
|
|
exit;
|
|
n := length(sids);
|
|
SetLength(sids, n + 1);
|
|
ToRawSid(sid, sids[n]);
|
|
end;
|
|
|
|
function RawSidToText(const sid: RawSid): RawUtf8;
|
|
begin
|
|
if IsValidRawSid(sid) then
|
|
result := SidToText(pointer(sid))
|
|
else
|
|
result := '';
|
|
end;
|
|
|
|
// GetNextCardinal() on POSIX does not ignore trailing '-'
|
|
function GetNextUInt32(var P: PUtf8Char): cardinal;
|
|
var
|
|
c: cardinal;
|
|
begin
|
|
result := 0;
|
|
if P = nil then
|
|
exit;
|
|
repeat
|
|
c := ord(P^) - 48;
|
|
if c > 9 then
|
|
break
|
|
else
|
|
result := result * 10 + c;
|
|
inc(P);
|
|
until false;
|
|
while P^ in ['.', '-', ' '] do
|
|
inc(P);
|
|
end;
|
|
|
|
function TextToSid(P: PUtf8Char; out sid: TSid): boolean;
|
|
begin
|
|
result := false;
|
|
if (P = nil) or
|
|
(PCardinal(P)^ <>
|
|
ord('S') + ord('-') shl 8 + ord('1') shl 16 + ord('-') shl 24) then
|
|
exit;
|
|
inc(P, 4);
|
|
if not (P^ in ['1'..'9']) then
|
|
exit;
|
|
PInt64(@sid)^ := 1;
|
|
PCardinal(@sid.IdentifierAuthority[2])^ := bswap32(GetNextUInt32(P));
|
|
while P^ in ['0'..'9'] do
|
|
begin
|
|
sid.SubAuthority[sid.SubAuthorityCount] := GetNextUInt32(P);
|
|
inc(sid.SubAuthorityCount);
|
|
if sid.SubAuthorityCount = 0 then
|
|
exit; // avoid any overflow
|
|
end;
|
|
result := P^ = #0
|
|
end;
|
|
|
|
function TextToRawSid(const text: RawUtf8): RawSid;
|
|
begin
|
|
TextToRawSid(text, result);
|
|
end;
|
|
|
|
function TextToRawSid(const text: RawUtf8; out sid: RawSid): boolean;
|
|
var
|
|
tmp: TSid; // maximum size possible on stack (1032 bytes)
|
|
begin
|
|
result := TextToSid(pointer(text), tmp);
|
|
if result then
|
|
ToRawSid(@tmp, sid)
|
|
end;
|
|
|
|
var
|
|
KNOWN_SID_SAFE: TLightLock; // lighter than GlobalLock/GlobalUnLock
|
|
KNOWN_SID: array[TWellKnownSid] of RawSid;
|
|
KNOWN_SID_TEXT: array[TWellKnownSid] of string[15];
|
|
const
|
|
INTEGRITY_SID: array[0..7] of word = ( // S-1-16-x known values
|
|
0, 4096, 8192, 8448, 12288, 16384, 20480, 28672);
|
|
|
|
procedure ComputeKnownSid(wks: TWellKnownSid);
|
|
var
|
|
sid: TSid;
|
|
begin
|
|
PInt64(@sid)^ := $0101; // sid.Revision=1, sid.SubAuthorityCount=1
|
|
if wks <= wksLocal then
|
|
begin // S-1-1-0
|
|
sid.IdentifierAuthority[5] := ord(wks);
|
|
sid.SubAuthority[0] := 0;
|
|
end
|
|
else if wks = wksConsoleLogon then
|
|
begin // S-1-2-1
|
|
sid.IdentifierAuthority[5] := 2;
|
|
sid.SubAuthority[0] := 1;
|
|
end
|
|
else if wks <= wksCreatorGroupServer then
|
|
begin // S-1-3-0
|
|
sid.IdentifierAuthority[5] := 3;
|
|
sid.SubAuthority[0] := ord(wks) - ord(wksCreatorOwner);
|
|
end
|
|
else if wks <= wksIntegritySecureProcess then
|
|
begin
|
|
sid.IdentifierAuthority[5] := 16; // S-1-16-x
|
|
sid.SubAuthority[0] := INTEGRITY_SID[ord(wks) - ord(wksIntegrityUntrusted)];
|
|
end
|
|
else if wks <= wksAuthenticationKeyPropertyAttestation then
|
|
begin // S-1-18-1
|
|
sid.IdentifierAuthority[5] := 18;
|
|
sid.SubAuthority[0] := ord(wks) - (ord(wksAuthenticationAuthorityAsserted) - 1)
|
|
end
|
|
else
|
|
begin // S-1-5-x
|
|
sid.IdentifierAuthority[5] := 5;
|
|
if wks = wksNtAuthority then
|
|
sid.SubAuthorityCount := 0
|
|
else if wks <= wksInteractive then
|
|
sid.SubAuthority[0] := ord(wks) - ord(wksNtAuthority)
|
|
else if wks <= wksThisOrganisation then
|
|
sid.SubAuthority[0] := ord(wks) - (ord(wksNtAuthority) - 1)
|
|
else if wks <= wksNetworkService then
|
|
sid.SubAuthority[0] := ord(wks) - (ord(wksNtAuthority) - 2)
|
|
else if wks <= wksLocalAccountAndAdministrator then // S-1-5-113
|
|
sid.SubAuthority[0] := ord(wks) - (ord(wksLocalAccount) - 113)
|
|
else
|
|
begin
|
|
sid.SubAuthority[0] := 32;
|
|
if wks <> wksBuiltinDomain then
|
|
begin
|
|
sid.SubAuthorityCount := 2;
|
|
if wks <= wksBuiltinDcomUsers then
|
|
sid.SubAuthority[1] := ord(wks) - (ord(wksBuiltinAdministrators) - 544)
|
|
else if wks <= wksBuiltinDeviceOwners then // S-1-5-32-583
|
|
sid.SubAuthority[1] := ord(wks) - (ord(wksBuiltinIUsers) - 568)
|
|
else if wks <= wksCapabilityContacts then
|
|
begin // S-1-15-3-1
|
|
sid.IdentifierAuthority[5] := 15;
|
|
sid.SubAuthority[0] := 3;
|
|
sid.SubAuthority[1] := ord(wks) - (ord(wksCapabilityInternetClient) - 1)
|
|
end
|
|
else if wks <= wksBuiltinAnyRestrictedPackage then
|
|
begin // S-1-15-2-1
|
|
sid.IdentifierAuthority[5] := 15;
|
|
sid.SubAuthority[0] := 2;
|
|
sid.SubAuthority[1] := ord(wks) - (ord(wksBuiltinAnyPackage) - 1)
|
|
end
|
|
else if wks <= wksDigestAuthentication then
|
|
begin
|
|
sid.SubAuthority[0] := 64;
|
|
case wks of
|
|
wksNtlmAuthentication:
|
|
sid.SubAuthority[1] := 10; // S-1-5-64-10
|
|
wksSChannelAuthentication:
|
|
sid.SubAuthority[1] := 14;
|
|
wksDigestAuthentication:
|
|
sid.SubAuthority[1] := 21;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
KNOWN_SID_SAFE.Lock;
|
|
if KNOWN_SID[wks] = '' then
|
|
begin
|
|
SidToTextShort(@sid, KNOWN_SID_TEXT[wks]);
|
|
ToRawSid(@sid, KNOWN_SID[wks]); // to be set last
|
|
end;
|
|
KNOWN_SID_SAFE.UnLock;
|
|
end;
|
|
|
|
function KnownRawSid(wks: TWellKnownSid): RawSid;
|
|
begin
|
|
if (wks <> wksNull) and
|
|
(KNOWN_SID[wks] = '') then
|
|
ComputeKnownSid(wks);
|
|
result := KNOWN_SID[wks];
|
|
end;
|
|
|
|
function KnownSidToText(wks: TWellKnownSid): PShortString;
|
|
begin
|
|
if (wks <> wksNull) and
|
|
(KNOWN_SID[wks] = '') then
|
|
ComputeKnownSid(wks);
|
|
result := @KNOWN_SID_TEXT[wks];
|
|
end;
|
|
|
|
// https://learn.microsoft.com/en-us/windows/win32/secauthz/well-known-sids
|
|
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/81d92bba-d22b-4a8c-908a-554ab29148ab
|
|
|
|
function SidToKnown(sid: PSid): TWellKnownSid;
|
|
var
|
|
c: integer;
|
|
begin
|
|
result := wksNull; // not recognized
|
|
if (sid = nil) or
|
|
(sid.Revision <> 1) or
|
|
(PCardinal(@sid.IdentifierAuthority)^ <> 0) or
|
|
(sid.IdentifierAuthority[4] <> 0) then
|
|
exit;
|
|
case sid.SubAuthorityCount of // very fast O(1) SID binary recognition
|
|
0:
|
|
if sid.IdentifierAuthority[5] = 5 then
|
|
result := wksNtAuthority; // S-1-5
|
|
1:
|
|
begin
|
|
c := sid.SubAuthority[0];
|
|
case sid.IdentifierAuthority[5] of
|
|
1:
|
|
if c = 0 then
|
|
result := wksWorld; // S-1-1-0
|
|
2:
|
|
if c in [0 .. 1] then // S-1-2-x
|
|
result := TWellKnownSid(ord(wksLocal) + c);
|
|
3:
|
|
if c in [0 .. 3] then // S-1-3-x
|
|
result := TWellKnownSid(ord(wksCreatorOwner) + c);
|
|
5:
|
|
case c of // S-1-5-x
|
|
1 .. 4:
|
|
result := TWellKnownSid((ord(wksDialup) - 1) + c);
|
|
6 .. 15:
|
|
result := TWellKnownSid((ord(wksService) - 6) + c);
|
|
17 .. 20:
|
|
result := TWellKnownSid((ord(wksIisUser) - 17) + c);
|
|
32:
|
|
result := wksBuiltinDomain;
|
|
113 .. 114:
|
|
result := TWellKnownSid(integer(ord(wksLocalAccount) - 113) + c);
|
|
end;
|
|
16:
|
|
begin // S-1-16-x
|
|
c := WordScanIndex(@INTEGRITY_SID, length(INTEGRITY_SID), c);
|
|
if c >= 0 then
|
|
result := TWellKnownSid(ord(wksIntegrityUntrusted) + c);
|
|
end;
|
|
18:
|
|
if c in [1 .. 6] then // S-1-18-x
|
|
result :=
|
|
TWellKnownSid((ord(wksAuthenticationAuthorityAsserted) - 1) + c);
|
|
end;
|
|
end;
|
|
2:
|
|
begin
|
|
c := sid.SubAuthority[1];
|
|
case sid.IdentifierAuthority[5] of
|
|
5:
|
|
case sid.SubAuthority[0] of
|
|
32: // S-1-5-32-544
|
|
case c of
|
|
544 .. 562:
|
|
result := TWellKnownSid(ord(wksBuiltinAdministrators) + c - 544);
|
|
568 .. 583:
|
|
result := TWellKnownSid(ord(wksBuiltinIUsers) + c - 568);
|
|
end;
|
|
64: // S-1-5-64-10
|
|
case c of
|
|
10:
|
|
result := wksNtlmAuthentication;
|
|
14:
|
|
result := wksSChannelAuthentication;
|
|
21:
|
|
result := wksDigestAuthentication;
|
|
end;
|
|
end;
|
|
15:
|
|
case sid.SubAuthority[0] of
|
|
2:
|
|
if c in [1 .. 2] then // S-1-15-2-x
|
|
result := TWellKnownSid(ord(pred(wksBuiltinAnyPackage)) + c);
|
|
3:
|
|
if c in [1 .. 12] then // S-1-15-3-x
|
|
result := TWellKnownSid(ord(pred(wksCapabilityInternetClient)) + c);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function SidToKnown(const text: RawUtf8): TWellKnownSid;
|
|
var
|
|
sid: TSid;
|
|
begin
|
|
if TextToSid(pointer(text), sid) then
|
|
result := SidToKnown(@sid)
|
|
else
|
|
result := wksNull;
|
|
end;
|
|
|
|
function SidToKnownGroups(const sids: PSids): TWellKnownSids;
|
|
var
|
|
k: TWellKnownSid;
|
|
i: PtrInt;
|
|
begin
|
|
result := [];
|
|
for i := 0 to length(sids) - 1 do
|
|
begin
|
|
k := SidToKnown(sids[i]);
|
|
if k <> wksNull then
|
|
include(result, k);
|
|
end;
|
|
end;
|
|
|
|
|
|
{ ****************** Gather Operating System Information }
|
|
|
|
function ToText(const osv: TOperatingSystemVersion): RawUtf8;
|
|
begin
|
|
result := OS_NAME[osv.os];
|
|
case osv.os of
|
|
osWindows:
|
|
result := 'Windows ' + WINDOWS_NAME[osv.win];
|
|
osOSX:
|
|
if osv.utsrelease[2] in [low(MACOS_NAME) .. high(MACOS_NAME)] then
|
|
result := 'macOS ' + MACOS_NAME[osv.utsrelease[2]];
|
|
end;
|
|
end;
|
|
|
|
function ToTextShort(const osv: TOperatingSystemVersion): RawUtf8;
|
|
begin
|
|
result := OS_NAME[osv.os];
|
|
case osv.os of
|
|
osWindows:
|
|
result := WINDOWS_NAME[osv.win];
|
|
osOSX:
|
|
if osv.utsrelease[2] in [low(MACOS_NAME) .. high(MACOS_NAME)] then
|
|
result := MACOS_NAME[osv.utsrelease[2]];
|
|
end;
|
|
end;
|
|
|
|
const
|
|
LINUX_TEXT: array[boolean] of string[7] = (
|
|
'', 'Linux ');
|
|
|
|
function ToTextOS(osint32: integer): RawUtf8;
|
|
var
|
|
osv: TOperatingSystemVersion absolute osint32;
|
|
begin
|
|
if osint32 = 0 then
|
|
begin
|
|
result := '';
|
|
exit;
|
|
end;
|
|
result := ToText(osv);
|
|
if (osv.os = osWindows) and
|
|
(osv.winbuild <> 0) then
|
|
// include the Windows build number, e.g. 'Windows 11 64bit 22000'
|
|
result := _fmt('%s %d', [result, osv.winbuild]);
|
|
if (osv.os >= osLinux) and
|
|
(osv.utsrelease[2] <> 0) then
|
|
// include kernel number to the distribution name, e.g. 'Ubuntu Linux 5.4.0'
|
|
result := _fmt('%s %s%d.%d.%d', [result, LINUX_TEXT[osv.os in OS_LINUX],
|
|
osv.utsrelease[2], osv.utsrelease[1], osv.utsrelease[0]]);
|
|
end;
|
|
|
|
function MatchOS(os: TOperatingSystem): boolean;
|
|
var
|
|
current: TOperatingSystem;
|
|
begin
|
|
current := OS_KIND;
|
|
if (os = osUnknown) or
|
|
(current = osUnknown) or
|
|
(os = current) then
|
|
result := true // exact match
|
|
else
|
|
case os of // search by family
|
|
osPosix:
|
|
result := current <> osWindows;
|
|
osLinux:
|
|
result := current in OS_LINUX;
|
|
else
|
|
result := false;
|
|
end;
|
|
end;
|
|
|
|
const
|
|
// https://github.com/karelzak/util-linux/blob/master/sys-utils/lscpu-arm.c
|
|
ARMCPU_ID: array[TArmCpuType] of word = (
|
|
0, // actUnknown
|
|
$0810, // actARM810
|
|
$0920, // actARM920
|
|
$0922, // actARM922
|
|
$0926, // actARM926
|
|
$0940, // actARM940
|
|
$0946, // actARM946
|
|
$0966, // actARM966
|
|
$0a20, // actARM1020
|
|
$0a22, // actARM1022
|
|
$0a26, // actARM1026
|
|
$0b02, // actARM11MPCore
|
|
$0b36, // actARM1136
|
|
$0b56, // actARM1156
|
|
$0b76, // actARM1176
|
|
$0c05, // actCortexA5
|
|
$0c07, // actCortexA7
|
|
$0c08, // actCortexA8
|
|
$0c09, // actCortexA9
|
|
$0c0d, // actCortexA12
|
|
$0c0f, // actCortexA15
|
|
$0c0e, // actCortexA17
|
|
$0c14, // actCortexR4
|
|
$0c15, // actCortexR5
|
|
$0c17, // actCortexR7
|
|
$0c18, // actCortexR8
|
|
$0c20, // actCortexM0
|
|
$0c21, // actCortexM1
|
|
$0c23, // actCortexM3
|
|
$0c24, // actCortexM4
|
|
$0c27, // actCortexM7
|
|
$0c60, // actCortexM0P
|
|
$0d01, // actCortexA32
|
|
$0d03, // actCortexA53
|
|
$0d04, // actCortexA35
|
|
$0d05, // actCortexA55
|
|
$0d06, // actCortexA65
|
|
$0d07, // actCortexA57
|
|
$0d08, // actCortexA72
|
|
$0d09, // actCortexA73
|
|
$0d0a, // actCortexA75
|
|
$0d0b, // actCortexA76
|
|
$0d0c, // actNeoverseN1
|
|
$0d0d, // actCortexA77
|
|
$0d0e, // actCortexA76AE
|
|
$0d13, // actCortexR52
|
|
$0d20, // actCortexM23
|
|
$0d21, // actCortexM33
|
|
$0d40, // actNeoverseV1
|
|
$0d41, // actCortexA78
|
|
$0d42, // actCortexA78AE
|
|
$0d44, // actCortexX1
|
|
$0d46, // actCortex510
|
|
$0d47, // actCortex710
|
|
$0d48, // actCortexX2
|
|
$0d49, // actNeoverseN2
|
|
$0d4a, // actNeoverseE1
|
|
$0d4b, // actCortexA78C
|
|
$0d4c, // actCortexX1C
|
|
$0d4d, // actCortexA715
|
|
$0d4e, // actCortexX3
|
|
$0d4f, // actNeoverseV2
|
|
$0d80, // actCortexA520
|
|
$0d81, // actCortexA720
|
|
$0d82, // actCortexX4
|
|
$0d84, // actNeoverseV3
|
|
$0d8e); // actNeoverseN3
|
|
|
|
ARMCPU_IMPL: array[TArmCpuImplementer] of byte = (
|
|
0, // aciUnknown
|
|
$41, // aciARM
|
|
$42, // aciBroadcom
|
|
$43, // aciCavium
|
|
$44, // aciDEC
|
|
$46, // aciFUJITSU
|
|
$48, // aciHiSilicon
|
|
$49, // aciInfineon
|
|
$4d, // aciMotorola
|
|
$4e, // aciNVIDIA
|
|
$50, // aciAPM
|
|
$51, // aciQualcomm
|
|
$53, // aciSamsung
|
|
$56, // aciMarvell
|
|
$61, // aciApple
|
|
$66, // aciFaraday
|
|
$69, // aciIntel
|
|
$6d, // aciMicrosoft
|
|
$70, // aciPhytium
|
|
$c0); // aciAmpere
|
|
|
|
ARMCPU_ID_TXT: array[TArmCpuType] of string[15] = (
|
|
'',
|
|
'ARM810', 'ARM920', 'ARM922', 'ARM926', 'ARM940', 'ARM946', 'ARM966',
|
|
'ARM1020', 'ARM1022', 'ARM1026', 'ARM11 MPCore', 'ARM1136', 'ARM1156',
|
|
'ARM1176', 'Cortex-A5', 'Cortex-A7', 'Cortex-A8', 'Cortex-A9',
|
|
'Cortex-A17',{Originally A12} 'Cortex-A15', 'Cortex-A17', 'Cortex-R4',
|
|
'Cortex-R5', 'Cortex-R7', 'Cortex-R8', 'Cortex-M0', 'Cortex-M1',
|
|
'Cortex-M3', 'Cortex-M4', 'Cortex-M7', 'Cortex-M0+', 'Cortex-A32',
|
|
'Cortex-A53', 'Cortex-A35', 'Cortex-A55', 'Cortex-A65', 'Cortex-A57',
|
|
'Cortex-A72', 'Cortex-A73', 'Cortex-A75', 'Cortex-A76', 'Neoverse-N1',
|
|
'Cortex-A77', 'Cortex-A76AE', 'Cortex-R52', 'Cortex-M23', 'Cortex-M33',
|
|
'Neoverse-V1', 'Cortex-A78', 'Cortex-A78AE', 'Cortex-X1', 'Cortex-510',
|
|
'Cortex-710', 'Cortex-X2', 'Neoverse-N2', 'Neoverse-E1', 'Cortex-A78C',
|
|
'Cortex-X1C', 'Cortex-A715', 'Cortex-X3', 'Neoverse-V2', 'Cortex-A520',
|
|
'Cortex-A720', 'Cortex-X4', 'Neoverse-V3', 'Neoverse-N3');
|
|
ARMCPU_IMPL_TXT: array[TArmCpuImplementer] of string[18] = (
|
|
'',
|
|
'ARM', 'Broadcom', 'Cavium', 'DEC', 'FUJITSU', 'HiSilicon', 'Infineon',
|
|
'Motorola/Freescale', 'NVIDIA', 'APM', 'Qualcomm', 'Samsung', 'Marvell',
|
|
'Apple', 'Faraday', 'Intel', 'Microsoft', 'Phytium', 'Ampere');
|
|
|
|
function ArmCpuType(id: word): TArmCpuType;
|
|
begin
|
|
for result := low(TArmCpuType) to high(TArmCpuType) do
|
|
if ARMCPU_ID[result] = id then
|
|
exit;
|
|
result := actUnknown;
|
|
end;
|
|
|
|
function ArmCpuTypeName(act: TArmCpuType; id: word): RawUtf8;
|
|
begin
|
|
if act = actUnknown then
|
|
result := 'ARM 0x' + RawUtf8(IntToHex(id, 3))
|
|
else
|
|
ShortStringToAnsi7String(ARMCPU_ID_TXT[act], result);
|
|
end;
|
|
|
|
function ArmCpuImplementer(id: byte): TArmCpuImplementer;
|
|
begin
|
|
for result := low(TArmCpuImplementer) to high(TArmCpuImplementer) do
|
|
if ARMCPU_IMPL[result] = id then
|
|
exit;
|
|
result := aciUnknown;
|
|
end;
|
|
|
|
function ArmCpuImplementerName(aci: TArmCpuImplementer; id: word): RawUtf8;
|
|
begin
|
|
if aci = aciUnknown then
|
|
result := 'HW 0x' + RawUtf8(IntToHex(id, 2))
|
|
else
|
|
ShortStringToAnsi7String(ARMCPU_IMPL_TXT[aci], result);
|
|
end;
|
|
|
|
|
|
{ *************** Per Class Properties O(1) Lookup via vmtAutoTable Slot }
|
|
|
|
procedure PatchCodePtrUInt(Code: PPtrUInt; Value: PtrUInt; LeaveUnprotected: boolean);
|
|
begin
|
|
PatchCode(Code, @Value, SizeOf(Code^), nil, LeaveUnprotected);
|
|
end;
|
|
|
|
{$ifdef CPUINTEL}
|
|
procedure RedirectCode(Func, RedirectFunc: Pointer);
|
|
var
|
|
rel: PtrInt;
|
|
NewJump: packed record
|
|
Code: byte; // $e9 = jmp {relative}
|
|
Distance: integer; // relative jump is 32-bit even on CPU64
|
|
end;
|
|
begin
|
|
if (Func = nil) or
|
|
(RedirectFunc = nil) or
|
|
(Func = RedirectFunc) then
|
|
exit; // nothing to redirect to
|
|
NewJump.Code := $e9; // on both i386 and x86_64
|
|
rel := PtrInt(PtrUInt(RedirectFunc) - PtrUInt(Func) - SizeOf(NewJump));
|
|
NewJump.Distance := rel;
|
|
{$ifdef CPU64}
|
|
if NewJump.Distance <> rel then
|
|
exit; // RedirectFunc is too far away from the original code :(
|
|
{$endif CPU64}
|
|
PatchCode(Func, @NewJump, SizeOf(NewJump));
|
|
assert(PByte(Func)^ = $e9);
|
|
end;
|
|
{$endif CPUINTEL}
|
|
|
|
|
|
|
|
{ ************** Cross-Platform Charset and CodePage Support }
|
|
|
|
function CharSetToCodePage(CharSet: integer): cardinal;
|
|
begin
|
|
case CharSet of
|
|
SHIFTJIS_CHARSET:
|
|
result := 932;
|
|
HANGEUL_CHARSET:
|
|
result := 949;
|
|
GB2312_CHARSET:
|
|
result := 936;
|
|
HEBREW_CHARSET:
|
|
result := 1255;
|
|
ARABIC_CHARSET:
|
|
result := 1256;
|
|
GREEK_CHARSET:
|
|
result := 1253;
|
|
TURKISH_CHARSET:
|
|
result := 1254;
|
|
VIETNAMESE_CHARSET:
|
|
result := 1258;
|
|
THAI_CHARSET:
|
|
result := 874;
|
|
EASTEUROPE_CHARSET:
|
|
result := 1250;
|
|
RUSSIAN_CHARSET:
|
|
result := 1251;
|
|
BALTIC_CHARSET:
|
|
result := 1257;
|
|
else
|
|
result := CP_WINANSI; // default ANSI_CHARSET = iso-8859-1 = windows-1252
|
|
end;
|
|
end;
|
|
|
|
function CodePageToCharSet(CodePage: cardinal): integer;
|
|
begin
|
|
case CodePage of
|
|
932:
|
|
result := SHIFTJIS_CHARSET;
|
|
949:
|
|
result := HANGEUL_CHARSET;
|
|
936:
|
|
result := GB2312_CHARSET;
|
|
1255:
|
|
result := HEBREW_CHARSET;
|
|
1256:
|
|
result := ARABIC_CHARSET;
|
|
1253:
|
|
result := GREEK_CHARSET;
|
|
1254:
|
|
result := TURKISH_CHARSET;
|
|
1258:
|
|
result := VIETNAMESE_CHARSET;
|
|
874:
|
|
result := THAI_CHARSET;
|
|
1250:
|
|
result := EASTEUROPE_CHARSET;
|
|
1251:
|
|
result := RUSSIAN_CHARSET;
|
|
1257:
|
|
result := BALTIC_CHARSET;
|
|
else
|
|
result := ANSI_CHARSET; // default is iso-8859-1 = windows-1252
|
|
end;
|
|
end;
|
|
|
|
|
|
{ ****************** Unicode, Time, File, Console, Library process }
|
|
|
|
procedure InitializeCriticalSectionIfNeededAndEnter(var cs: TRTLCriticalSection);
|
|
begin
|
|
if not IsInitializedCriticalSection(cs) then
|
|
InitializeCriticalSection(cs);
|
|
mormot.core.os.EnterCriticalSection(cs);
|
|
end;
|
|
|
|
procedure DeleteCriticalSectionIfNeeded(var cs: TRTLCriticalSection);
|
|
begin
|
|
if IsInitializedCriticalSection(cs) then
|
|
DeleteCriticalSection(cs);
|
|
end;
|
|
|
|
function Unicode_CodePage: integer;
|
|
begin
|
|
{$ifdef FPC}
|
|
// = GetSystemCodePage on POSIX, Lazarus may override to UTF-8 on Windows
|
|
result := DefaultSystemCodePage;
|
|
{$else}
|
|
// Delphi always uses the main Windows System Code Page
|
|
result := GetACP;
|
|
{$endif FPC}
|
|
end;
|
|
|
|
function Unicode_CompareString(PW1, PW2: PWideChar; L1, L2: PtrInt;
|
|
IgnoreCase: boolean): integer;
|
|
const
|
|
_CASEFLAG: array[boolean] of DWORD = (0, NORM_IGNORECASE);
|
|
begin
|
|
result := CompareStringW(LOCALE_USER_DEFAULT, _CASEFLAG[IgnoreCase], PW1, L1, PW2, L2);
|
|
end;
|
|
|
|
procedure Unicode_WideToShort(W: PWideChar; LW, CodePage: PtrInt;
|
|
var res: ShortString);
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
if LW <= 0 then
|
|
res[0] := #0
|
|
else if (LW <= 255) and
|
|
IsAnsiCompatibleW(W, LW) then
|
|
begin
|
|
// fast handling of pure English content
|
|
res[0] := AnsiChar(LW);
|
|
i := 1;
|
|
repeat
|
|
res[i] := AnsiChar(W^);
|
|
if i = LW then
|
|
break;
|
|
inc(W);
|
|
inc(i);
|
|
until false;
|
|
end
|
|
else
|
|
// use WinAPI, ICU or cwstring/RTL for accurate conversion
|
|
res[0] := AnsiChar(
|
|
Unicode_WideToAnsi(W, PAnsiChar(@res[1]), LW, 255, CodePage));
|
|
end;
|
|
|
|
function NowUtc: TDateTime;
|
|
begin
|
|
result := UnixMSTimeUtcFast / Int64(MSecsPerDay) + Int64(UnixDelta);
|
|
end;
|
|
|
|
function DateTimeToWindowsFileTime(DateTime: TDateTime): integer;
|
|
var
|
|
YY, MM, DD, H, m, s, ms: word;
|
|
begin
|
|
DecodeDate(DateTime, YY, MM, DD);
|
|
DecodeTime(DateTime, h, m, s, ms);
|
|
if (YY < 1980) or
|
|
(YY > 2099) then
|
|
result := 0
|
|
else
|
|
result := (s shr 1) or (m shl 5) or (h shl 11) or
|
|
cardinal((DD shl 16) or (MM shl 21) or (cardinal(YY - 1980) shl 25));
|
|
end;
|
|
|
|
function WindowsFileTimeToDateTime(WinTime: integer): TDateTime;
|
|
var
|
|
date, time: TDateTime;
|
|
begin
|
|
with PLongRec(@WinTime)^ do
|
|
if TryEncodeDate(Hi shr 9 + 1980, Hi shr 5 and 15, Hi and 31, date) and
|
|
TryEncodeTime(Lo shr 11, Lo shr 5 and 63, Lo and 31 shl 1, 0, time) then
|
|
result := date + time
|
|
else
|
|
result := 0;
|
|
end;
|
|
|
|
const
|
|
FileTimePerMs = 10000; // a tick is 100ns
|
|
|
|
function WindowsFileTime64ToUnixMSTime(WinTime: QWord): TUnixMSTime;
|
|
begin
|
|
result := (Int64(WinTime) - UnixFileTimeDelta) div FileTimePerMs;
|
|
end;
|
|
|
|
function DirectorySize(const FileName: TFileName; Recursive: boolean;
|
|
const Mask: TFileName): Int64;
|
|
var
|
|
SR: TSearchRec;
|
|
dir: TFileName;
|
|
begin
|
|
result := 0;
|
|
dir := IncludeTrailingPathDelimiter(FileName);
|
|
if FindFirst(dir + Mask, faAnyFile, SR) <> 0 then
|
|
exit;
|
|
repeat
|
|
if SearchRecValidFile(SR) then
|
|
inc(result, SR.Size)
|
|
else if Recursive and
|
|
SearchRecValidFolder(SR) then
|
|
inc(result, DirectorySize(dir + SR.Name, true));
|
|
until FindNext(SR) <> 0;
|
|
FindClose(SR);
|
|
end;
|
|
|
|
function SafePathName(const Path: TFileName): boolean;
|
|
var
|
|
i, o: PtrInt;
|
|
begin
|
|
if Path <> '' then
|
|
begin
|
|
result := false;
|
|
if (Path[1] = '/') or
|
|
(PosExString(':', Path) <> 0) or
|
|
(PosExString('\\', Path) <> 0) then
|
|
exit;
|
|
o := 1;
|
|
repeat
|
|
i := PosExString('..', Path, o);
|
|
if i = 0 then
|
|
break;
|
|
o := i + 2; // '..test' or 'test..' are valid folder names
|
|
if cardinal(Path[o]) in [0, ord('\'), ord('/')] then
|
|
if (i = 1) or
|
|
(cardinal(Path[i - 1]) in [ord('\'), ord('/')]) then
|
|
exit;
|
|
until false;
|
|
end;
|
|
result := true;
|
|
end;
|
|
|
|
function SafePathNameU(const Path: RawUtf8): boolean;
|
|
var
|
|
i, o: PtrInt;
|
|
begin
|
|
if Path <> '' then
|
|
begin
|
|
result := false;
|
|
if (Path[1] = '/') or
|
|
(PosExChar(':', Path) <> 0) or
|
|
(PosEx('\\', Path) <> 0) then
|
|
exit;
|
|
o := 1;
|
|
repeat
|
|
i := PosEx('..', Path, o);
|
|
if i = 0 then
|
|
break;
|
|
o := i + 2;
|
|
if Path[o] in [#0, '\', '/'] then
|
|
if (i = 1) or
|
|
(Path[i - 1] in ['\', '/']) then
|
|
exit;
|
|
until false;
|
|
end;
|
|
result := true;
|
|
end;
|
|
|
|
function SafeFileName(const FileName: TFileName): boolean;
|
|
begin
|
|
result := SafePathName(ExtractPath(FileName));
|
|
end;
|
|
|
|
function SafeFileNameU(const FileName: RawUtf8): boolean;
|
|
begin
|
|
result := SafePathNameU(ExtractPathU(FileName));
|
|
end;
|
|
|
|
function NormalizeFileName(const FileName: TFileName): TFileName;
|
|
begin
|
|
result := StringReplace(FileName, InvertedPathDelim, PathDelim, [rfReplaceAll]);
|
|
end;
|
|
|
|
function QuoteFileName(const FileName: TFileName): TFileName;
|
|
begin
|
|
if (FileName <> '') and
|
|
(PosExString(' ', FileName) <> 0) and
|
|
(FileName[1] <> '"') then
|
|
result := '"' + FileName + '"'
|
|
else
|
|
result := FileName;
|
|
end;
|
|
|
|
procedure DisplayError(const fmt: string; const args: array of const);
|
|
var
|
|
msg: string;
|
|
begin
|
|
msg := Format(fmt, args);
|
|
DisplayFatalError('', RawUtf8(msg));
|
|
end;
|
|
|
|
function SearchRecToDateTime(const F: TSearchRec): TDateTime;
|
|
begin
|
|
{$ifdef ISDELPHIXE}
|
|
result := F.Timestamp; // use new API
|
|
{$else}
|
|
result := FileDateToDateTime(F.Time);
|
|
{$endif ISDELPHIXE}
|
|
end;
|
|
|
|
function SearchRecToDateTimeUtc(const F: TSearchRec): TDateTime;
|
|
begin
|
|
result := SearchRecToUnixTimeUtc(F) / Int64(SecsPerDay) + Int64(UnixDelta);
|
|
end;
|
|
|
|
function SearchRecValidFile(const F: TSearchRec): boolean;
|
|
begin
|
|
result := (F.Name <> '') and
|
|
(F.Attr and faInvalidFile = 0);
|
|
end;
|
|
|
|
function SearchRecValidFolder(const F: TSearchRec): boolean;
|
|
begin
|
|
result := (F.Attr and faDirectoryMask = faDirectory) and
|
|
(F.Name <> '') and
|
|
(F.Name <> '.') and
|
|
(F.Name <> '..');
|
|
end;
|
|
|
|
{ TFileStreamFromHandle }
|
|
|
|
destructor TFileStreamFromHandle.Destroy;
|
|
begin
|
|
if not fDontReleaseHandle then
|
|
FileClose(Handle); // otherwise file remains opened (FPC RTL inconsistency)
|
|
end;
|
|
|
|
{ TFileStreamEx }
|
|
|
|
function TFileStreamEx.GetSize: Int64;
|
|
begin
|
|
result := FileSize(Handle); // faster than 3 FileSeek() calls
|
|
end;
|
|
|
|
constructor TFileStreamEx.Create(const aFileName: TFileName; Mode: cardinal);
|
|
var
|
|
h: THandle;
|
|
begin
|
|
if Mode and fmCreate = fmCreate then
|
|
h := FileCreate(aFileName, Mode and (not fmCreate))
|
|
else
|
|
h := FileOpen(aFileName, Mode);
|
|
CreateFromHandle(aFileName, h);
|
|
end;
|
|
|
|
constructor TFileStreamEx.CreateFromHandle(const aFileName: TFileName; aHandle: THandle);
|
|
begin
|
|
if not ValidHandle(aHandle) then
|
|
raise EOSException.CreateFmt('%s.Create(%s) failed as %s',
|
|
[ClassNameShort(self)^, aFileName, GetErrorText(GetLastError)]);
|
|
inherited Create(aHandle); // TFileStreamFromHandle constructor which own it
|
|
fFileName := aFileName;
|
|
end;
|
|
|
|
constructor TFileStreamEx.CreateWrite(const aFileName: TFileName);
|
|
var
|
|
h: THandle;
|
|
begin
|
|
h := FileOpen(aFileName, fmOpenReadWrite or fmShareRead);
|
|
if not ValidHandle(h) then // we may need to create the file
|
|
h := FileCreate(aFileName, fmShareRead);
|
|
CreateFromHandle(aFileName, h);
|
|
end;
|
|
|
|
|
|
{ TFileStreamNoWriteError }
|
|
|
|
constructor TFileStreamNoWriteError.CreateAndRenameIfLocked(
|
|
var aFileName: TFileName; aAliases: integer);
|
|
var
|
|
h: THandle;
|
|
fn, ext: TFileName;
|
|
err, retry: integer;
|
|
|
|
function CanOpenWrite: boolean;
|
|
begin
|
|
h := FileOpen(aFileName, fmOpenReadWrite or fmShareRead);
|
|
result := ValidHandle(h);
|
|
if not result then
|
|
err := GetLastError;
|
|
end;
|
|
|
|
begin
|
|
// logic similar to TSynLog.CreateLogWriter
|
|
h := 0;
|
|
err := 0;
|
|
if not CanOpenWrite then
|
|
if not FileExists(aFileName) then
|
|
// immediately raise EOSException if this new file could not be created
|
|
h := FileCreate(aFileName, fmShareRead)
|
|
else
|
|
begin
|
|
fn := aFileName;
|
|
ext := ExtractFileExt(aFileName);
|
|
for retry := 1 to aAliases do
|
|
begin
|
|
if IsSharedViolation(err) then
|
|
begin
|
|
// file was locked: wait a little for a background process and retry
|
|
SleepHiRes(50);
|
|
if CanOpenWrite then
|
|
break;
|
|
end;
|
|
// file can't be opened: try '<filename>-locked<#>.<ext>' alternatives
|
|
aFileName := ChangeFileExt(fn, '-locked' + IntToStr(retry) + ext);
|
|
if CanOpenWrite then
|
|
break;
|
|
end;
|
|
end;
|
|
CreateFromHandle(aFileName, h);
|
|
end;
|
|
|
|
function TFileStreamNoWriteError.Write(const Buffer; Count: Longint): Longint;
|
|
begin
|
|
FileWriteAll(Handle, @Buffer, Count); // and ignore any I/O error
|
|
result := Count; //
|
|
end;
|
|
|
|
|
|
function FileStreamSequentialRead(const FileName: TFileName): THandleStream;
|
|
begin
|
|
result := TFileStreamFromHandle.Create(FileOpenSequentialRead(FileName));
|
|
end;
|
|
|
|
function StreamCopyUntilEnd(Source, Dest: TStream): Int64;
|
|
var
|
|
tmp: array[word] of word; // 128KB stack buffer
|
|
read: integer;
|
|
begin
|
|
result := 0;
|
|
if (Source <> nil) and
|
|
(Dest <> nil) then
|
|
repeat
|
|
read := Source.Read(tmp, SizeOf(tmp));
|
|
if read <= 0 then
|
|
break;
|
|
Dest.WriteBuffer(tmp, read);
|
|
inc(result, read);
|
|
until false;
|
|
end;
|
|
|
|
function FileReadAll(F: THandle; Buffer: pointer; Size: PtrInt): boolean;
|
|
var
|
|
chunk, read: PtrInt;
|
|
begin
|
|
result := false;
|
|
if Size > 0 then
|
|
repeat
|
|
chunk := Size;
|
|
{$ifdef OSWINDOWS}
|
|
if chunk > 16 shl 20 then
|
|
chunk := 16 shl 20; // to avoid ERROR_NO_SYSTEM_RESOURCES errors
|
|
{$endif OSWINDOWS}
|
|
read := FileRead(F, Buffer^, chunk);
|
|
if read <= 0 then
|
|
exit; // error reading Size bytes
|
|
inc(PByte(Buffer), read);
|
|
dec(Size, read);
|
|
until Size = 0;
|
|
result := true;
|
|
end;
|
|
|
|
function FileWriteAll(F: THandle; Buffer: pointer; Size: PtrInt): boolean;
|
|
var
|
|
written: PtrInt;
|
|
begin
|
|
result := false;
|
|
if Size > 0 then
|
|
repeat
|
|
written := FileWrite(F, Buffer^, Size);
|
|
if written <= 0 then
|
|
exit; // fatal error
|
|
inc(PByte(Buffer), written); // e.g. may have been interrrupted
|
|
dec(Size, written);
|
|
until Size = 0;
|
|
result := true;
|
|
end;
|
|
|
|
function StringFromFile(const FileName: TFileName; HasNoSize: boolean): RawByteString;
|
|
var
|
|
F: THandle;
|
|
size: Int64;
|
|
read, pos: integer;
|
|
tmp: array[0..$7fff] of AnsiChar; // 32KB stack buffer
|
|
begin
|
|
result := '';
|
|
if FileName = '' then
|
|
exit;
|
|
F := FileOpenSequentialRead(FileName); // = plain fpOpen() on POSIX
|
|
if ValidHandle(F) then
|
|
begin
|
|
if HasNoSize then
|
|
begin
|
|
pos := 0;
|
|
repeat
|
|
read := FileRead(F, tmp, SizeOf(tmp)); // fill per 32KB local buffer
|
|
if read <= 0 then
|
|
break;
|
|
SetLength(result, pos + read); // in-place resize
|
|
MoveFast(tmp, PByteArray(result)^[pos], read);
|
|
inc(pos, read);
|
|
until false;
|
|
end
|
|
else
|
|
begin
|
|
size := FileSize(F);
|
|
if (size < MaxInt) and // 2GB seems big enough for a RawByteString
|
|
(size > 0) then
|
|
begin
|
|
FastSetString(RawUtf8(result), size); // assume CP_UTF8 for FPC RTL bug
|
|
if not FileReadAll(F, pointer(result), size) then
|
|
result := ''; // error reading
|
|
end;
|
|
end;
|
|
FileClose(F);
|
|
end;
|
|
end;
|
|
|
|
function StringFromFirstFile(const FileName: array of TFileName): RawByteString;
|
|
var
|
|
f: PtrInt;
|
|
begin
|
|
for f := 0 to high(FileName) do
|
|
begin
|
|
result := StringFromFile(FileName[f]);
|
|
if result <> '' then
|
|
exit;
|
|
end;
|
|
result := '';
|
|
end;
|
|
|
|
function StringFromFiles(const FileName: array of TFileName): TRawByteStringDynArray;
|
|
var
|
|
f: PtrInt;
|
|
begin
|
|
result := nil;
|
|
SetLength(result, length(FileName));
|
|
for f := 0 to high(FileName) do
|
|
result[f] := StringFromFile(FileName[f]);
|
|
end;
|
|
|
|
function StringFromFolders(const Folders: array of TFileName;
|
|
const Mask: TFileName; FileNames: PFileNameDynArray): TRawByteStringDynArray;
|
|
var
|
|
dir, fn: TFileName;
|
|
sr: TSearchRec;
|
|
f, n: PtrInt;
|
|
one: RawUtf8;
|
|
begin
|
|
result := nil;
|
|
if FileNames <> nil then
|
|
FileNames^ := nil;
|
|
n := 0;
|
|
for f := 0 to high(Folders) do
|
|
if DirectoryExists(Folders[f]) then
|
|
begin
|
|
dir := IncludeTrailingPathDelimiter(Folders[f]);
|
|
if FindFirst(dir + Mask, faAnyFile - faDirectory, sr) = 0 then
|
|
begin
|
|
repeat
|
|
if SearchRecValidFile(sr) then
|
|
begin
|
|
fn := dir + sr.Name;
|
|
one := StringFromFile(fn);
|
|
if one <> '' then
|
|
begin
|
|
if length(result) = n then
|
|
begin
|
|
SetLength(result, NextGrow(n));
|
|
if FileNames <> nil then
|
|
SetLength(FileNames^, length(result));
|
|
end;
|
|
result[n] := one;
|
|
if FileNames <> nil then
|
|
FileNames^[n] := fn;
|
|
inc(n);
|
|
end;
|
|
end;
|
|
until FindNext(sr) <> 0;
|
|
FindClose(sr);
|
|
end;
|
|
end;
|
|
if n = 0 then
|
|
exit;
|
|
DynArrayFakeLength(result, n);
|
|
if FileNames <> nil then
|
|
DynArrayFakeLength(FileNames^, n);
|
|
end;
|
|
|
|
function FileFromString(const Content: RawByteString;
|
|
const FileName: TFileName; FlushOnDisk: boolean): boolean;
|
|
var
|
|
h: THandle;
|
|
begin
|
|
result := false;
|
|
h := FileCreate(FileName);
|
|
if not ValidHandle(h) then
|
|
exit;
|
|
if not FileWriteAll(h, pointer(Content), length(Content)) then
|
|
begin
|
|
FileClose(h); // abort on write error
|
|
exit;
|
|
end;
|
|
if FlushOnDisk then
|
|
FlushFileBuffers(h);
|
|
FileClose(h);
|
|
result := true;
|
|
end;
|
|
|
|
function FileFromBuffer(Buf: pointer; Len: PtrInt; const FileName: TFileName): boolean;
|
|
var
|
|
h: THandle;
|
|
begin
|
|
result := false;
|
|
h := FileCreate(FileName);
|
|
if not ValidHandle(h) then
|
|
exit;
|
|
result := FileWriteAll(h, Buf, Len);
|
|
FileClose(h);
|
|
end;
|
|
|
|
function AppendToFile(const Content: RawUtf8; const FileName: TFileName;
|
|
BackupOverMaxSize: Int64): boolean;
|
|
var
|
|
h: THandle;
|
|
bak: TFileName;
|
|
begin
|
|
result := Content = '';
|
|
if result then
|
|
exit;
|
|
if (BackupOverMaxSize > 0) and
|
|
(FileSize(FileName) > BackupOverMaxSize) then
|
|
begin
|
|
bak := FileName + '.bak';
|
|
DeleteFile(bak);
|
|
RenameFile(FileName, bak);
|
|
h := 0;
|
|
end
|
|
else
|
|
h := FileOpen(FileName, fmOpenWriteShared);
|
|
if ValidHandle(h) then
|
|
FileSeek64(h, 0, soFromEnd) // append
|
|
else
|
|
begin
|
|
h := FileCreate(FileName, fmShareReadWrite);
|
|
if not ValidHandle(h) then
|
|
exit;
|
|
end;
|
|
result := FileWriteAll(h, pointer(Content), Length(Content));
|
|
FileClose(h);
|
|
end;
|
|
|
|
var
|
|
_TmpCounter: integer;
|
|
|
|
function TemporaryFileName: TFileName;
|
|
var
|
|
folder: TFileName;
|
|
retry: integer;
|
|
begin
|
|
// fast cross-platform implementation
|
|
folder := GetSystemPath(spTemp);
|
|
if _TmpCounter = 0 then
|
|
_TmpCounter := Random32;
|
|
retry := 10;
|
|
repeat
|
|
// thread-safe unique file name generation
|
|
result := Format('%s%s_%x.tmp',
|
|
[folder, Executable.ProgramName, InterlockedIncrement(_TmpCounter)]);
|
|
if not FileExists(result) then
|
|
exit;
|
|
dec(retry); // no endless loop
|
|
until retry = 0;
|
|
raise EOSException.Create('TemporaryFileName failed');
|
|
end;
|
|
|
|
function GetLastDelim(const FileName: TFileName; OtherDelim: cardinal): PtrInt;
|
|
var
|
|
{$ifdef UNICODE}
|
|
p: PWordArray absolute FileName;
|
|
{$else}
|
|
p: PByteArray absolute FileName;
|
|
{$endif UNICODE}
|
|
begin
|
|
result := length(FileName);
|
|
while (result > 0) and
|
|
not (p[result - 1] in [ord('\'), ord('/'), ord(':'), OtherDelim]) do
|
|
dec(result);
|
|
end;
|
|
|
|
function GetLastDelimU(const FileName: RawUtf8; OtherDelim: AnsiChar): PtrInt;
|
|
begin
|
|
result := length(FileName);
|
|
while (result > 0) and
|
|
not (FileName[result] in ['\', '/', ':', OtherDelim]) do
|
|
dec(result);
|
|
end;
|
|
|
|
function ExtractPath(const FileName: TFileName): TFileName;
|
|
begin
|
|
SetString(result, PChar(pointer(FileName)), GetLastDelim(FileName, 0));
|
|
end;
|
|
|
|
function ExtractName(const FileName: TFileName): TFileName;
|
|
begin
|
|
result := copy(FileName, GetLastDelim(FileName, 0) + 1, maxInt);
|
|
end;
|
|
|
|
function ExtractNameU(const FileName: RawUtf8): RawUtf8;
|
|
begin
|
|
result := copy(FileName, GetLastDelimU(FileName, #0) + 1, maxInt);
|
|
end;
|
|
|
|
function ExtractPathU(const FileName: RawUtf8): RawUtf8;
|
|
begin
|
|
FastSetString(result, pointer(FileName), GetLastDelimU(FileName, #0));
|
|
end;
|
|
|
|
function ExtractExt(const FileName: TFileName; WithoutDot: boolean): TFileName;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
result := '';
|
|
i := GetLastDelim(FileName, ord('.'));
|
|
if (i <= 1) or
|
|
(FileName[i] <> '.') then
|
|
exit;
|
|
if WithoutDot then
|
|
inc(i);
|
|
result := copy(FileName, i, 100);
|
|
end;
|
|
|
|
function ExtractExtU(const FileName: RawUtf8; WithoutDot: boolean): RawUtf8;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
result := '';
|
|
i := GetLastDelimU(FileName, '.');
|
|
if (i <= 1) or
|
|
(FileName[i] <> '.') then
|
|
exit;
|
|
if WithoutDot then
|
|
inc(i);
|
|
result := copy(FileName, i, 100);
|
|
end;
|
|
|
|
function ExtractExtP(const FileName: RawUtf8; WithoutDot: boolean): PUtf8Char;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
result := nil;
|
|
i := GetLastDelimU(FileName, '.') - 1;
|
|
if i <= 0 then
|
|
exit;
|
|
result := PUtf8Char(pointer(FileName)) + i;
|
|
if result^ <> '.' then
|
|
result := nil
|
|
else if WithoutDot then
|
|
inc(result);
|
|
end;
|
|
|
|
function GetFileNameWithoutExt(const FileName: TFileName; Extension: PFileName): TFileName;
|
|
var
|
|
i, max: PtrInt;
|
|
begin
|
|
i := length(FileName);
|
|
max := i - 16; // a file .extension is unlikely to be more than 16 chars
|
|
while (i > 0) and
|
|
not (cardinal(FileName[i]) in [ord('\'), ord('/'), ord('.'), ord(':')]) and
|
|
(i >= max) do
|
|
dec(i);
|
|
if (i = 0) or
|
|
(FileName[i] <> '.') then
|
|
begin
|
|
result := FileName;
|
|
if Extension <> nil then
|
|
Extension^ := '';
|
|
end
|
|
else
|
|
begin
|
|
result := copy(FileName, 1, i - 1);
|
|
if Extension <> nil then
|
|
Extension^ := copy(FileName, i, 100);
|
|
end;
|
|
end;
|
|
|
|
function GetFileNameWithoutExtOrPath(const FileName: TFileName): RawUtf8;
|
|
begin
|
|
result := RawUtf8(GetFileNameWithoutExt(ExtractFileName(FileName)));
|
|
end;
|
|
|
|
{$ifdef ISDELPHI20062007} // circumvent Delphi 2007 RTL inlining issue
|
|
function AnsiCompareFileName(const S1, S2 : TFileName): integer;
|
|
begin
|
|
result := SysUtils.AnsiCompareFileName(S1,S2);
|
|
end;
|
|
{$endif ISDELPHI20062007}
|
|
|
|
function SortDynArrayFileName(const A, B): integer;
|
|
var
|
|
Aname, Aext, Bname, Bext: TFileName;
|
|
begin
|
|
// code below is not very fast, but correct ;)
|
|
Aname := GetFileNameWithoutExt(string(A), @Aext);
|
|
Bname := GetFileNameWithoutExt(string(B), @Bext);
|
|
result := AnsiCompareFileName(Aext, Bext);
|
|
if result = 0 then
|
|
// if both extensions matches, compare by filename
|
|
result := AnsiCompareFileName(Aname, Bname);
|
|
end;
|
|
|
|
function EnsureDirectoryExists(const Directory: TFileName;
|
|
RaiseExceptionOnCreationFailure: ExceptionClass): TFileName;
|
|
begin
|
|
if Directory = '' then
|
|
if RaiseExceptionOnCreationFailure <> nil then
|
|
raise RaiseExceptionOnCreationFailure.Create('EnsureDirectoryExists('''')')
|
|
else
|
|
result := ''
|
|
else
|
|
begin
|
|
result := IncludeTrailingPathDelimiter(ExpandFileName(Directory));
|
|
if not DirectoryExists(result) then
|
|
if not ForceDirectories(result) then
|
|
if RaiseExceptionOnCreationFailure <> nil then
|
|
raise RaiseExceptionOnCreationFailure.CreateFmt(
|
|
'EnsureDirectoryExists(%s) failed as %s',
|
|
[result, GetErrorText(GetLastError)])
|
|
else
|
|
result := '';
|
|
end;
|
|
end;
|
|
|
|
function NormalizeDirectoryExists(const Directory: TFileName;
|
|
RaiseExceptionOnCreationFailure: ExceptionClass): TFileName;
|
|
begin
|
|
result := EnsureDirectoryExists(NormalizeFileName(Directory),
|
|
RaiseExceptionOnCreationFailure);
|
|
end;
|
|
|
|
function DirectoryDelete(const Directory: TFileName; const Mask: TFileName;
|
|
DeleteOnlyFilesNotDirectory: boolean; DeletedCount: PInteger): boolean;
|
|
var
|
|
F: TSearchRec;
|
|
Dir: TFileName;
|
|
n: integer;
|
|
begin
|
|
n := 0;
|
|
result := true;
|
|
if DirectoryExists(Directory) then
|
|
begin
|
|
Dir := IncludeTrailingPathDelimiter(Directory);
|
|
if FindFirst(Dir + Mask, faAnyFile - faDirectory, F) = 0 then
|
|
begin
|
|
repeat
|
|
if SearchRecValidFile(F) then
|
|
if DeleteFile(Dir + F.Name) then
|
|
inc(n)
|
|
else
|
|
result := false;
|
|
until FindNext(F) <> 0;
|
|
FindClose(F);
|
|
end;
|
|
if not DeleteOnlyFilesNotDirectory and
|
|
not RemoveDir(Dir) then
|
|
result := false;
|
|
end;
|
|
if DeletedCount <> nil then
|
|
DeletedCount^ := n;
|
|
end;
|
|
|
|
function DirectoryDeleteOlderFiles(const Directory: TFileName;
|
|
TimePeriod: TDateTime; const Mask: TFileName; Recursive: boolean;
|
|
TotalSize: PInt64): boolean;
|
|
var
|
|
F: TSearchRec;
|
|
Dir: TFileName;
|
|
old: TDateTime;
|
|
begin
|
|
if not Recursive and
|
|
(TotalSize <> nil) then
|
|
TotalSize^ := 0;
|
|
result := true;
|
|
if (Directory = '') or
|
|
not DirectoryExists(Directory) then
|
|
exit;
|
|
Dir := IncludeTrailingPathDelimiter(Directory);
|
|
if FindFirst(Dir + Mask, faAnyFile, F) = 0 then
|
|
begin
|
|
old := NowUtc - TimePeriod;
|
|
repeat
|
|
if SearchRecValidFolder(F) then
|
|
begin
|
|
if Recursive then
|
|
DirectoryDeleteOlderFiles(
|
|
Dir + F.Name, TimePeriod, Mask, true, TotalSize);
|
|
end
|
|
else if SearchRecValidFile(F) and
|
|
(SearchRecToDateTimeUtc(F) < old) then
|
|
if not DeleteFile(Dir + F.Name) then
|
|
result := false
|
|
else if TotalSize <> nil then
|
|
inc(TotalSize^, F.Size);
|
|
until FindNext(F) <> 0;
|
|
FindClose(F);
|
|
end;
|
|
end;
|
|
|
|
var
|
|
lastIsDirectoryWritable: TFileName; // naive but efficient cache
|
|
|
|
function IsDirectoryWritable(const Directory: TFileName;
|
|
Flags: TIsDirectoryWritable): boolean;
|
|
var
|
|
dir, last, fmt, fn: TFileName;
|
|
f: THandle;
|
|
retry: integer;
|
|
begin
|
|
// check the Directory itself
|
|
result := false;
|
|
if Directory = '' then
|
|
exit;
|
|
dir := ExcludeTrailingPathDelimiter(Directory);
|
|
if Flags = [] then
|
|
begin
|
|
last := lastIsDirectoryWritable;
|
|
result := (last <> '') and
|
|
(dir = last);
|
|
if result then
|
|
exit; // we just tested this folder
|
|
end;
|
|
if not FileIsWritable(dir) then
|
|
exit; // the folder does not exist or is read-only for the current user
|
|
{$ifdef OSWINDOWS}
|
|
// ensure is not a system/virtual folder
|
|
if ((idwExcludeWinUac in Flags) and
|
|
IsUacVirtualFolder(dir)) or
|
|
((idwExcludeWinSys in Flags) and
|
|
IsSystemFolder(dir)) then
|
|
exit;
|
|
// compute a non existing temporary file name in this Directory
|
|
if idwTryWinExeFile in Flags then
|
|
fmt := '%s\%x.exe' // may trigger the anti-virus heuristic
|
|
else
|
|
fmt := '%s\%x.test'; // neutral file name
|
|
// we tried .crt which triggered UAC heuristic but also some anti-viruses :(
|
|
{$else}
|
|
// compute a non existing temporary file name in this Directory
|
|
fmt := '%s/.%x.test'; // make the file "invisible"
|
|
{$endif OSWINDOWS}
|
|
retry := 10;
|
|
repeat
|
|
fn := Format(fmt, [dir, Random32]);
|
|
if not FileExists(fn) then
|
|
break;
|
|
dec(retry); // never loop forever
|
|
if retry = 0 then
|
|
exit;
|
|
until false;
|
|
// ensure we can create this temporary file
|
|
f := FileCreate(fn);
|
|
if not ValidHandle(f) then
|
|
exit; // a file can't be created
|
|
result := true;
|
|
if (idwWriteSomeContent in flags) and // some pointers and hash
|
|
(FileWrite(f, Executable, SizeOf(Executable)) <> SizeOf(Executable)) then
|
|
result := false;
|
|
FileClose(f);
|
|
if not DeleteFile(fn) then // success if the file can be created and deleted
|
|
result := false
|
|
else if result then
|
|
lastIsDirectoryWritable := dir
|
|
end;
|
|
|
|
|
|
{$ifndef NOEXCEPTIONINTERCEPT}
|
|
|
|
{$ifdef WITH_RAISEPROC} // for FPC on Win32 + Linux (Win64=WITH_VECTOREXCEPT)
|
|
var
|
|
OldRaiseProc: TExceptProc;
|
|
|
|
procedure SynRaiseProc(Obj: TObject; Addr: CodePointer;
|
|
FrameCount: integer; Frame: PCodePointer);
|
|
var
|
|
ctxt: TSynLogExceptionContext;
|
|
backuplasterror: DWORD;
|
|
backuphandler: TOnRawLogException;
|
|
begin
|
|
if (Obj <> nil) and
|
|
Obj.InheritsFrom(Exception) then
|
|
begin
|
|
backuplasterror := GetLastError;
|
|
backuphandler := _RawLogException;
|
|
if Assigned(backuphandler) then
|
|
try
|
|
_RawLogException := nil; // disable nested exception
|
|
ctxt.EClass := PPointer(Obj)^;
|
|
ctxt.EInstance := Exception(Obj);
|
|
ctxt.EAddr := PtrUInt(Addr);
|
|
if Obj.InheritsFrom(EExternal) then
|
|
ctxt.ELevel := sllExceptionOS
|
|
else
|
|
ctxt.ELevel := sllException;
|
|
ctxt.ETimestamp := UnixTimeUtc;
|
|
ctxt.EStack := pointer(Frame);
|
|
ctxt.EStackCount := FrameCount;
|
|
backuphandler(ctxt);
|
|
except
|
|
{ ignore any nested exception }
|
|
end;
|
|
_RawLogException := backuphandler;
|
|
SetLastError(backuplasterror); // may have changed above
|
|
end;
|
|
if Assigned(OldRaiseProc) then
|
|
OldRaiseProc(Obj, Addr, FrameCount, Frame);
|
|
end;
|
|
|
|
{$endif WITH_RAISEPROC}
|
|
|
|
var
|
|
RawExceptionIntercepted: boolean;
|
|
|
|
procedure RawExceptionIntercept(const Handler: TOnRawLogException);
|
|
begin
|
|
_RawLogException := Handler;
|
|
if RawExceptionIntercepted or
|
|
not Assigned(Handler) then
|
|
exit;
|
|
RawExceptionIntercepted := true; // intercept once
|
|
{$ifdef WITH_RAISEPROC}
|
|
// FPC RTL redirection function
|
|
if not Assigned(OldRaiseProc) then
|
|
begin
|
|
OldRaiseProc := RaiseProc;
|
|
RaiseProc := @SynRaiseProc;
|
|
end;
|
|
{$endif WITH_RAISEPROC}
|
|
{$ifdef WITH_VECTOREXCEPT} // SEH32/SEH64 official API
|
|
// RemoveVectoredContinueHandler() is available under 64 bit editions only
|
|
if Assigned(AddVectoredExceptionHandler) then
|
|
begin
|
|
AddVectoredExceptionHandler(0, @SynLogVectoredHandler);
|
|
AddVectoredExceptionHandler := nil;
|
|
end;
|
|
{$endif WITH_VECTOREXCEPT}
|
|
{$ifdef WITH_RTLUNWINDPROC}
|
|
// Delphi x86 RTL redirection function
|
|
if not Assigned(OldUnWindProc) then
|
|
begin
|
|
OldUnWindProc := RTLUnwindProc;
|
|
RTLUnwindProc := @SynRtlUnwind;
|
|
end;
|
|
{$endif WITH_RTLUNWINDPROC}
|
|
end;
|
|
|
|
{$endif NOEXCEPTIONINTERCEPT}
|
|
|
|
|
|
{ TMemoryMap }
|
|
|
|
function TMemoryMap.Map(aFile: THandle; aCustomSize: PtrUInt;
|
|
aCustomOffset: Int64; aFileOwned: boolean; aFileSize: Int64): boolean;
|
|
var
|
|
Available: Int64;
|
|
begin
|
|
fBuf := nil;
|
|
fBufSize := 0;
|
|
{$ifdef OSWINDOWS}
|
|
fMap := 0;
|
|
{$endif OSWINDOWS}
|
|
fFileLocal := aFileOwned;
|
|
fFile := aFile;
|
|
if aFileSize < 0 then
|
|
aFileSize := mormot.core.os.FileSize(fFile);
|
|
fFileSize := aFileSize;
|
|
if aFileSize = 0 then
|
|
begin
|
|
result := true; // handle 0 byte file without error (but no memory map)
|
|
exit;
|
|
end;
|
|
result := false;
|
|
if (fFileSize <= 0)
|
|
{$ifdef CPU32} or (fFileSize > maxInt){$endif} then
|
|
// maxInt = $7FFFFFFF = 1.999 GB (2GB would induce PtrInt errors on CPU32)
|
|
exit;
|
|
if aCustomSize = 0 then
|
|
fBufSize := fFileSize
|
|
else
|
|
begin
|
|
Available := fFileSize - aCustomOffset;
|
|
if Available < 0 then
|
|
exit;
|
|
if aCustomSize > Available then
|
|
fBufSize := Available;
|
|
fBufSize := aCustomSize;
|
|
end;
|
|
fLoadedNotMapped := fBufSize < 1 shl 20;
|
|
if fLoadedNotMapped then
|
|
begin
|
|
// mapping is not worth it for size < 1MB which can be just read at once
|
|
GetMem(fBuf, fBufSize);
|
|
FileSeek64(fFile, aCustomOffset);
|
|
if FileReadAll(fFile, fBuf, fBufSize) then
|
|
result := true
|
|
else
|
|
begin
|
|
Freemem(fBuf);
|
|
fBuf := nil;
|
|
fLoadedNotMapped := false;
|
|
end;
|
|
end
|
|
else
|
|
// call actual Windows/POSIX memory mapping API
|
|
result := DoMap(aCustomOffset);
|
|
end;
|
|
|
|
procedure TMemoryMap.Map(aBuffer: pointer; aBufferSize: PtrUInt);
|
|
begin
|
|
fBuf := aBuffer;
|
|
fFileSize := aBufferSize;
|
|
fBufSize := aBufferSize;
|
|
{$ifdef OSWINDOWS}
|
|
fMap := 0;
|
|
{$endif OSWINDOWS}
|
|
fFile := 0;
|
|
fFileLocal := false;
|
|
end;
|
|
|
|
function TMemoryMap.Map(const aFileName: TFileName): boolean;
|
|
var
|
|
F: THandle;
|
|
begin
|
|
result := false;
|
|
// Memory-mapped file access does not go through the cache manager so
|
|
// using FileOpenSequentialRead() is pointless here
|
|
F := FileOpen(aFileName, fmOpenReadShared);
|
|
if not ValidHandle(F) then
|
|
exit;
|
|
result := Map(F);
|
|
if not result then
|
|
FileClose(F);
|
|
fFileLocal := result;
|
|
end;
|
|
|
|
procedure TMemoryMap.UnMap;
|
|
begin
|
|
if fLoadedNotMapped then
|
|
// mapping was not worth it
|
|
Freemem(fBuf)
|
|
else
|
|
// call actual Windows/POSIX map API
|
|
DoUnMap;
|
|
fBuf := nil;
|
|
fBufSize := 0;
|
|
if fFile <> 0 then
|
|
begin
|
|
if fFileLocal then
|
|
FileClose(fFile);
|
|
fFile := 0;
|
|
end;
|
|
end;
|
|
|
|
|
|
|
|
{ TSynMemoryStreamMapped }
|
|
|
|
constructor TSynMemoryStreamMapped.Create(const aFileName: TFileName;
|
|
aCustomSize: PtrUInt; aCustomOffset: Int64);
|
|
begin
|
|
fFileName := aFileName;
|
|
// Memory-mapped file access does not go through the cache manager so
|
|
// using FileOpenSequentialRead() is pointless here
|
|
fFileStream := TFileStreamEx.Create(aFileName, fmOpenReadShared);
|
|
Create(fFileStream.Handle, aCustomSize, aCustomOffset);
|
|
end;
|
|
|
|
constructor TSynMemoryStreamMapped.Create(aFile: THandle;
|
|
aCustomSize: PtrUInt; aCustomOffset: Int64);
|
|
begin
|
|
if not fMap.Map(aFile, aCustomSize, aCustomOffset) then
|
|
raise EOSException.CreateFmt('%s.Create(%s) mapping error',
|
|
[ClassNameShort(self)^, fFileName]);
|
|
inherited Create(fMap.fBuf, fMap.fBufSize);
|
|
end;
|
|
|
|
destructor TSynMemoryStreamMapped.Destroy;
|
|
begin
|
|
fMap.UnMap;
|
|
fFileStream.Free;
|
|
inherited;
|
|
end;
|
|
|
|
|
|
{ TExecutableResource }
|
|
|
|
function TExecutableResource.Open(const ResourceName: string; ResType: PChar;
|
|
Instance: TLibHandle): boolean;
|
|
begin
|
|
result := false;
|
|
if Instance = 0 then
|
|
Instance := HInstance;
|
|
HResInfo := FindResource(Instance, PChar(ResourceName), ResType);
|
|
if HResInfo = 0 then
|
|
exit;
|
|
HGlobal := LoadResource(Instance, HResInfo);
|
|
if HGlobal = 0 then // direct decompression from memory mapped .exe content
|
|
exit;
|
|
Buffer := LockResource(HGlobal);
|
|
Size := SizeofResource(Instance, HResInfo);
|
|
if Size > 0 then
|
|
result := true
|
|
else
|
|
Close; // paranoid check
|
|
end;
|
|
|
|
procedure TExecutableResource.Close;
|
|
begin
|
|
if HGlobal <> 0 then
|
|
begin
|
|
UnlockResource(HGlobal); // only needed outside of Windows
|
|
FreeResource(HGlobal);
|
|
HGlobal := 0;
|
|
end;
|
|
end;
|
|
|
|
|
|
{ ReserveExecutableMemory() / TFakeStubBuffer }
|
|
|
|
type
|
|
// internal memory buffer created with PAGE_EXECUTE_READWRITE flags
|
|
TFakeStubBuffer = class
|
|
public
|
|
Stub: PByteArray;
|
|
StubUsed: cardinal;
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
function Reserve(size: cardinal): pointer;
|
|
end;
|
|
|
|
var
|
|
CurrentFakeStubBuffer: TFakeStubBuffer;
|
|
CurrentFakeStubBuffers: array of TFakeStubBuffer;
|
|
CurrentFakeStubBufferLock: TLightLock;
|
|
{$ifdef UNIX}
|
|
MemoryProtection: boolean = false; // set to true if PROT_EXEC seems to fail
|
|
{$endif UNIX}
|
|
|
|
constructor TFakeStubBuffer.Create;
|
|
begin
|
|
{$ifdef OSWINDOWS}
|
|
Stub := VirtualAlloc(nil, STUB_SIZE, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
|
|
if Stub = nil then
|
|
{$else OSWINDOWS}
|
|
if not MemoryProtection then
|
|
Stub := StubCallAllocMem(STUB_SIZE, PROT_READ or PROT_WRITE or PROT_EXEC);
|
|
if (Stub = MAP_FAILED) or
|
|
MemoryProtection then
|
|
begin
|
|
// i.e. on OpenBSD or OSX M1, we can not have w^x protection
|
|
Stub := StubCallAllocMem(STUB_SIZE, PROT_READ OR PROT_WRITE);
|
|
if Stub <> MAP_FAILED then
|
|
MemoryProtection := True;
|
|
end;
|
|
if Stub = MAP_FAILED then
|
|
{$endif OSWINDOWS}
|
|
raise EOSException.Create('ReserveExecutableMemory(): OS mmap failed');
|
|
PtrArrayAdd(CurrentFakeStubBuffers, self);
|
|
end;
|
|
|
|
destructor TFakeStubBuffer.Destroy;
|
|
begin
|
|
{$ifdef OSWINDOWS}
|
|
VirtualFree(Stub, 0, MEM_RELEASE);
|
|
{$else}
|
|
fpmunmap(Stub, STUB_SIZE);
|
|
{$endif OSWINDOWS}
|
|
inherited;
|
|
end;
|
|
|
|
function TFakeStubBuffer.Reserve(size: cardinal): pointer;
|
|
begin
|
|
result := @Stub[StubUsed];
|
|
while size and 15 <> 0 do
|
|
inc(size); // ensure the returned buffers are 16 bytes aligned
|
|
inc(StubUsed, size);
|
|
end;
|
|
|
|
function ReserveExecutableMemory(size: cardinal): pointer;
|
|
begin
|
|
if size > STUB_SIZE then
|
|
raise EOSException.CreateFmt('ReserveExecutableMemory(size=%d>%d)',
|
|
[size, STUB_SIZE]);
|
|
CurrentFakeStubBufferLock.Lock;
|
|
try
|
|
if (CurrentFakeStubBuffer = nil) or
|
|
(CurrentFakeStubBuffer.StubUsed + size > STUB_SIZE) then
|
|
CurrentFakeStubBuffer := TFakeStubBuffer.Create;
|
|
result := CurrentFakeStubBuffer.Reserve(size);
|
|
finally
|
|
CurrentFakeStubBufferLock.UnLock;
|
|
end;
|
|
end;
|
|
|
|
{$ifdef UNIX}
|
|
procedure ReserveExecutableMemoryPageAccess(Reserved: pointer; Exec: boolean);
|
|
var
|
|
PageAlignedFakeStub: pointer;
|
|
flags: cardinal;
|
|
begin
|
|
if not MemoryProtection then
|
|
// nothing to be done on this platform
|
|
exit;
|
|
// toggle execution permission of memory to be able to write into memory
|
|
PageAlignedFakeStub := Pointer(
|
|
(PtrUInt(Reserved) div SystemInfo.dwPageSize) * SystemInfo.dwPageSize);
|
|
if Exec then
|
|
flags := PROT_READ OR PROT_EXEC
|
|
else
|
|
flags := PROT_READ or PROT_WRITE;
|
|
if SynMProtect(PageAlignedFakeStub, SystemInfo.dwPageSize shl 1, flags) < 0 then
|
|
raise EOSException.Create('ReserveExecutableMemoryPageAccess: mprotect fail');
|
|
end;
|
|
{$else}
|
|
procedure ReserveExecutableMemoryPageAccess(Reserved: pointer; Exec: boolean);
|
|
begin
|
|
// nothing to be done
|
|
end;
|
|
{$endif UNIX}
|
|
|
|
{$ifndef PUREMORMOT2}
|
|
function GetDelphiCompilerVersion: RawUtf8;
|
|
begin
|
|
result := COMPILER_VERSION;
|
|
end;
|
|
{$endif PUREMORMOT2}
|
|
|
|
function GetMemoryInfoText: RawUtf8;
|
|
var
|
|
info: TMemoryInfo;
|
|
begin
|
|
if GetMemoryInfo(info, false) then
|
|
_fmt('used %s/%s (%d%s free)', [_oskb(info.memtotal - info.memfree),
|
|
_oskb(info.memtotal), info.percent, '%'], result)
|
|
else
|
|
result := '';
|
|
end;
|
|
|
|
function GetDiskAvailable(aDriveFolderOrFile: TFileName): QWord;
|
|
var
|
|
free, total: QWord; // dummy values
|
|
begin
|
|
if not GetDiskInfo(aDriveFolderOrFile, result, free, total) then
|
|
result := 0;
|
|
end;
|
|
|
|
function GetSystemInfoText: RawUtf8;
|
|
var
|
|
avail, free, total: QWord;
|
|
begin
|
|
GetDiskInfo(Executable.ProgramFilePath, avail, free, total);
|
|
result := _fmt('Current UTC date is %s (%d)'#13#10'Memory %s'#13#10 +
|
|
'Executable free disk %s/%s'#13#10 +
|
|
{$ifdef OSPOSIX} 'LoadAvg is %s'#13#10 + {$endif OSPOSIX}
|
|
'%s'#13#10'%s'#13#10'%s'#13#10'%s'#13#10,
|
|
[FormatDateTime('yyyy"-"mm"-"dd" "hh":"nn":"ss', NowUtc), UnixTimeUtc,
|
|
GetMemoryInfoText, _oskb(avail), _oskb(total),
|
|
{$ifdef OSPOSIX} RetrieveLoadAvg, {$endif} Executable.Version.VersionInfo,
|
|
OSVersionText, CpuInfoText, BiosInfoText]);
|
|
end;
|
|
|
|
function ConsoleReadBody: RawByteString;
|
|
var
|
|
len, n: integer;
|
|
P: PByte;
|
|
begin
|
|
len := ConsoleStdInputLen;
|
|
FastNewRawByteString(result, len);
|
|
P := pointer(result);
|
|
while len > 0 do
|
|
begin
|
|
n := FileRead(StdInputHandle, P^, len);
|
|
if n <= 0 then
|
|
begin
|
|
result := ''; // read error
|
|
break;
|
|
end;
|
|
dec(len, n);
|
|
inc(P, n);
|
|
end;
|
|
end;
|
|
|
|
var
|
|
GlobalCriticalSection: TOSLock;
|
|
|
|
{ TSynLibrary }
|
|
|
|
function TSynLibrary.Resolve(const Prefix, ProcName: RawUtf8; Entry: PPointer;
|
|
RaiseExceptionOnFailure: ExceptionClass): boolean;
|
|
var
|
|
P: PAnsiChar;
|
|
name, search: RawUtf8;
|
|
{$ifdef OSPOSIX}
|
|
dlinfo: dl_info;
|
|
{$endif OSPOSIX}
|
|
begin
|
|
result := false;
|
|
if (Entry = nil) or
|
|
(fHandle = 0) or
|
|
(ProcName = '') then
|
|
exit; // avoid GPF
|
|
P := pointer(ProcName);
|
|
repeat
|
|
name := GetNextItem(P); // try all alternate names
|
|
if name = '' then
|
|
break;
|
|
if name[1] = '?' then
|
|
begin
|
|
RaiseExceptionOnFailure := nil;
|
|
delete(name, 1, 1);
|
|
end;
|
|
search := Prefix + name;
|
|
Entry^ := LibraryResolve(fHandle, pointer(search));
|
|
if (Entry^ = nil) and
|
|
(Prefix <> '') then // try without the prefix
|
|
Entry^ := LibraryResolve(fHandle, pointer(name));
|
|
result := Entry^ <> nil;
|
|
until result;
|
|
{$ifdef OSPOSIX}
|
|
if result and
|
|
not fLibraryPathTested then
|
|
begin
|
|
fLibraryPathTested := true;
|
|
FillCharFast(dlinfo, SizeOf(dlinfo), 0);
|
|
dladdr(Entry^, @dlinfo);
|
|
if dlinfo.dli_fname <> nil then
|
|
fLibraryPath := dlinfo.dli_fname;
|
|
end;
|
|
{$endif OSPOSIX}
|
|
if (RaiseExceptionOnFailure <> nil) and
|
|
not result then
|
|
begin
|
|
FreeLib;
|
|
raise RaiseExceptionOnFailure.CreateFmt(
|
|
'%s.Resolve(''%s%s''): not found in %s',
|
|
[ClassNameShort(self)^, Prefix, ProcName, LibraryPath]);
|
|
end;
|
|
end;
|
|
|
|
function TSynLibrary.ResolveAll(ProcName: PPAnsiChar; Entry: PPointer): boolean;
|
|
var
|
|
tmp: RawUtf8;
|
|
begin
|
|
repeat
|
|
if ProcName^ = nil then
|
|
break;
|
|
FastSetString(tmp, ProcName^, StrLen(ProcName^));
|
|
if not Resolve('', tmp, Entry) then
|
|
begin
|
|
FreeLib;
|
|
result := false;
|
|
exit;
|
|
end;
|
|
inc(ProcName);
|
|
inc(Entry);
|
|
until false;
|
|
result := true;
|
|
end;
|
|
|
|
destructor TSynLibrary.Destroy;
|
|
begin
|
|
FreeLib;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TSynLibrary.FreeLib;
|
|
begin
|
|
if fHandle = 0 then
|
|
exit; // nothing to free
|
|
LibraryClose(fHandle);
|
|
fHandle := 0;
|
|
end;
|
|
|
|
function TSynLibrary.TryLoadLibrary(const aLibrary: array of TFileName;
|
|
aRaiseExceptionOnFailure: ExceptionClass): boolean;
|
|
var
|
|
i, j: PtrInt;
|
|
{$ifdef OSWINDOWS}
|
|
cwd,
|
|
{$endif OSWINDOWS}
|
|
lib, libs, nwd: TFileName;
|
|
err: string;
|
|
begin
|
|
for i := 0 to high(aLibrary) do
|
|
begin
|
|
// check library name
|
|
lib := aLibrary[i];
|
|
if lib = '' then
|
|
continue;
|
|
result := true;
|
|
for j := 0 to i - 1 do
|
|
if aLibrary[j] = lib then
|
|
begin
|
|
result := false;
|
|
break;
|
|
end;
|
|
if not result then
|
|
continue; // don't try twice the same library name
|
|
// open the library
|
|
nwd := ExtractFilePath(lib);
|
|
if fTryFromExecutableFolder and
|
|
(nwd = '') and
|
|
FileExists(Executable.ProgramFilePath + lib) then
|
|
begin
|
|
lib := Executable.ProgramFilePath + lib;
|
|
nwd := Executable.ProgramFilePath;
|
|
end;
|
|
{$ifdef OSWINDOWS}
|
|
if nwd <> '' then
|
|
begin
|
|
cwd := GetCurrentDir;
|
|
SetCurrentDir(nwd); // change the current folder at loading on Windows
|
|
end;
|
|
fHandle := LibraryOpen(lib); // preserve x87 flags and prevent msg box
|
|
if nwd <> '' then
|
|
SetCurrentDir(cwd{%H-});
|
|
{$else}
|
|
fHandle := LibraryOpen(lib); // use regular .so loading behavior
|
|
{$endif OSWINDOWS}
|
|
if fHandle <> 0 then
|
|
begin
|
|
{$ifdef OSWINDOWS} // on POSIX, will call dladdr() in Resolve()
|
|
fLibraryPath := GetModuleName(fHandle);
|
|
if length(fLibraryPath) < length(lib) then
|
|
{$endif OSWINDOWS}
|
|
fLibraryPath := lib;
|
|
exit;
|
|
end;
|
|
// handle any error
|
|
if {%H-}libs = '' then
|
|
libs := lib
|
|
else
|
|
libs := libs + ', ' + lib;
|
|
err := LibraryError;
|
|
if err <> '' then
|
|
libs := libs + ' [' + err + ']';
|
|
end;
|
|
result := false;
|
|
if aRaiseExceptionOnFailure <> nil then
|
|
raise aRaiseExceptionOnFailure.CreateFmt('%s.TryLoadLibray failed' +
|
|
' - searched in %s', [ClassNameShort(self)^, libs]);
|
|
end;
|
|
|
|
function TSynLibrary.Exists: boolean;
|
|
begin
|
|
result := (self <> nil) and
|
|
(fHandle <> 0);
|
|
end;
|
|
|
|
|
|
{ TFileVersion }
|
|
|
|
constructor TFileVersion.Create(const aFileName: TFileName;
|
|
aMajor, aMinor, aRelease, aBuild: integer);
|
|
var
|
|
M, D: word;
|
|
begin
|
|
fFileName := aFileName;
|
|
SetVersion(aMajor, aMinor, aRelease, aBuild);
|
|
if fBuildDateTime = 0 then // get build date from file age
|
|
fBuildDateTime := FileAgeToDateTime(aFileName);
|
|
if fBuildDateTime <> 0 then
|
|
DecodeDate(fBuildDateTime, BuildYear, M, D);
|
|
end;
|
|
|
|
function TFileVersion.Version32: integer;
|
|
begin
|
|
if self = nil then
|
|
result := 0
|
|
else
|
|
result := Major shl 16 + Minor shl 8 + Release;
|
|
end;
|
|
|
|
function TFileVersion.SetVersion(aMajor, aMinor, aRelease, aBuild: integer): boolean;
|
|
begin
|
|
result := (Major <> aMajor) or
|
|
(Minor <> aMinor) or
|
|
(Release <> aRelease) or
|
|
(Build <> aBuild);
|
|
if not result then
|
|
exit;
|
|
Major := aMajor;
|
|
Minor := aMinor;
|
|
Release := aRelease;
|
|
Build := aBuild;
|
|
Main := Format('%d.%d', [Major, Minor]);
|
|
if Build <> 0 then
|
|
fDetailed := Format('%s.%d.%d', [Main, Release, Build])
|
|
else if Release <> 0 then
|
|
fDetailed := Format('%s.%d', [Main, Release])
|
|
else
|
|
fDetailed := Main;
|
|
fVersionInfo := '';
|
|
fUserAgent := '';
|
|
end;
|
|
|
|
function TFileVersion.BuildDateTimeString: string;
|
|
begin
|
|
result := DateTimeToIsoString(fBuildDateTime);
|
|
end;
|
|
|
|
function TFileVersion.DetailedOrVoid: string;
|
|
begin
|
|
if (self = nil) or
|
|
(Major or Minor or Release or Build = 0) then
|
|
result := ''
|
|
else
|
|
result := fDetailed;
|
|
end;
|
|
|
|
function TFileVersion.VersionInfo: RawUtf8;
|
|
begin
|
|
if self = nil then
|
|
result := ''
|
|
else
|
|
begin
|
|
if fVersionInfo = '' then
|
|
_fmt('%s %s (%s)', [ExtractFileName(fFileName),
|
|
DetailedOrVoid, BuildDateTimeString], fVersionInfo);
|
|
result := fVersionInfo;
|
|
end;
|
|
end;
|
|
|
|
function TFileVersion.UserAgent: RawUtf8;
|
|
begin
|
|
if self = nil then
|
|
result := ''
|
|
else
|
|
begin
|
|
if fUserAgent = '' then
|
|
begin
|
|
_fmt('%s/%s%s', [GetFileNameWithoutExtOrPath(fFileName), DetailedOrVoid,
|
|
OS_INITIAL[OS_KIND]], fUserAgent);
|
|
{$ifdef OSWINDOWS}
|
|
if OSVersion in WINDOWS_32 then
|
|
fUserAgent := fUserAgent + '32';
|
|
{$endif OSWINDOWS}
|
|
end;
|
|
result := fUserAgent;
|
|
end;
|
|
end;
|
|
|
|
class function TFileVersion.GetVersionInfo(const aFileName: TFileName): RawUtf8;
|
|
begin
|
|
with Create(aFileName, 0, 0, 0, 0) do
|
|
try
|
|
result := VersionInfo;
|
|
finally
|
|
Free;
|
|
end;
|
|
end;
|
|
|
|
function UserAgentParse(const UserAgent: RawUtf8;
|
|
out ProgramName, ProgramVersion: RawUtf8;
|
|
out OS: TOperatingSystem): boolean;
|
|
var
|
|
i, v, vlen, o: PtrInt;
|
|
begin
|
|
result := false;
|
|
ProgramName := Split(UserAgent, '/');
|
|
v := length(ProgramName);
|
|
if (v = 0) or
|
|
(UserAgent[v + 1] <> '/') then
|
|
exit;
|
|
inc(v, 2);
|
|
vlen := 0;
|
|
o := -1;
|
|
for i := v to length(UserAgent) do
|
|
if not (UserAgent[i] in ['0' .. '9', '.']) then
|
|
begin
|
|
vlen := i - v; // vlen may be 0 if DetailedOrVoid was ''
|
|
if UserAgent[i + 1] in [#0, '3'] then // end with OS_INITIAL or '32' suffix
|
|
o := ByteScanIndex(pointer(@OS_INITIAL),
|
|
ord(high(TOperatingSystem)) + 1, ord(UserAgent[i]));
|
|
break;
|
|
end;
|
|
if o < 0 then
|
|
exit; // should end with OS_INITIAL[OS_KIND]]
|
|
os := TOperatingSystem(o);
|
|
ProgramVersion := copy(UserAgent, v, vlen);
|
|
result := true;
|
|
end;
|
|
|
|
procedure SetExecutableVersion(const aVersionText: RawUtf8);
|
|
var
|
|
P: PUtf8Char;
|
|
i: integer;
|
|
ver: array[0 .. 3] of integer;
|
|
begin
|
|
P := pointer(aVersionText);
|
|
for i := 0 to 3 do
|
|
ver[i] := GetNextUInt32(P);
|
|
SetExecutableVersion(ver[0], ver[1], ver[2], ver[3]);
|
|
end;
|
|
|
|
procedure ComputeExecutableHash;
|
|
begin
|
|
with Executable do
|
|
begin
|
|
_fmt('%s %s (%s)', [ProgramFileName,
|
|
Version.DetailedOrVoid, Version.BuildDateTimeString], ProgramFullSpec);
|
|
Hash.c0 := Version.Version32;
|
|
{$ifdef OSLINUXANDROID}
|
|
Hash.c0 := crc32c(Hash.c0, pointer(CpuInfoFeatures), length(CpuInfoFeatures));
|
|
{$else}
|
|
{$ifdef CPUINTELARM}
|
|
Hash.c0 := crc32c(Hash.c0, @CpuFeatures, SizeOf(CpuFeatures));
|
|
{$else}
|
|
Hash.c0 := crc32c(Hash.c0, pointer(CpuInfoText), length(CpuInfoText));
|
|
{$endif OSLINUXANDROID}
|
|
{$endif CPUINTELARM}
|
|
Hash.c0 := crc32c(Hash.c0, pointer(Host), length(Host));
|
|
Hash.c1 := crc32c(Hash.c0, pointer(User), length(User));
|
|
Hash.c2 := crc32c(Hash.c1, pointer(ProgramFullSpec), length(ProgramFullSpec));
|
|
Hash.c3 := crc32c(Hash.c2, pointer(InstanceFileName), length(InstanceFileName));
|
|
end;
|
|
end;
|
|
|
|
procedure GetExecutableVersion;
|
|
begin
|
|
if Executable.Version.RetrieveInformationFromFileName then
|
|
ComputeExecutableHash;
|
|
end;
|
|
|
|
procedure InitializeExecutableInformation; // called once at startup
|
|
begin
|
|
with Executable do
|
|
begin
|
|
{$ifdef OSWINDOWS}
|
|
ProgramFileName := ParamStr(0); // RTL seems just fine here
|
|
{$else}
|
|
ProgramFileName := GetExecutableName(@InitializeExecutableInformation);
|
|
if (ProgramFileName = '') or
|
|
not FileExists(ProgramFileName) then
|
|
ProgramFileName := ExpandFileName(ParamStr(0));
|
|
{$endif OSWINDOWS}
|
|
ProgramFilePath := ExtractFilePath(ProgramFileName);
|
|
if IsLibrary then
|
|
InstanceFileName := GetModuleName(HInstance)
|
|
else
|
|
InstanceFileName := ProgramFileName;
|
|
ProgramName := GetFileNameWithoutExtOrPath(ProgramFileName);
|
|
GetUserHost(User, Host);
|
|
if Host = '' then
|
|
Host := 'unknown';
|
|
if User = '' then
|
|
User := 'unknown';
|
|
Version := TFileVersion.Create(ProgramFileName); // with versions=0
|
|
Command := TExecutableCommandLine.Create;
|
|
Command.ExeDescription := ProgramName;
|
|
Command.Parse;
|
|
end;
|
|
ComputeExecutableHash;
|
|
end;
|
|
|
|
procedure SetExecutableVersion(aMajor, aMinor, aRelease, aBuild: integer);
|
|
begin
|
|
if Executable.Version.SetVersion(aMajor, aMinor, aRelease, aBuild) then
|
|
ComputeExecutableHash; // re-compute if changed
|
|
end;
|
|
|
|
|
|
{ TExecutableCommandLine }
|
|
|
|
function TExecutableCommandLine.SwitchAsText(const v: RawUtf8): RawUtf8;
|
|
begin
|
|
result := fSwitch[length(v) > 1] + v;
|
|
end;
|
|
|
|
procedure TExecutableCommandLine.Describe(const v: array of RawUtf8;
|
|
k: TExecutableCommandLineKind; d, def: RawUtf8; argindex: integer);
|
|
var
|
|
i, j: PtrInt;
|
|
desc, param, pnames, sp: RawUtf8;
|
|
begin
|
|
if (self = nil) or
|
|
(d = '') then
|
|
exit;
|
|
if k <> clkArg then
|
|
begin
|
|
if high(v) < 0 then
|
|
exit;
|
|
desc := SwitchAsText(v[0]);
|
|
if length(v[0]) <> 1 then
|
|
desc := ' ' + desc; // right align --#
|
|
for i := 1 to high(v) do
|
|
desc := desc + ', ' + SwitchAsText(v[i]);
|
|
end;
|
|
if k <> clkOption then
|
|
begin
|
|
i := PosExChar('#', d); // #valuename in description -> <valuename>
|
|
if i > 0 then
|
|
begin
|
|
j := 1;
|
|
while d[i + j] > ' ' do
|
|
inc(j);
|
|
delete(d, i, 1); // remove #
|
|
if d[i] <> '#' then
|
|
param := copy(d, i, j - 1) // extract '<valuename>'
|
|
else
|
|
begin
|
|
param := copy(d, i + 1, j - 2); // ##type
|
|
delete(d, i, j); // not included in description
|
|
end;
|
|
end
|
|
else if k = clkArg then
|
|
if high(v) = 0 then
|
|
param := v[0]
|
|
else if argindex > 0 then
|
|
param := _fmt('arg%d', [argindex])
|
|
else
|
|
param := 'arg'
|
|
else
|
|
begin
|
|
i := PosEx(' - values: ', d); // see SetObjectFromExecutableCommandLine()
|
|
if i > 0 then
|
|
begin
|
|
inc(i, 11);
|
|
j := 1;
|
|
if copy(d, i, 7) = 'set of ' then
|
|
inc(j, 7);
|
|
while d[i + j] > ' ' do
|
|
inc(j);
|
|
param := copy(d, i, j);
|
|
dec(i, 11);
|
|
delete(d, i, j + 11);
|
|
if j > 50 then
|
|
begin
|
|
j := 50;
|
|
for i := 50 downto 1 do
|
|
if param[i] = '|' then
|
|
begin
|
|
j := i;
|
|
break;
|
|
end;
|
|
insert(fLineFeed + ' ', param, j + 1);
|
|
end;
|
|
end
|
|
else
|
|
param := 'value';
|
|
end;
|
|
desc := desc + ' <' + param + '>';
|
|
if (k = clkArg) and
|
|
(argindex > 0) then
|
|
begin
|
|
if argindex > length(fDescArg) then
|
|
SetLength(fDescArg, argindex);
|
|
fDescArg[argindex - 1] := param;
|
|
end;
|
|
end;
|
|
fDesc[k] := fDesc[k] + ' ' + desc;
|
|
j := 1;
|
|
if fSwitch[true] <> '--' then
|
|
repeat
|
|
i := PosEx('--', d, j); // e.g. '--switch' -> '/switch' on Windows
|
|
if i = 0 then
|
|
break;
|
|
delete(d, i, 2);
|
|
insert(fSwitch[true], d, i);
|
|
j := i;
|
|
until false;
|
|
if def <> '' then
|
|
def := ' (default ' + def + ')';
|
|
pnames := _fmt(' %0:-20s', [desc + def]);
|
|
if (length(pnames) > 22) or
|
|
(length(d) > 80) then
|
|
begin
|
|
// write description on next line(s)
|
|
sp := fLineFeed + ' ';
|
|
while length(d) > 57 do
|
|
begin
|
|
j := 57;
|
|
for i := 57 downto 1 do
|
|
if d[i] = ' ' then
|
|
begin
|
|
j := i;
|
|
break;
|
|
end;
|
|
if j = 57 then
|
|
for i := 57 downto 1 do
|
|
if d[i] in [',', ';', '|'] then
|
|
begin
|
|
j := i;
|
|
break;
|
|
end;
|
|
pnames := pnames + sp + copy(d, 1, j);
|
|
delete(d, 1, j);
|
|
end;
|
|
pnames := pnames + sp + d;
|
|
end
|
|
else
|
|
pnames := pnames + d; // we can put everything on the same line
|
|
fDescDetail[k] := fDescDetail[k] + pnames + fLineFeed;
|
|
end;
|
|
|
|
function TExecutableCommandLine.Find(const v: array of RawUtf8;
|
|
k: TExecutableCommandLineKind; const d, def: RawUtf8; f: PtrInt): PtrInt;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
if self <> nil then
|
|
begin
|
|
if k <> clkUndefined then
|
|
Describe(v, k, d, def, -1);
|
|
if (high(v) >= 0) and
|
|
(fNames[k] <> nil) then
|
|
for i := 0 to high(v) do
|
|
begin
|
|
result := FindNonVoid[fCaseSensitiveNames](
|
|
@fNames[k][f], pointer(v[i]), length(v[i]), length(fNames[k]) - f);
|
|
if result >= 0 then
|
|
begin
|
|
inc(result, f);
|
|
fRetrieved[k][result] := true;
|
|
exit;
|
|
end;
|
|
end;
|
|
end;
|
|
result := -1
|
|
end;
|
|
|
|
function TExecutableCommandLine.Arg(index: integer; const description: RawUtf8;
|
|
optional: boolean): boolean;
|
|
var
|
|
n: PtrUInt;
|
|
begin
|
|
result := self <> nil;
|
|
if not result then
|
|
exit;
|
|
n := length(fNames[clkArg]);
|
|
result := PtrUInt(index) < n;
|
|
if result then
|
|
fRetrieved[clkArg][index] := true
|
|
else
|
|
begin
|
|
SetLength(fRetrieved[clkArg], n + 1); // to notify missing <arg>
|
|
if optional then
|
|
fRetrieved[clkArg][index] := true;
|
|
end;
|
|
Describe([], clkArg, description, '', index + 1);
|
|
end;
|
|
|
|
function TExecutableCommandLine.ArgString(index: integer;
|
|
const description: RawUtf8; optional: boolean): string;
|
|
begin
|
|
result := '';
|
|
if Arg(index, description, optional) then
|
|
result := string(Args[0]);
|
|
end;
|
|
|
|
function TExecutableCommandLine.Arg(const name, description: RawUtf8): boolean;
|
|
begin
|
|
result := Arg([name], description);
|
|
end;
|
|
|
|
function TExecutableCommandLine.Arg(const name: array of RawUtf8;
|
|
const description: RawUtf8): boolean;
|
|
begin
|
|
result := Find(name, clkArg, description) >= 0;
|
|
end;
|
|
|
|
function TExecutableCommandLine.Option(const name, description: RawUtf8): boolean;
|
|
begin
|
|
result := Find([name], clkOption, description) >= 0;
|
|
end;
|
|
|
|
function TExecutableCommandLine.Option(const name: array of RawUtf8;
|
|
const description: RawUtf8): boolean;
|
|
begin
|
|
result := Find(name, clkOption, description) >= 0;
|
|
end;
|
|
|
|
function TExecutableCommandLine.Get(const name: RawUtf8; out value: RawUtf8;
|
|
const description, default: RawUtf8): boolean;
|
|
begin
|
|
result := Get([name], value, description, default);
|
|
end;
|
|
|
|
procedure AddRawUtf8(var Values: TRawUtf8DynArray; const Value: RawUtf8);
|
|
var
|
|
n: PtrInt;
|
|
begin
|
|
n := length(Values);
|
|
SetLength(Values, n + 1);
|
|
Values[n] := Value;
|
|
end;
|
|
|
|
function TExecutableCommandLine.Get(const name: array of RawUtf8;
|
|
out value: TRawUtf8DynArray; const description: RawUtf8): boolean;
|
|
var
|
|
first, i: PtrInt;
|
|
begin
|
|
result := false;
|
|
if self = nil then
|
|
exit;
|
|
Describe(name, clkParam, description, '', -1);
|
|
first := 0;
|
|
repeat
|
|
i := Find(name, clkParam, '', '', first);
|
|
if i < 0 then
|
|
break;
|
|
AddRawUtf8(value, fValues[i]);
|
|
result := true;
|
|
first := i + 1;
|
|
until first >= length(fValues);
|
|
end;
|
|
|
|
function TExecutableCommandLine.Get(const name: array of RawUtf8;
|
|
out value: RawUtf8; const description, default: RawUtf8): boolean;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
if self = nil then
|
|
i := -1
|
|
else
|
|
i := Find(name, clkParam, description, default);
|
|
if i >= 0 then
|
|
begin
|
|
value := Values[i];
|
|
result := true;
|
|
end
|
|
else
|
|
begin
|
|
value := default;
|
|
result := false;
|
|
end;
|
|
end;
|
|
|
|
function TExecutableCommandLine.Get(const name: RawUtf8; out value: string;
|
|
const description: RawUtf8; const default: string): boolean;
|
|
begin
|
|
result := Get([name], value, description, default);
|
|
end;
|
|
|
|
function TExecutableCommandLine.Get(const name: array of RawUtf8;
|
|
out value: string; const description: RawUtf8; const default: string): boolean;
|
|
var
|
|
tmp: RawUtf8;
|
|
begin
|
|
result := Get(name, tmp, description);
|
|
if result then
|
|
value := string(tmp)
|
|
else
|
|
value := default; // no conversion needed
|
|
end;
|
|
|
|
function TExecutableCommandLine.Get(const name: RawUtf8;
|
|
out value: TStringDynarray; const description: RawUtf8): boolean;
|
|
begin
|
|
result := Get([name], value, description);
|
|
end;
|
|
|
|
function TExecutableCommandLine.Get(const name: array of RawUtf8;
|
|
out value: TStringDynarray; const description: RawUtf8): boolean;
|
|
var
|
|
tmp: TRawUtf8DynArray;
|
|
i: PtrInt;
|
|
begin
|
|
result := Get(name, tmp, description);
|
|
SetLength(value, length(tmp));
|
|
for i := 0 to length(tmp) - 1 do
|
|
value[i] := string(tmp[i]);
|
|
end;
|
|
|
|
function TExecutableCommandLine.Get(const name: RawUtf8;
|
|
out value: integer; const description: RawUtf8; default: integer): boolean;
|
|
begin
|
|
result := Get([name], value, description, default);
|
|
end;
|
|
|
|
function defI(default: integer): RawUtf8;
|
|
begin
|
|
if default = maxInt then
|
|
result := ''
|
|
else
|
|
result := RawUtf8(IntToStr(default));
|
|
end;
|
|
|
|
function TExecutableCommandLine.Get(const name: array of RawUtf8;
|
|
out value: integer; const description: RawUtf8; default: integer): boolean;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
if self = nil then
|
|
i := -1
|
|
else
|
|
i := Find(name, clkParam, description, defI(default));
|
|
result := (i >= 0) and
|
|
ToInteger(Values[i], value);
|
|
if not result and
|
|
(default <> maxInt) then
|
|
value := default;
|
|
end;
|
|
|
|
function TExecutableCommandLine.Get(const name: RawUtf8; min, max: integer;
|
|
out value: integer; const description: RawUtf8; default: integer): boolean;
|
|
begin
|
|
result := Get([name], min, max, value, description, default);
|
|
end;
|
|
|
|
function TExecutableCommandLine.Get(const name: array of RawUtf8;
|
|
min, max: integer; out value: integer; const description: RawUtf8;
|
|
default: integer): boolean;
|
|
begin
|
|
result := Get(name, value, description, default) and
|
|
(value >= min) and
|
|
(value <= max);
|
|
end;
|
|
|
|
function TExecutableCommandLine.Has(const name: RawUtf8): boolean;
|
|
begin
|
|
result := Find([name], clkParam) >= 0;
|
|
end;
|
|
|
|
function TExecutableCommandLine.Has(const name: array of RawUtf8): boolean;
|
|
begin
|
|
result := Find(name, clkParam) >= 0;
|
|
end;
|
|
|
|
function TExecutableCommandLine.Param(
|
|
const name, description, default: RawUtf8): RawUtf8;
|
|
begin
|
|
Get([name], result, description, default);
|
|
end;
|
|
|
|
function TExecutableCommandLine.Param(const name: array of RawUtf8;
|
|
const description, default: RawUtf8): RawUtf8;
|
|
begin
|
|
Get(name, result, description, default);
|
|
end;
|
|
|
|
function TExecutableCommandLine.ParamS(const name: array of RawUtf8;
|
|
const description: RawUtf8; const default: string): string;
|
|
begin
|
|
Get(name, result, description, default);
|
|
end;
|
|
|
|
function TExecutableCommandLine.Param(const name: RawUtf8;
|
|
default: integer; const description: RawUtf8): integer;
|
|
begin
|
|
Get([name], result, description, default);
|
|
end;
|
|
|
|
function TExecutableCommandLine.Param(const name: array of RawUtf8;
|
|
default: integer;const description: RawUtf8): integer;
|
|
begin
|
|
Get(name, result, description, default);
|
|
end;
|
|
|
|
const
|
|
CLK_TXT: array[clkOption .. clkParam] of RawUtf8 = (
|
|
' [options]', ' [params]');
|
|
CLK_DESCR: array[clkOption .. clkParam] of RawUtf8 = (
|
|
'Options', 'Params');
|
|
CASE_DESCR: array[boolean] of RawUtf8 = (
|
|
':', ' (case-sensitive):');
|
|
|
|
function TExecutableCommandLine.FullDescription(
|
|
const customexedescription, exename, onlyusage: RawUtf8): RawUtf8;
|
|
var
|
|
clk: TExecutableCommandLineKind;
|
|
begin
|
|
if customexedescription <> '' then
|
|
fExeDescription := customexedescription;
|
|
result := fExeDescription + fLineFeed + fLineFeed + 'Usage: ';
|
|
if exename = '' then
|
|
result := result + Executable.ProgramName
|
|
else
|
|
result := result + exename;
|
|
result := result + fDesc[clkArg];
|
|
for clk := low(CLK_TXT) to high(CLK_TXT) do
|
|
if fDesc[clk] <> '' then
|
|
result := result + CLK_TXT[clk];
|
|
result := result + fLineFeed;
|
|
if onlyusage <> '' then
|
|
result := result + onlyusage
|
|
else
|
|
for clk := low(fDescDetail) to high(fDescDetail) do
|
|
if fDescDetail[clk] <> '' then
|
|
begin
|
|
if clk in [low(CLK_TXT) .. high(CLK_TXT)] then
|
|
result := result + fLineFeed +
|
|
CLK_DESCR[clk] + CASE_DESCR[CaseSensitiveNames];
|
|
result := result + fLineFeed + fDescDetail[clk];
|
|
end;
|
|
end;
|
|
|
|
function TExecutableCommandLine.DetectUnknown: RawUtf8;
|
|
var
|
|
clk: TExecutableCommandLineKind;
|
|
i: PtrInt;
|
|
begin
|
|
result := '';
|
|
for clk := low(fRetrieved) to high(fRetrieved) do
|
|
for i := 0 to length(fRetrieved[clk]) - 1 do
|
|
if not fRetrieved[clk][i] then
|
|
if clk = clkArg then
|
|
result := result + 'Missing <' + fDescArg[i] + '> argument' + fLineFeed
|
|
else
|
|
begin
|
|
result := result + 'Unexpected ' + SwitchAsText(fNames[clk][i]) + ' ';
|
|
case clk of
|
|
clkOption:
|
|
result := result + 'option';
|
|
clkParam:
|
|
result := result + fValues[i] + ' parameter';
|
|
end;
|
|
result := result + fLineFeed;
|
|
end;
|
|
end;
|
|
|
|
function TExecutableCommandLine.ConsoleWriteUnknown(
|
|
const exedescription: RawUtf8): boolean;
|
|
var
|
|
err: RawUtf8;
|
|
begin
|
|
err := DetectUnknown;
|
|
result := err <> '';
|
|
if not result then
|
|
exit;
|
|
ConsoleWrite(FullDescription(exedescription));
|
|
ConsoleWrite(err, ccLightRed);
|
|
TextColor(ccLightGray);
|
|
end;
|
|
|
|
function TExecutableCommandLine.ConsoleHelpFailed(
|
|
const exedescription: RawUtf8): boolean;
|
|
begin
|
|
if exedescription <> '' then
|
|
fExeDescription := exedescription;
|
|
result := Option(['h', 'help'], 'display this help');
|
|
if result then
|
|
ConsoleWrite(FullDescription)
|
|
else
|
|
result := ConsoleWriteUnknown(exedescription);
|
|
end;
|
|
|
|
procedure TExecutableCommandLine.Clear;
|
|
begin
|
|
CleanupInstance; // finalize all TRawUtf8DynArray fields
|
|
end;
|
|
|
|
function TExecutableCommandLine.Parse(
|
|
const DescriptionLineFeed, ShortSwitch, LongSwitch: RawUtf8): boolean;
|
|
var
|
|
i, j, n: PtrInt;
|
|
swlen: TByteDynArray;
|
|
s: RawUtf8;
|
|
begin
|
|
result := false;
|
|
fLineFeed := DescriptionLineFeed;
|
|
if (ShortSwitch = '') or
|
|
(LongSwitch = '') then
|
|
exit;
|
|
fSwitch[false] := ShortSwitch;
|
|
fSwitch[true] := LongSwitch;
|
|
if fRawParams = nil then
|
|
begin
|
|
n := ParamCount;
|
|
if n <= 0 then
|
|
exit; // may equal -1 e.g. from a .so on MacOS
|
|
SetLength(fRawParams, n);
|
|
for i := 0 to n - 1 do
|
|
fRawParams[i] := RawUtf8(ParamStr(i + 1));
|
|
end;
|
|
n := length(fRawParams);
|
|
if n = 0 then
|
|
begin
|
|
result := true;
|
|
exit;
|
|
end;
|
|
SetLength(swlen, n);
|
|
for i := 0 to n - 1 do
|
|
begin
|
|
s := fRawParams[i];
|
|
if s <> '' then
|
|
if CompareMemSmall(pointer(s), pointer(LongSwitch), length(LongSwitch)) then
|
|
swlen[i] := length(LongSwitch)
|
|
else if CompareMemSmall(pointer(s), pointer(ShortSwitch), length(ShortSwitch)) then
|
|
swlen[i] := length(ShortSwitch)
|
|
{$ifdef OSWINDOWS}
|
|
else while s[swlen[i] + 1] = '-' do
|
|
inc(swlen[i]); // allow -v --verbose on Windows for cross-platform run
|
|
{$endif OSWINDOWS}
|
|
end;
|
|
i := 0;
|
|
repeat
|
|
s := fRawParams[i];
|
|
if s <> '' then
|
|
if swlen[i] <> 0 then
|
|
begin
|
|
delete(s, 1, swlen[i]);
|
|
if s <> '' then
|
|
begin
|
|
j := PosExChar('=', s);
|
|
if j <> 1 then
|
|
if j <> 0 then
|
|
begin
|
|
AddRawUtf8(fNames[clkParam], copy(s, 1, j - 1));
|
|
AddRawUtf8(fValues, copy(s, j + 1, MaxInt));
|
|
end
|
|
else if (i + 1 = n) or
|
|
(swlen[i + 1] <> 0) then
|
|
AddRawUtf8(fNames[clkOption], s)
|
|
else
|
|
begin
|
|
AddRawUtf8(fNames[clkParam], s);
|
|
inc(i);
|
|
AddRawUtf8(fValues, fRawParams[i]);
|
|
end;
|
|
end;
|
|
end
|
|
else
|
|
AddRawUtf8(fNames[clkArg], s);
|
|
inc(i);
|
|
until i = n;
|
|
SetLength(fRetrieved[clkArg], length(fNames[clkArg]));
|
|
SetLength(fRetrieved[clkOption], length(fNames[clkOption]));
|
|
SetLength(fRetrieved[clkParam], length(fNames[clkParam]));
|
|
result := true;
|
|
end;
|
|
|
|
var
|
|
_SystemPath: array[TSystemPath] of TFileName; // GetSystemPath() cache
|
|
|
|
function GetSystemPath(kind: TSystemPath): TFileName;
|
|
begin
|
|
result := _SystemPath[kind];
|
|
if result <> '' then
|
|
exit;
|
|
_ComputeSystemPath(kind, result); // in os.posix.inc or os.windows.inc
|
|
_SystemPath[kind] := result;
|
|
end;
|
|
|
|
function SetSystemPath(kind: TSystemPath; const path: TFileName): boolean;
|
|
var
|
|
full: TFileName;
|
|
begin
|
|
full := ExpandFileName(ExcludeTrailingPathDelimiter(path));
|
|
result := DirectoryExists(full);
|
|
if result then
|
|
_SystemPath[kind] := IncludeTrailingPathDelimiter(full);
|
|
end;
|
|
|
|
function _GetExecutableLocation(aAddress: pointer): ShortString;
|
|
var
|
|
i: PtrInt;
|
|
begin // return the address as hexadecimal - hexstr() is not available on Delphi
|
|
result[0] := #0;
|
|
for i := SizeOf(aAddress) - 1 downto 0 do
|
|
AppendShortByteHex(PByteArray(aAddress)[i], result);
|
|
end; // mormot.core.log.pas will properly decode debug info - and handle .mab
|
|
|
|
var
|
|
_SystemStoreAsPemSafe: TLightLock;
|
|
_OneSystemStoreAsPem: array[TSystemCertificateStore] of record
|
|
Tix: cardinal;
|
|
Pem: RawUtf8;
|
|
end;
|
|
_SystemStoreAsPem: record
|
|
Tix: cardinal;
|
|
Scope: TSystemCertificateStores;
|
|
Pem: RawUtf8;
|
|
end;
|
|
|
|
function GetOneSystemStoreAsPem(CertStore: TSystemCertificateStore;
|
|
FlushCache: boolean; now: cardinal): RawUtf8;
|
|
begin
|
|
if now = 0 then
|
|
now := GetTickCount64 shr 18 + 1; // div 262.144 seconds = every 4.4 min
|
|
_SystemStoreAsPemSafe.Lock;
|
|
try
|
|
// first search if not already in cache
|
|
with _OneSystemStoreAsPem[CertStore] do
|
|
begin
|
|
if not FlushCache then
|
|
if Tix = now then
|
|
begin
|
|
result := Pem; // quick retrieved from cache
|
|
exit;
|
|
end;
|
|
// fallback search depending on the POSIX / Windows specific OS
|
|
result := _GetSystemStoreAsPem(CertStore); // implemented in each .inc
|
|
Tix := now;
|
|
Pem := result;
|
|
end;
|
|
finally
|
|
_SystemStoreAsPemSafe.UnLock;
|
|
end;
|
|
end;
|
|
|
|
function GetSystemStoreAsPem(CertStores: TSystemCertificateStores;
|
|
FlushCache, OnlySystemStore: boolean): RawUtf8;
|
|
var
|
|
now: cardinal;
|
|
s: TSystemCertificateStore;
|
|
v: RawUtf8;
|
|
begin
|
|
result := '';
|
|
now := GetTickCount64 shr 18 + 1;
|
|
_SystemStoreAsPemSafe.Lock;
|
|
try
|
|
// first search if not already in cache
|
|
if not FlushCache then
|
|
with _SystemStoreAsPem do
|
|
if (Tix = now) and
|
|
(Scope = CertStores) and
|
|
(Pem <> '') then
|
|
begin
|
|
result := Pem; // quick retrieved from cache
|
|
exit;
|
|
end;
|
|
// load from a file, bounded within the application or from env variable
|
|
if not OnlySystemStore then
|
|
begin
|
|
if GetSystemStoreAsPemLocalFile <> '' then
|
|
{$ifdef OSPOSIX}
|
|
if GetSystemStoreAsPemLocalFile[1] = '/' then // full /posix/path
|
|
{$else}
|
|
if GetSystemStoreAsPemLocalFile[2] = ':' then // 'C:\path\to\file.pem'
|
|
{$endif OSPOSIX}
|
|
result := StringFromFile(GetSystemStoreAsPemLocalFile)
|
|
else
|
|
result := StringFromFile(
|
|
Executable.ProgramFilePath + GetSystemStoreAsPemLocalFile);
|
|
if result = '' then
|
|
result := StringFromFile(GetEnvironmentVariable('SSL_CA_CERT_FILE'));
|
|
end;
|
|
finally
|
|
_SystemStoreAsPemSafe.UnLock; // GetOneSystemStoreAsPem() blocks
|
|
end;
|
|
// fallback to search depending on the POSIX / Windows specific OS stores
|
|
if result = '' then
|
|
for s := low(s) to high(s) do
|
|
if s in CertStores then
|
|
begin
|
|
v := GetOneSystemStoreAsPem(s, FlushCache, now);
|
|
if v <> '' then
|
|
result := result + v + #13#10;
|
|
end;
|
|
if result <> '' then
|
|
begin
|
|
_SystemStoreAsPemSafe.Lock;
|
|
try
|
|
with _SystemStoreAsPem do
|
|
begin
|
|
Tix := now;
|
|
Scope := CertStores;
|
|
Pem := result;
|
|
end;
|
|
finally
|
|
_SystemStoreAsPemSafe.UnLock;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{$ifdef CPUINTEL} // don't mess with raw SMBIOS encoding outside of Intel/AMD
|
|
|
|
// from DSP0134 3.6.0 System Management BIOS (SMBIOS) Reference Specification
|
|
const
|
|
SMB_ANCHOR = $5f4d535f; // _SM_
|
|
SMB_INT4 = $494d445f; // _DMI
|
|
SMB_INT5 = $5f; // _
|
|
SMB_ANCHOR4 = $334d535f; // _SM3
|
|
SMB_ANCHOR5 = $5f; // _
|
|
|
|
type
|
|
TSmbEntryPoint32 = packed record
|
|
Anchor: cardinal; // = SMB_ANCHOR
|
|
Checksum: byte;
|
|
Length: byte;
|
|
MajVers: byte;
|
|
MinVers: byte;
|
|
MaxSize: word;
|
|
Revision: byte;
|
|
PadTo16: array[1 .. 5] of byte;
|
|
IntAnch4: cardinal; // = SMB_INT4
|
|
IntAnch5: byte; // = SMB_INT5
|
|
IntChecksum: byte;
|
|
StructLength: word;
|
|
StructAddr: cardinal;
|
|
NumStruct: word;
|
|
BcdRevision: byte;
|
|
end;
|
|
PSmbEntryPoint32 = ^TSmbEntryPoint32;
|
|
|
|
TSmbEntryPoint64 = packed record
|
|
Anch4: cardinal; // = SMB_ANCHOR4
|
|
Anch5: byte; // = SMB_ANCHOR5
|
|
Checksum: byte;
|
|
Length: byte;
|
|
MajVers: byte;
|
|
MinVers: byte;
|
|
DocRev: byte;
|
|
Revision: byte;
|
|
Reserved: byte;
|
|
StructMaxLength: cardinal;
|
|
StructAddr: QWord;
|
|
end;
|
|
PSmbEntryPoint64 = ^TSmbEntryPoint64;
|
|
|
|
function GetRawSmbios32(p: PSmbEntryPoint32; var info: TRawSmbiosInfo): PtrUInt;
|
|
var
|
|
cs: byte;
|
|
i: PtrInt;
|
|
begin
|
|
cs := 0;
|
|
for i := 0 to p^.Length - 1 do
|
|
inc(cs, PByteArray(p)[i]);
|
|
if cs <> 0 then
|
|
begin
|
|
result := 0; // invalid checksum
|
|
exit;
|
|
end;
|
|
result := p^.StructAddr;
|
|
info.SmbMajorVersion := p^.MajVers;
|
|
info.SmbMinorVersion := p^.MinVers;
|
|
info.DmiRevision := p^.Revision; // 0 = SMBIOS 2.1
|
|
info.Length := p^.StructLength;
|
|
end;
|
|
|
|
function GetRawSmbios64(p: PSmbEntryPoint64; var info: TRawSmbiosInfo): PtrUInt;
|
|
var
|
|
cs: byte;
|
|
i: PtrInt;
|
|
begin
|
|
cs := 0;
|
|
for i := 0 to p^.Length - 1 do
|
|
inc(cs, PByteArray(p)[i]);
|
|
if cs <> 0 then
|
|
begin
|
|
result := 0;
|
|
exit;
|
|
end;
|
|
result := p^.StructAddr;
|
|
info.SmbMajorVersion := p^.MajVers;
|
|
info.SmbMinorVersion := p^.MinVers;
|
|
info.DmiRevision := p^.Revision; // 1 = SMBIOS 3.0
|
|
info.Length := p^.StructMaxLength;
|
|
end;
|
|
|
|
// caller should then try to decode SMB from pointer(result) + info.Len
|
|
function SearchSmbios(const mem: RawByteString; var info: TRawSmbiosInfo): PtrUInt;
|
|
var
|
|
p, pend: PSmbEntryPoint32;
|
|
begin
|
|
result := 0;
|
|
if mem = '' then
|
|
exit;
|
|
p := pointer(mem);
|
|
pend := @PByteArray(mem)[length(mem) - SizeOf(p^)];
|
|
repeat
|
|
if (p^.Anchor = SMB_ANCHOR) and
|
|
(p^.IntAnch4 = SMB_INT4) and
|
|
(p^.IntAnch5 = SMB_INT5) then
|
|
begin
|
|
result := GetRawSmbios32(p, info);
|
|
if result <> 0 then
|
|
exit;
|
|
end
|
|
else if (p^.Anchor = SMB_ANCHOR4) and
|
|
(p^.Checksum = SMB_ANCHOR5) then
|
|
begin
|
|
result := GetRawSmbios64(pointer(p), info);
|
|
if result <> 0 then
|
|
exit; // here info.Length = max length
|
|
end;
|
|
inc(PHash128(p)); // search on 16-byte (paragraph) boundaries
|
|
until PtrUInt(p) >= PtrUInt(pend);
|
|
end;
|
|
|
|
{$endif CPUINTEL}
|
|
|
|
procedure ComputeGetSmbios;
|
|
begin
|
|
GlobalLock; // thread-safe retrieval
|
|
try
|
|
if not _SmbiosRetrieved then
|
|
begin
|
|
_SmbiosRetrieved := true;
|
|
Finalize(RawSmbios.Data);
|
|
FillCharFast(RawSmbios, SizeOf(RawSmbios), 0);
|
|
if _GetRawSmbios(RawSmbios) then // OS specific call
|
|
if DecodeSmbios(RawSmbios, _Smbios) <> 0 then
|
|
begin
|
|
// we were able to retrieve and decode SMBIOS information
|
|
{$ifdef OSPOSIX}
|
|
_AfterDecodeSmbios(RawSmbios); // persist in SMB_CACHE for non-root
|
|
{$endif OSPOSIX}
|
|
exit;
|
|
end;
|
|
// if not root on POSIX, SMBIOS is not available
|
|
// -> try to get what the OS exposes (Linux, MacOS or FreeBSD)
|
|
DirectSmbiosInfo(_Smbios);
|
|
end;
|
|
finally
|
|
GlobalUnLock;
|
|
end;
|
|
end;
|
|
|
|
function GetRawSmbios: boolean;
|
|
begin
|
|
if not _SmbiosRetrieved then
|
|
ComputeGetSmbios; // fill both RawSmbios and _Smbios[]
|
|
result := RawSmbios.Data <> '';
|
|
end;
|
|
|
|
function GetSmbios(info: TSmbiosBasicInfo): RawUtf8;
|
|
begin
|
|
if not _SmbiosRetrieved then
|
|
ComputeGetSmbios; // fill both RawSmbios and _Smbios[]
|
|
result := _Smbios[info];
|
|
end;
|
|
|
|
{$ifdef ISDELPHI} // missing convenient RTL function in Delphi
|
|
function TryStringToGUID(const s: string; var uuid: TGuid): boolean;
|
|
begin
|
|
try
|
|
uuid := StringToGUID(s);
|
|
result := true;
|
|
except
|
|
result := false;
|
|
end;
|
|
end;
|
|
{$endif ISDELPHI}
|
|
|
|
procedure GetComputerUuid(out uuid: TGuid);
|
|
var
|
|
n, i: PtrInt;
|
|
u: THash128Rec absolute uuid;
|
|
s: RawByteString;
|
|
fn: TFileName;
|
|
mac: TRawUtf8DynArray;
|
|
|
|
procedure crctext(const s: RawUtf8);
|
|
begin
|
|
if s = '' then
|
|
exit;
|
|
u.c[n] := crc32c(u.c[n], pointer(s), length(s));
|
|
n := (n + 1) and 3; // update only 32-bit of UUID per crctext() call
|
|
end;
|
|
|
|
begin
|
|
// first try to retrieve the Machine BIOS UUID
|
|
if not _SmbiosRetrieved then
|
|
ComputeGetSmbios; // maybe from local SMB_CACHE file for non-root
|
|
if (_Smbios[sbiUuid] <> '') and
|
|
TryStringToGUID('{' + string(_Smbios[sbiUuid]) + '}', uuid) then
|
|
exit;
|
|
// did we already compute this UUID?
|
|
fn := UUID_CACHE;
|
|
s := StringFromFile(fn);
|
|
if length(s) = SizeOf(uuid) then
|
|
begin
|
|
uuid := PGuid(s)^; // seems to be a valid UUID binary blob
|
|
exit;
|
|
end;
|
|
// no known UUID: compute and store a 128-bit hash from HW specs
|
|
// which should remain identical even between full OS reinstalls
|
|
// note: /etc/machine-id is no viable alternative since it is from SW random
|
|
{$ifdef CPUINTELARM}
|
|
crc128c(@CpuFeatures, SizeOf(CpuFeatures), u.b);
|
|
{$else}
|
|
s := CPU_ARCH_TEXT;
|
|
crc128c(pointer(s), length(s), u.b); // rough starting point
|
|
{$endif CPUINTELARM}
|
|
if RawSmbios.Data <> '' then // some bios have no uuid but some HW info
|
|
crc32c128(@u.b, pointer(RawSmbios.Data), length(RawSmbios.Data));
|
|
n := 0;
|
|
for i := 0 to length(_Smbios) - 1 do // some of _Smbios[] may be set
|
|
crctext(PRawUtf8Array(@_Smbios)[i]);
|
|
crctext(CpuCacheText);
|
|
crctext(BiosInfoText);
|
|
crctext(CpuInfoText);
|
|
if Assigned(GetSystemMacAddress) then
|
|
// from mormot.net.sock or mormot.core.os.posix.inc for Linux only
|
|
mac := GetSystemMacAddress;
|
|
if mac <> nil then
|
|
begin
|
|
// MAC should make it unique at least over the local network
|
|
for i := 0 to high(mac) do
|
|
crctext(mac[i]);
|
|
// we have enough unique HW information to store it locally for next startup
|
|
// note: RawSmbios.Data may not be genuine e.g. between VMs
|
|
if FileFromBuffer(@u, SizeOf(u), fn) then
|
|
FileSetSticky(fn); // use S_ISVTX so that file is not removed from /var/tmp
|
|
end
|
|
else
|
|
// unpersisted fallback if mormot.net.sock is not included (very unlikely)
|
|
crctext(Executable.Host);
|
|
end;
|
|
|
|
procedure DecodeSmbiosUuid(src: PGuid; out dest: RawUtf8; const raw: TRawSmbiosInfo);
|
|
var
|
|
uid: TGuid;
|
|
begin
|
|
uid := src^;
|
|
// reject full $00 = unsupported or full $ff = not set
|
|
if IsZero(@uid, SizeOf(uid)) or
|
|
((PCardinalArray(@uid)[0] = $ffffffff) and
|
|
(PCardinalArray(@uid)[1] = $ffffffff) and
|
|
(PCardinalArray(@uid)[2] = $ffffffff) and
|
|
(PCardinalArray(@uid)[3] = $ffffffff)) then
|
|
exit;
|
|
// GUIDToString() already displays the first 4 bytes as little-endian
|
|
// - we don't need to swap those bytes as dmi_system_uuid() in dmidecode.c
|
|
// on Windows, to match "wmic csproduct get uuid" official value
|
|
// - on MacOs, sduInvert is set to match IOPlatformUUID value from ioreg :(
|
|
if (_SmbiosDecodeUuid = sduInvert) or
|
|
// - dmi_save_uuid() from the Linux kernel do check for SMBIOS 2.6 version
|
|
// https://elixir.bootlin.com/linux/latest/source/drivers/firmware/dmi_scan.c
|
|
((_SmbiosDecodeUuid = sduVersion) and
|
|
(raw.SmbMajorVersion shl 8 + raw.SmbMinorVersion < $0206)) then
|
|
begin
|
|
uid.D1 := bswap32(uid.D1);
|
|
uid.D2 := swap(uid.D2);
|
|
uid.D3 := swap(uid.D3);
|
|
end;
|
|
dest := RawUtf8(UpperCase(copy(GUIDToString(uid), 2, 36)));
|
|
end;
|
|
|
|
function DecodeSmbios(var raw: TRawSmbiosInfo; out info: TSmbiosBasicInfos): PtrInt;
|
|
var
|
|
lines: array[byte] of TSmbiosBasicInfo; // single pass efficient decoding
|
|
len, trimright: PtrInt;
|
|
cur: ^TSmbiosBasicInfo;
|
|
s, sEnd: PByteArray;
|
|
begin
|
|
result := 0;
|
|
Finalize(info);
|
|
s := pointer(raw.Data);
|
|
if s = nil then
|
|
exit;
|
|
sEnd := @s[length(raw.Data)];
|
|
FillCharFast(lines, SizeOf(lines), 0);
|
|
repeat
|
|
if (s[0] = 127) or // type (127=EOT)
|
|
(s[1] < 4) or // length
|
|
(PtrUInt(@s[s[1]]) > PtrUInt(sEnd)) then
|
|
begin
|
|
s := @s[2]; // truncate to the exact end of DMI/SMBIOS input
|
|
break;
|
|
end;
|
|
case s[0] of
|
|
0: // Bios Information (type 0)
|
|
begin
|
|
lines[s[4]] := sbiBiosVendor;
|
|
lines[s[5]] := sbiBiosVersion;
|
|
lines[s[8]] := sbiBiosDate;
|
|
if s[1] >= $17 then // 2.4+
|
|
begin
|
|
_fmt('%d.%d', [s[$14], s[$15]], info[sbiBiosRelease]);
|
|
_fmt('%d.%d', [s[$16], s[$17]], info[sbiBiosFirmware]);
|
|
end;
|
|
end;
|
|
1: // System Information (type 1)
|
|
begin
|
|
lines[s[4]] := sbiManufacturer;
|
|
lines[s[5]] := sbiProductName;
|
|
lines[s[6]] := sbiVersion;
|
|
lines[s[7]] := sbiSerial;
|
|
if s[1] >= $18 then // 2.1+
|
|
begin
|
|
DecodeSmbiosUuid(@s[8], info[sbiUuid], raw);
|
|
if s[1] >= $1a then // 2.4+
|
|
begin
|
|
lines[s[$19]] := sbiSku;
|
|
lines[s[$1a]] := sbiFamily;
|
|
end;
|
|
end;
|
|
end;
|
|
2: // Baseboard (or Module) Information (type 2) - keep only the first
|
|
begin
|
|
lines[s[4]] := sbiBoardManufacturer;
|
|
lines[s[5]] := sbiBoardProductName;
|
|
lines[s[6]] := sbiBoardVersion;
|
|
lines[s[7]] := sbiBoardSerial;
|
|
lines[s[8]] := sbiBoardAssetTag;
|
|
lines[s[10]] := sbiBoardLocation;
|
|
end;
|
|
4: // Processor Information (type 4) - keep only the first
|
|
begin
|
|
lines[s[7]] := sbiCpuManufacturer;
|
|
lines[s[$10]] := sbiCpuVersion;
|
|
if s[1] >= $22 then // 2.3+
|
|
begin
|
|
lines[s[$20]] := sbiCpuSerial;
|
|
lines[s[$21]] := sbiCpuAssetTag;
|
|
lines[s[$22]] := sbiCpuPartNumber;
|
|
end;
|
|
end;
|
|
11: // OEM Strings (Type 11) - keep only the first
|
|
if s[4] <> 0 then
|
|
lines[1] := sbiOem; // e.g. 'vboxVer_6.1.36'
|
|
22: // Portable Battery (type 22) - keep only the first
|
|
if s[1] >= $0f then // 2.1+
|
|
begin
|
|
lines[s[4]] := sbiBatteryLocation;
|
|
lines[s[5]] := sbiBatteryManufacturer;
|
|
lines[s[8]] := sbiBatteryName;
|
|
lines[s[$0e]] := sbiBatteryVersion;
|
|
if s[1] >= $14 then // 2.2+
|
|
lines[s[$14]] := sbiBatteryChemistry;
|
|
end;
|
|
end;
|
|
s := @s[s[1]]; // go to string table
|
|
cur := @lines[1];
|
|
if s[0] = 0 then
|
|
inc(PByte(s)) // no string table
|
|
else
|
|
repeat
|
|
len := StrLen(s);
|
|
if cur^ <> sbiUndefined then
|
|
begin
|
|
if info[cur^] = '' then // only set the first occurrence if multiple
|
|
begin
|
|
trimright := len;
|
|
while (trimright <> 0) and
|
|
(s[trimright - 1] <= ord(' ')) do
|
|
dec(trimright);
|
|
FastSetString(info[cur^], s, trimright);
|
|
end;
|
|
cur^ := sbiUndefined; // reset slot in lines[]
|
|
end;
|
|
s := @s[len + 1]; // next string
|
|
inc(cur);
|
|
until s[0] = 0; // end of string table
|
|
inc(PByte(s)); // go to next structure
|
|
until false;
|
|
// compute the exact DMI/SMBIOS size, and adjust the raw.Data length
|
|
result := PtrUInt(s) - PtrUInt(raw.Data);
|
|
raw.Length := result;
|
|
if length(raw.Data) <> result then
|
|
FakeSetLength(raw.Data, result);
|
|
end;
|
|
|
|
|
|
{ **************** TSynLocker Threading Features }
|
|
|
|
// as reference, take a look at Linus insight
|
|
// from https://www.realworldtech.com/forum/?threadid=189711&curpostid=189755
|
|
{$ifdef CPUINTEL}
|
|
procedure DoPause; {$ifdef FPC} assembler; nostackframe; {$endif}
|
|
asm
|
|
pause
|
|
end;
|
|
{$endif CPUINTEL}
|
|
|
|
const
|
|
{$ifdef CPUINTEL}
|
|
SPIN_COUNT = 1000;
|
|
{$else}
|
|
SPIN_COUNT = 100; // since DoPause does nothing, switch to thread sooner
|
|
{$endif CPUINTEL}
|
|
|
|
function DoSpin(spin: PtrUInt): PtrUInt;
|
|
{$ifdef CPUINTEL} {$ifdef HASINLINE} inline; {$endif} {$endif}
|
|
// on Intel, the pause CPU instruction would relax the core
|
|
// on ARM/AARCH64, the not-inlined function call makes a small delay
|
|
begin
|
|
{$ifdef CPUINTEL}
|
|
DoPause;
|
|
{$endif CPUINTEL}
|
|
dec(spin);
|
|
if spin = 0 then
|
|
begin
|
|
SwitchToThread; // fpnanosleep on POSIX
|
|
spin := SPIN_COUNT;
|
|
end;
|
|
result := spin;
|
|
end;
|
|
|
|
|
|
{ TLightLock }
|
|
|
|
procedure TLightLock.Init;
|
|
begin
|
|
Flags := 0;
|
|
end;
|
|
|
|
procedure TLightLock.Done;
|
|
begin // just for compatibility with TOSLock
|
|
end;
|
|
|
|
procedure TLightLock.Lock;
|
|
begin
|
|
// we tried a dedicated asm but it was slower: inlining is preferred
|
|
if not LockedExc(Flags, 1, 0) then
|
|
LockSpin;
|
|
end;
|
|
|
|
procedure TLightLock.UnLock;
|
|
begin
|
|
{$ifdef CPUINTEL}
|
|
Flags := 0; // non reentrant locks need no additional thread safety
|
|
{$else}
|
|
LockedExc(Flags, 0, 1); // ARM can be weak-ordered
|
|
// https://preshing.com/20121019/this-is-why-they-call-it-a-weakly-ordered-cpu
|
|
{$endif CPUINTEL}
|
|
end;
|
|
|
|
function TLightLock.TryLock: boolean;
|
|
begin
|
|
result := (Flags = 0) and // first check without any (slow) atomic opcode
|
|
LockedExc(Flags, 1, 0);
|
|
end;
|
|
|
|
function TLightLock.IsLocked: boolean;
|
|
begin
|
|
result := Flags <> 0;
|
|
end;
|
|
|
|
procedure TLightLock.LockSpin;
|
|
var
|
|
spin: PtrUInt;
|
|
begin
|
|
spin := SPIN_COUNT;
|
|
repeat
|
|
spin := DoSpin(spin);
|
|
until TryLock;
|
|
end;
|
|
|
|
|
|
{ TRWLightLock }
|
|
|
|
procedure TRWLightLock.Init;
|
|
begin
|
|
Flags := 0; // bit 0=WriteLock, >0=ReadLock counter
|
|
end;
|
|
|
|
procedure TRWLightLock.ReadLock;
|
|
var
|
|
f: PtrUInt;
|
|
begin
|
|
// if not writing, atomically increase the RD counter in the upper flag bits
|
|
f := Flags and not 1; // bit 0=WriteLock, >0=ReadLock counter
|
|
if not LockedExc(Flags, f + 2, f) then
|
|
ReadLockSpin;
|
|
end;
|
|
|
|
function TRWLightLock.TryReadLock: boolean;
|
|
var
|
|
f: PtrUInt;
|
|
begin
|
|
// if not writing, atomically increase the RD counter in the upper flag bits
|
|
f := Flags and not 1; // bit 0=WriteLock, >0=ReadLock counter
|
|
result := LockedExc(Flags, f + 2, f);
|
|
end;
|
|
|
|
procedure TRWLightLock.ReadUnLock;
|
|
begin
|
|
LockedDec(Flags, 2);
|
|
end;
|
|
|
|
procedure TRWLightLock.ReadLockSpin;
|
|
var
|
|
spin: PtrUInt;
|
|
begin
|
|
spin := SPIN_COUNT;
|
|
repeat
|
|
spin := DoSpin(spin);
|
|
until TryReadLock;
|
|
end;
|
|
|
|
function TRWLightLock.TryWriteLock: boolean;
|
|
var
|
|
f: PtrUInt;
|
|
begin
|
|
f := Flags and not 1; // bit 0=WriteLock, >0=ReadLock
|
|
result := (Flags = f) and
|
|
LockedExc(Flags, f + 1, f);
|
|
end;
|
|
|
|
procedure TRWLightLock.WriteLock;
|
|
begin
|
|
if not TryWriteLock then
|
|
WriteLockSpin;
|
|
end;
|
|
|
|
procedure TRWLightLock.WriteUnLock;
|
|
begin
|
|
LockedDec(Flags, 1);
|
|
end;
|
|
|
|
procedure TRWLightLock.WriteLockSpin;
|
|
var
|
|
spin: PtrUInt;
|
|
begin
|
|
spin := SPIN_COUNT;
|
|
repeat
|
|
spin := DoSpin(spin);
|
|
until TryWriteLock;
|
|
end;
|
|
|
|
|
|
{ TRWLock }
|
|
|
|
procedure TRWLock.Init;
|
|
begin
|
|
// bit 0 = WriteLock, 1 = ReadWriteLock, 2.. = ReadOnlyLock counter
|
|
Flags := 0;
|
|
// no need to set the other fields because they will be reset if Flags=0
|
|
end;
|
|
|
|
procedure TRWLock.AssertDone;
|
|
begin
|
|
if Flags <> 0 then
|
|
raise EOSException.CreateFmt('TRWLock Flags=%x', [Flags]);
|
|
end;
|
|
|
|
// dedicated asm for this most simple (and used) method
|
|
{$ifdef FPC_ASMX64} // some Delphi version was reported to fail with no clue why
|
|
|
|
procedure TRWLock.ReadOnlyLock;
|
|
asm // stack frame is required since we may call SwitchToThread
|
|
{$ifdef SYSVABI}
|
|
mov rcx, rdi // rcx = self
|
|
{$endif SYSVABI}
|
|
@retry: mov r8d, SPIN_COUNT
|
|
@spin: mov rax, qword ptr [rcx + TRWLock.Flags]
|
|
and rax, not 1
|
|
lea rdx, [rax + 4]
|
|
lock cmpxchg qword ptr [rcx + TRWLock.Flags], rdx
|
|
jz @done
|
|
pause
|
|
dec r8d
|
|
jnz @spin
|
|
push rcx
|
|
call SwitchToThread
|
|
pop rcx
|
|
jmp @retry
|
|
@done: // restore the stack frame
|
|
end;
|
|
|
|
{$else}
|
|
|
|
procedure TRWLock.ReadOnlyLock;
|
|
var
|
|
f: PtrUInt;
|
|
begin
|
|
// if not writing, atomically increase the RD counter in the upper flag bits
|
|
f := Flags and not 1; // bit 0=WriteLock, 1=ReadWriteLock, >1=ReadOnlyLock
|
|
if not LockedExc(Flags, f + 4, f) then
|
|
ReadOnlyLockSpin;
|
|
end;
|
|
|
|
procedure TRWLock.ReadOnlyLockSpin;
|
|
var
|
|
spin, f: PtrUInt;
|
|
begin
|
|
spin := SPIN_COUNT;
|
|
repeat
|
|
spin := DoSpin(spin);
|
|
f := Flags and not 1; // retry ReadOnlyLock
|
|
until (Flags = f) and
|
|
LockedExc(Flags, f + 4, f);
|
|
end;
|
|
|
|
{$endif FPC_ASMX64}
|
|
|
|
procedure TRWLock.ReadOnlyUnLock;
|
|
begin
|
|
LockedDec(Flags, 4);
|
|
end;
|
|
|
|
procedure TRWLock.ReadWriteLock;
|
|
var
|
|
spin, f: PtrUInt;
|
|
tid: TThreadID;
|
|
begin
|
|
tid := GetCurrentThreadId;
|
|
if (Flags and 2 = 2) and
|
|
(LastReadWriteLockThread = tid) then
|
|
begin
|
|
inc(LastReadWriteLockCount); // allow ReadWriteLock to be reentrant
|
|
exit;
|
|
end;
|
|
// if not writing, atomically acquire the upgradable RD flag bit
|
|
spin := SPIN_COUNT;
|
|
repeat
|
|
f := Flags and not 3; // bit 0=WriteLock, 1=ReadWriteLock, >1=ReadOnlyLock
|
|
if (Flags = f) and
|
|
LockedExc(Flags, f + 2, f) then
|
|
break;
|
|
spin := DoSpin(spin);
|
|
until false;
|
|
LastReadWriteLockThread := tid;
|
|
LastReadWriteLockCount := 0;
|
|
end;
|
|
|
|
procedure TRWLock.ReadWriteUnLock;
|
|
begin
|
|
if LastReadWriteLockCount <> 0 then
|
|
begin
|
|
dec(LastReadWriteLockCount);
|
|
exit;
|
|
end;
|
|
LastReadWriteLockThread := TThreadID(0);
|
|
LockedDec(Flags, 2);
|
|
end;
|
|
|
|
procedure TRWLock.WriteLock;
|
|
var
|
|
spin, f: PtrUInt;
|
|
tid: TThreadID;
|
|
begin
|
|
tid := GetCurrentThreadId;
|
|
if (Flags and 1 = 1) and
|
|
(LastWriteLockThread = tid) then
|
|
begin
|
|
inc(LastWriteLockCount); // allow WriteLock to be reentrant
|
|
exit;
|
|
end;
|
|
spin := SPIN_COUNT;
|
|
// acquire the WR flag bit
|
|
repeat
|
|
f := Flags and not 1; // bit 0=WriteLock, 1=ReadWriteLock, >1=ReadOnlyLock
|
|
if (Flags = f) and
|
|
LockedExc(Flags, f + 1, f) then
|
|
if (Flags and 2 = 2) and
|
|
(LastReadWriteLockThread <> tid) then
|
|
// there is a pending ReadWriteLock but not on this thread
|
|
LockedDec(Flags, 1) // try again
|
|
else
|
|
// we exclusively acquired the WR lock
|
|
break;
|
|
spin := DoSpin(spin);
|
|
until false;
|
|
LastWriteLockThread := tid;
|
|
LastWriteLockCount := 0;
|
|
// wait for all readers to have finished their job
|
|
while Flags > 3 do
|
|
spin := DoSpin(spin);
|
|
end;
|
|
|
|
procedure TRWLock.WriteUnlock;
|
|
begin
|
|
if LastWriteLockCount <> 0 then
|
|
begin
|
|
dec(LastWriteLockCount); // reentrant call
|
|
exit;
|
|
end;
|
|
LastWriteLockThread := TThreadID(0);
|
|
LockedDec(Flags, 1);
|
|
end;
|
|
|
|
procedure TRWLock.Lock(context: TRWLockContext);
|
|
begin
|
|
if context = cReadOnly then
|
|
ReadOnlyLock
|
|
else if context = cReadWrite then
|
|
ReadWriteLock
|
|
else
|
|
WriteLock;
|
|
end;
|
|
|
|
procedure TRWLock.UnLock(context: TRWLockContext);
|
|
begin
|
|
if context = cReadOnly then
|
|
ReadOnlyUnLock
|
|
else if context = cReadWrite then
|
|
ReadWriteUnLock
|
|
else
|
|
WriteUnLock;
|
|
end;
|
|
|
|
|
|
{ TOSLock }
|
|
|
|
procedure TOSLock.Init;
|
|
begin
|
|
mormot.core.os.InitializeCriticalSection(CS);
|
|
end;
|
|
|
|
procedure TOSLock.Done;
|
|
begin
|
|
DeleteCriticalSectionIfNeeded(CS);
|
|
end;
|
|
|
|
procedure TOSLock.Lock;
|
|
begin
|
|
mormot.core.os.EnterCriticalSection(CS);
|
|
end;
|
|
|
|
function TOSLock.TryLock: boolean;
|
|
begin
|
|
result := mormot.core.os.TryEnterCriticalSection(CS) <> 0;
|
|
end;
|
|
|
|
procedure TOSLock.UnLock;
|
|
begin
|
|
mormot.core.os.LeaveCriticalSection(CS);
|
|
end;
|
|
|
|
|
|
{ TLockedList }
|
|
|
|
procedure TLockedList.Init(onesize: PtrUInt; const onefree: TOnLockedListOne);
|
|
begin
|
|
FillCharFast(self, SizeOf(self), 0);
|
|
fSize := onesize;
|
|
fOnFree := onefree;
|
|
fSequence := (Random32 shr 2) + 65536; // 65535 < sequence < MaxInt
|
|
end;
|
|
|
|
function LockedListFreeAll(o: PLockedListOne; const OnFree: TOnLockedListOne): integer;
|
|
var
|
|
next: PLockedListOne;
|
|
begin
|
|
result := 0;
|
|
while o <> nil do
|
|
begin
|
|
inc(result);
|
|
next := o.next;
|
|
if Assigned(OnFree) then
|
|
OnFree(o);
|
|
FreeMem(o);
|
|
o := next;
|
|
end;
|
|
end;
|
|
|
|
procedure TLockedList.Done;
|
|
begin
|
|
Clear;
|
|
EmptyBin;
|
|
end;
|
|
|
|
procedure TLockedList.Clear;
|
|
begin
|
|
Safe.Lock;
|
|
try
|
|
LockedListFreeAll(fHead, fOnFree);
|
|
fHead := nil;
|
|
Count := 0;
|
|
finally
|
|
Safe.UnLock;
|
|
end;
|
|
end;
|
|
|
|
function TLockedList.EmptyBin: integer;
|
|
begin
|
|
Safe.Lock;
|
|
try
|
|
result := LockedListFreeAll(fBin, nil);
|
|
fBin := nil;
|
|
finally
|
|
Safe.UnLock;
|
|
end;
|
|
end;
|
|
|
|
function TLockedList.New: pointer;
|
|
begin
|
|
Safe.Lock;
|
|
try
|
|
// try to recycle from single-linked list bin, or allocate
|
|
result := fBin;
|
|
if result <> nil then
|
|
fBin := PLockedListOne(result).next
|
|
else
|
|
result := AllocMem(fSize);
|
|
PLockedListOne(result).sequence := fSequence;
|
|
inc(fSequence); // protected by Safe.Lock
|
|
// insert at beginning of the main double-linked list
|
|
PLockedListOne(result).next := fHead;
|
|
if fHead <> nil then
|
|
PLockedListOne(fHead).prev := result;
|
|
fHead := result;
|
|
inc(Count);
|
|
finally
|
|
Safe.UnLock;
|
|
end;
|
|
end;
|
|
|
|
function TLockedList.Free(one: pointer): boolean;
|
|
var
|
|
o: PLockedListOne absolute one;
|
|
begin
|
|
result := false;
|
|
if (o = nil) or
|
|
(o^.sequence = 0) then
|
|
exit;
|
|
Safe.Lock;
|
|
try
|
|
// remove from main double-linked list
|
|
if o = fHead then
|
|
fHead := o.next;
|
|
if o.next <> nil then
|
|
PLockedListOne(o.next).prev := o.prev;
|
|
if o.prev <> nil then
|
|
PLockedListOne(o.prev).next := o.next;
|
|
// release internals and add to the recycle bin
|
|
if Assigned(fOnFree) then
|
|
fOnFree(o);
|
|
FillCharFast(o^, fSize, 0); // garbage collect as void
|
|
o.next := fBin;
|
|
fBin := o;
|
|
dec(Count);
|
|
finally
|
|
Safe.UnLock;
|
|
end;
|
|
result := true;
|
|
end;
|
|
|
|
|
|
{ TAutoLock }
|
|
|
|
constructor TAutoLock.Create(aLock: PSynLocker);
|
|
begin
|
|
fLock := aLock;
|
|
fLock^.Lock;
|
|
end;
|
|
|
|
destructor TAutoLock.Destroy;
|
|
begin
|
|
fLock^.UnLock;
|
|
end;
|
|
|
|
|
|
{ TSynLocker }
|
|
|
|
function NewSynLocker: PSynLocker;
|
|
begin
|
|
result := AllocMem(SizeOf(TSynLocker));
|
|
InitializeCriticalSection(result^.fSection);
|
|
result^.fInitialized := true;
|
|
end;
|
|
|
|
procedure TSynLocker.Init;
|
|
begin
|
|
InitializeCriticalSection(fSection);
|
|
fLockCount := 0;
|
|
fPaddingUsedCount := 0;
|
|
fInitialized := true;
|
|
fRW.Init;
|
|
end;
|
|
|
|
procedure TSynLocker.Done;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
for i := 0 to fPaddingUsedCount - 1 do
|
|
if not (integer(Padding[i].VType) in VTYPE_SIMPLE) then
|
|
VarClearProc(Padding[i]);
|
|
DeleteCriticalSection(fSection);
|
|
fInitialized := false;
|
|
end;
|
|
|
|
procedure TSynLocker.DoneAndFreeMem;
|
|
begin
|
|
Done;
|
|
FreeMem(@self);
|
|
end;
|
|
|
|
function TSynLocker.GetIsLocked: boolean;
|
|
begin
|
|
case fRWUse of
|
|
uSharedLock:
|
|
result := fLockCount <> 0; // only updated by uSharedLock
|
|
uRWLock:
|
|
result := fRW.Flags = 0; // no lock at all
|
|
else
|
|
result := false; // uNoLock will never lock
|
|
end;
|
|
end;
|
|
|
|
procedure TSynLocker.RWLock(context: TRWLockContext);
|
|
begin
|
|
case fRWUse of
|
|
uSharedLock:
|
|
begin
|
|
mormot.core.os.EnterCriticalSection(fSection);
|
|
inc(fLockCount);
|
|
end;
|
|
uRWLock:
|
|
fRW.Lock(context);
|
|
end; // uNoLock will just do nothing
|
|
end;
|
|
|
|
procedure TSynLocker.RWUnLock(context: TRWLockContext);
|
|
begin
|
|
case fRWUse of
|
|
uSharedLock:
|
|
begin
|
|
dec(fLockCount);
|
|
mormot.core.os.LeaveCriticalSection(fSection);
|
|
end;
|
|
uRWLock:
|
|
fRW.UnLock(context);
|
|
end; // uNoLock will just do nothing
|
|
end;
|
|
|
|
procedure TSynLocker.ReadLock;
|
|
begin
|
|
RWLock(cReadOnly); // will be properly inlined
|
|
end;
|
|
|
|
procedure TSynLocker.ReadUnLock;
|
|
begin
|
|
RWUnLock(cReadOnly);
|
|
end;
|
|
|
|
procedure TSynLocker.ReadWriteLock;
|
|
begin
|
|
RWLock(cReadWrite);
|
|
end;
|
|
|
|
procedure TSynLocker.ReadWriteUnLock;
|
|
begin
|
|
RWUnLock(cReadWrite);
|
|
end;
|
|
|
|
procedure TSynLocker.Lock;
|
|
begin
|
|
RWLock(cWrite);
|
|
end;
|
|
|
|
procedure TSynLocker.UnLock;
|
|
begin
|
|
RWUnLock(cWrite);
|
|
end;
|
|
|
|
function TSynLocker.TryLock: boolean;
|
|
begin
|
|
result := (fRWUse = uSharedLock) and
|
|
(mormot.core.os.TryEnterCriticalSection(fSection) <> 0);
|
|
if result then
|
|
inc(fLockCount);
|
|
end;
|
|
|
|
function TSynLocker.TryLockMS(retryms: integer; terminated: PBoolean): boolean;
|
|
var
|
|
ms: integer;
|
|
endtix: Int64;
|
|
begin
|
|
result := TryLock;
|
|
if result or
|
|
(fRWUse <> uSharedLock) or
|
|
(retryms <= 0) then
|
|
exit;
|
|
ms := 0;
|
|
endtix := GetTickCount64 + retryms;
|
|
repeat
|
|
SleepHiRes(ms);
|
|
result := TryLock;
|
|
if result or
|
|
((terminated <> nil) and
|
|
terminated^) then
|
|
exit;
|
|
ms := ms xor 1; // 0,1,0,1... seems to be good for scaling
|
|
until GetTickCount64 > endtix;
|
|
end;
|
|
|
|
function TSynLocker.ProtectMethod: IUnknown;
|
|
begin
|
|
result := TAutoLock.Create(@self);
|
|
end;
|
|
|
|
function TSynLocker.GetVariant(Index: integer): Variant;
|
|
begin
|
|
if cardinal(Index) < cardinal(fPaddingUsedCount) then
|
|
{$ifdef HASFASTTRYFINALLY}
|
|
try
|
|
{$else}
|
|
begin
|
|
{$endif HASFASTTRYFINALLY}
|
|
RWLock(cReadOnly);
|
|
result := variant(Padding[Index]);
|
|
{$ifdef HASFASTTRYFINALLY}
|
|
finally
|
|
{$endif HASFASTTRYFINALLY}
|
|
RWUnLock(cReadOnly);
|
|
end
|
|
else
|
|
VarClear(result);
|
|
end;
|
|
|
|
procedure TSynLocker.SetVariant(Index: integer; const Value: Variant);
|
|
begin
|
|
if cardinal(Index) <= high(Padding) then
|
|
try
|
|
RWLock(cWrite);
|
|
if Index >= fPaddingUsedCount then
|
|
fPaddingUsedCount := Index + 1;
|
|
variant(Padding[Index]) := Value;
|
|
finally
|
|
RWUnLock(cWrite);
|
|
end;
|
|
end;
|
|
|
|
function TSynLocker.GetInt64(Index: integer): Int64;
|
|
begin
|
|
if cardinal(Index) < cardinal(fPaddingUsedCount) then
|
|
{$ifdef HASFASTTRYFINALLY}
|
|
try
|
|
{$else}
|
|
begin
|
|
{$endif HASFASTTRYFINALLY}
|
|
RWLock(cReadOnly);
|
|
if not VariantToInt64(variant(Padding[Index]), result) then
|
|
result := 0;
|
|
{$ifdef HASFASTTRYFINALLY}
|
|
finally
|
|
{$endif HASFASTTRYFINALLY}
|
|
RWUnLock(cReadOnly);
|
|
end
|
|
else
|
|
result := 0;
|
|
end;
|
|
|
|
procedure TSynLocker.SetInt64(Index: integer; const Value: Int64);
|
|
begin
|
|
SetVariant(Index, Value);
|
|
end;
|
|
|
|
function TSynLocker.GetBool(Index: integer): boolean;
|
|
begin
|
|
if cardinal(Index) < cardinal(fPaddingUsedCount) then
|
|
{$ifdef HASFASTTRYFINALLY}
|
|
try
|
|
{$else}
|
|
begin
|
|
{$endif HASFASTTRYFINALLY}
|
|
RWLock(cReadOnly);
|
|
if not VariantToBoolean(variant(Padding[Index]), result) then
|
|
result := false;
|
|
{$ifdef HASFASTTRYFINALLY}
|
|
finally
|
|
{$endif HASFASTTRYFINALLY}
|
|
RWUnLock(cReadOnly);
|
|
end
|
|
else
|
|
result := false;
|
|
end;
|
|
|
|
procedure TSynLocker.SetBool(Index: integer; const Value: boolean);
|
|
begin
|
|
SetVariant(Index, Value);
|
|
end;
|
|
|
|
function TSynLocker.GetUnlockedInt64(Index: integer): Int64;
|
|
begin
|
|
if (cardinal(Index) >= cardinal(fPaddingUsedCount)) or
|
|
not VariantToInt64(variant(Padding[Index]), result) then
|
|
result := 0;
|
|
end;
|
|
|
|
procedure TSynLocker.SetUnlockedInt64(Index: integer; const Value: Int64);
|
|
begin
|
|
if cardinal(Index) <= high(Padding) then
|
|
begin
|
|
if Index >= fPaddingUsedCount then
|
|
fPaddingUsedCount := Index + 1;
|
|
variant(Padding[Index]) := Value;
|
|
end;
|
|
end;
|
|
|
|
function TSynLocker.GetPointer(Index: integer): Pointer;
|
|
begin
|
|
if cardinal(Index) < cardinal(fPaddingUsedCount) then
|
|
{$ifdef HASFASTTRYFINALLY}
|
|
try
|
|
{$else}
|
|
begin
|
|
{$endif HASFASTTRYFINALLY}
|
|
RWLock(cReadOnly);
|
|
with Padding[Index] do
|
|
if VType = varUnknown then
|
|
result := VUnknown
|
|
else
|
|
result := nil;
|
|
{$ifdef HASFASTTRYFINALLY}
|
|
finally
|
|
{$endif HASFASTTRYFINALLY}
|
|
RWUnLock(cReadOnly);
|
|
end
|
|
else
|
|
result := nil;
|
|
end;
|
|
|
|
procedure TSynLocker.SetPointer(Index: integer; const Value: Pointer);
|
|
begin
|
|
if cardinal(Index) <= high(Padding) then
|
|
try
|
|
RWLock(cWrite);
|
|
if Index >= fPaddingUsedCount then
|
|
fPaddingUsedCount := Index + 1;
|
|
with Padding[Index] do
|
|
begin
|
|
VarClearAndSetType(PVariant(@VType)^, varUnknown);
|
|
VUnknown := Value;
|
|
end;
|
|
finally
|
|
RWUnLock(cWrite);
|
|
end;
|
|
end;
|
|
|
|
function TSynLocker.GetUtf8(Index: integer): RawUtf8;
|
|
begin
|
|
if cardinal(Index) < cardinal(fPaddingUsedCount) then
|
|
{$ifdef HASFASTTRYFINALLY}
|
|
try
|
|
{$else}
|
|
begin
|
|
{$endif HASFASTTRYFINALLY}
|
|
RWLock(cReadOnly);
|
|
VariantStringToUtf8(variant(Padding[Index]), result);
|
|
{$ifdef HASFASTTRYFINALLY}
|
|
finally
|
|
{$endif HASFASTTRYFINALLY}
|
|
RWUnLock(cReadOnly);
|
|
end
|
|
else
|
|
result := '';
|
|
end;
|
|
|
|
procedure TSynLocker.SetUtf8(Index: integer; const Value: RawUtf8);
|
|
begin
|
|
if cardinal(Index) <= high(Padding) then
|
|
try
|
|
RWLock(cWrite);
|
|
if Index >= fPaddingUsedCount then
|
|
fPaddingUsedCount := Index + 1;
|
|
RawUtf8ToVariant(Value, variant(Padding[Index]));
|
|
finally
|
|
RWUnLock(cWrite);
|
|
end;
|
|
end;
|
|
|
|
function TSynLocker.LockedInt64Increment(Index: integer; const Increment: Int64): Int64;
|
|
begin
|
|
if cardinal(Index) <= high(Padding) then
|
|
try
|
|
RWLock(cWrite);
|
|
result := 0;
|
|
if Index < fPaddingUsedCount then
|
|
VariantToInt64(variant(Padding[Index]), result)
|
|
else
|
|
fPaddingUsedCount := Index + 1;
|
|
variant(Padding[Index]) := Int64(result + Increment);
|
|
finally
|
|
RWUnLock(cWrite);
|
|
end
|
|
else
|
|
result := 0;
|
|
end;
|
|
|
|
function TSynLocker.LockedExchange(Index: integer; const Value: variant): variant;
|
|
begin
|
|
VarClear(result);
|
|
if cardinal(Index) <= high(Padding) then
|
|
try
|
|
RWLock(cWrite);
|
|
with Padding[Index] do
|
|
begin
|
|
if Index < fPaddingUsedCount then
|
|
result := PVariant(@VType)^
|
|
else
|
|
fPaddingUsedCount := Index + 1;
|
|
PVariant(@VType)^ := Value;
|
|
end;
|
|
finally
|
|
RWUnLock(cWrite);
|
|
end;
|
|
end;
|
|
|
|
function TSynLocker.LockedPointerExchange(Index: integer; Value: pointer): pointer;
|
|
begin
|
|
if cardinal(Index) <= high(Padding) then
|
|
try
|
|
RWLock(cWrite);
|
|
with Padding[Index] do
|
|
begin
|
|
if Index < fPaddingUsedCount then
|
|
if VType = varUnknown then
|
|
result := VUnknown
|
|
else
|
|
begin
|
|
VarClear(PVariant(@VType)^);
|
|
result := nil;
|
|
end
|
|
else
|
|
begin
|
|
fPaddingUsedCount := Index + 1;
|
|
result := nil;
|
|
end;
|
|
VType := varUnknown;
|
|
VUnknown := Value;
|
|
end;
|
|
finally
|
|
RWUnLock(cWrite);
|
|
end
|
|
else
|
|
result := nil;
|
|
end;
|
|
|
|
|
|
|
|
{ TSynLocked }
|
|
|
|
constructor TSynLocked.Create;
|
|
begin
|
|
fSafe := NewSynLocker;
|
|
end;
|
|
|
|
destructor TSynLocked.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
fSafe^.DoneAndFreeMem;
|
|
end;
|
|
|
|
|
|
{ TSynEvent }
|
|
|
|
function TSynEvent.SleepStep(var start: Int64; terminated: PBoolean): Int64;
|
|
var
|
|
ms: integer;
|
|
endtix: Int64;
|
|
begin
|
|
ms := SleepStepTime(start, result, @endtix);
|
|
if (ms < 10) or
|
|
(terminated = nil) then
|
|
if ms = 0 then
|
|
SleepHiRes(0) // < 16 ms is a pious wish on Windows anyway
|
|
else
|
|
WaitFor(ms)
|
|
else
|
|
repeat
|
|
WaitFor(10);
|
|
if terminated^ then
|
|
exit;
|
|
result := GetTickCount64;
|
|
until result >= endtix;
|
|
end;
|
|
|
|
function TSynEvent.IsEventFD: boolean;
|
|
begin
|
|
{$ifdef HASEVENTFD}
|
|
result := fFD <> 0;
|
|
{$else}
|
|
result := false;
|
|
{$endif HASEVENTFD}
|
|
end;
|
|
|
|
|
|
{ TLecuyerThreadSafe }
|
|
|
|
function TLecuyerThreadSafe.Next: cardinal;
|
|
begin
|
|
Safe.Lock;
|
|
result := Generator.Next;
|
|
Safe.UnLock;
|
|
end;
|
|
|
|
function TLecuyerThreadSafe.NextDouble: double;
|
|
begin
|
|
Safe.Lock;
|
|
result := Generator.NextDouble;
|
|
Safe.UnLock;
|
|
end;
|
|
|
|
procedure TLecuyerThreadSafe.Fill(dest: pointer; count: integer);
|
|
begin
|
|
Safe.Lock;
|
|
Generator.Fill(dest, count);
|
|
Safe.UnLock;
|
|
end;
|
|
|
|
procedure TLecuyerThreadSafe.FillShort31(var dest: TShort31);
|
|
begin
|
|
Fill(@dest, 32);
|
|
FillAnsiStringFromRandom(@dest, 32);
|
|
end;
|
|
|
|
|
|
procedure GlobalLock;
|
|
begin
|
|
mormot.core.os.EnterCriticalSection(GlobalCriticalSection.CS);
|
|
end;
|
|
|
|
procedure GlobalUnLock;
|
|
begin
|
|
mormot.core.os.LeaveCriticalSection(GlobalCriticalSection.CS);
|
|
end;
|
|
|
|
var
|
|
InternalGarbageCollection: record // RegisterGlobalShutdownRelease() list
|
|
Instances: TObjectDynArray;
|
|
Count: integer;
|
|
Shutdown: boolean; // paranoid check to avoid messing with Instances[]
|
|
end;
|
|
|
|
function RegisterGlobalShutdownRelease(Instance: TObject;
|
|
SearchExisting: boolean): pointer;
|
|
begin
|
|
if not InternalGarbageCollection.Shutdown then
|
|
begin
|
|
GlobalLock;
|
|
try
|
|
with InternalGarbageCollection do
|
|
if not SearchExisting or
|
|
not PtrUIntScanExists(pointer(Instances), Count, PtrUInt(Instance)) then
|
|
PtrArrayAdd(Instances, Instance, Count);
|
|
finally
|
|
GlobalUnLock;
|
|
end;
|
|
end;
|
|
result := Instance;
|
|
end;
|
|
|
|
function SleepDelay(elapsed: PtrInt): PtrInt;
|
|
begin
|
|
if elapsed < 50 then
|
|
result := 0 // 10us on POSIX, SwitchToThread on Windows
|
|
else if elapsed < 200 then
|
|
result := 1
|
|
else if elapsed < 500 then
|
|
result := 5
|
|
else if elapsed < 2000 then
|
|
result := 50
|
|
else
|
|
result := 120 + Random32(130); // random 120-250 ms
|
|
end;
|
|
|
|
function SleepStepTime(var start, tix: Int64; endtix: PInt64): PtrInt;
|
|
begin
|
|
tix := GetTickCount64;
|
|
if (start = 0) or
|
|
(tix < 50) then
|
|
start := tix
|
|
else if start < 0 then
|
|
start := tix - 50; // ensure tix - start = elapsed is not < 50
|
|
result := SleepDelay(tix - start);
|
|
if endtix <> nil then
|
|
endtix^ := tix + result;
|
|
end;
|
|
|
|
function SleepStep(var start: Int64; terminated: PBoolean): Int64;
|
|
var
|
|
ms: integer;
|
|
endtix: Int64;
|
|
begin
|
|
ms := SleepStepTime(start, result, @endtix);
|
|
if (ms < 10) or
|
|
(terminated = nil) then
|
|
SleepHiRes(ms) // < 16 ms is a pious wish on Windows anyway
|
|
else
|
|
repeat
|
|
SleepHiRes(10); // on Windows, HW clock resolution is around 16 ms
|
|
result := GetTickCount64;
|
|
until (ms = 0) or
|
|
terminated^ or
|
|
(result >= endtix);
|
|
end;
|
|
|
|
function SleepHiRes(ms: cardinal; var terminated: boolean;
|
|
terminatedvalue: boolean): boolean;
|
|
var
|
|
start, endtix: Int64;
|
|
begin
|
|
if terminated <> terminatedvalue then
|
|
if ms < 20 then
|
|
SleepHiRes(ms) // below HW clock resolution
|
|
else
|
|
begin
|
|
start := GetTickCount64;
|
|
endtix := start + ms;
|
|
repeat
|
|
until (terminated = terminatedvalue) or
|
|
(SleepStep(start, @terminated) > endtix);
|
|
end;
|
|
result := terminated = terminatedvalue;
|
|
end;
|
|
|
|
procedure SpinExc(var Target: PtrUInt; NewValue, Comperand: PtrUInt);
|
|
var
|
|
spin: PtrUInt;
|
|
begin
|
|
spin := SPIN_COUNT;
|
|
while (Target <> Comperand) or
|
|
not LockedExc(Target, NewValue, Comperand) do
|
|
spin := DoSpin(spin);
|
|
end;
|
|
|
|
function ObjArrayAdd(var aObjArray; aItem: TObject;
|
|
var aSafe: TLightLock; aCount: PInteger): PtrInt;
|
|
begin
|
|
aSafe.Lock;
|
|
if aCount <> nil then
|
|
result := PtrArrayAdd(aObjArray, aItem, aCount^)
|
|
else
|
|
result := PtrArrayAdd(aObjArray, aItem);
|
|
aSafe.UnLock;
|
|
end;
|
|
|
|
function PtrArrayDelete(var aPtrArray; aItem: pointer; var aSafe: TLightLock;
|
|
aCount: PInteger): PtrInt;
|
|
begin
|
|
if pointer(aPtrArray) = nil then
|
|
begin
|
|
result := -1; // no need to lock anything
|
|
exit;
|
|
end;
|
|
aSafe.Lock;
|
|
result := PtrArrayDelete(aPtrArray, aItem, aCount);
|
|
aSafe.UnLock;
|
|
end;
|
|
|
|
function SetCpuSet(var CpuSet: TCpuSet; CpuIndex: cardinal): boolean;
|
|
begin
|
|
result := false;
|
|
if (CpuIndex >= SizeOf(CpuSet) shl 3) or
|
|
(CpuIndex >= SystemInfo.dwNumberOfProcessors) then
|
|
exit;
|
|
SetBitPtr(@CpuSet, CpuIndex);
|
|
result := true;
|
|
end;
|
|
|
|
function CurrentCpuSet(out CpuSet: TCpuSet): integer;
|
|
begin
|
|
ResetCpuSet(CpuSet);
|
|
if GetMaskAffinity(CpuSet) then
|
|
result := GetBitsCount(CpuSet, SizeOf(CpuSet) shl 3)
|
|
else
|
|
result := 0;
|
|
end;
|
|
|
|
function SetThreadCpuAffinity(Thread: TThread; CpuIndex: cardinal): boolean;
|
|
var
|
|
mask: TCpuSet;
|
|
begin
|
|
ResetCpuSet(mask);
|
|
result := SetCpuSet(mask, CpuIndex) and
|
|
SetThreadMaskAffinity(Thread, mask);
|
|
end;
|
|
|
|
function SetThreadSocketAffinity(Thread: TThread; SocketIndex: cardinal): boolean;
|
|
begin
|
|
result := (SocketIndex < cardinal(length(CpuSocketsMask))) and
|
|
SetThreadMaskAffinity(Thread, CpuSocketsMask[SocketIndex]);
|
|
end;
|
|
|
|
procedure _SetThreadName(ThreadID: TThreadID; const Format: RawUtf8;
|
|
const Args: array of const);
|
|
begin
|
|
// do nothing - properly implemented in mormot.core.log
|
|
end;
|
|
|
|
procedure SetCurrentThreadName(const Format: RawUtf8; const Args: array of const);
|
|
begin
|
|
SetThreadName(GetCurrentThreadId, Format, Args);
|
|
end;
|
|
|
|
procedure SetCurrentThreadName(const Name: RawUtf8);
|
|
begin
|
|
SetThreadName(GetCurrentThreadId, '%', [Name]);
|
|
end;
|
|
|
|
threadvar // do not publish for compilation within Delphi packages
|
|
_CurrentThreadName: TShort31; // 31 chars is enough for our debug purpose
|
|
|
|
function CurrentThreadNameShort: PShortString;
|
|
begin
|
|
result := @_CurrentThreadName;
|
|
end;
|
|
|
|
function GetCurrentThreadName: RawUtf8;
|
|
begin
|
|
ShortStringToAnsi7String(_CurrentThreadName, result);
|
|
end;
|
|
|
|
function GetCurrentThreadInfo: ShortString;
|
|
begin
|
|
result := ShortString(format('Thread %x [%s]',
|
|
[PtrUInt(GetCurrentThreadId), _CurrentThreadName]));
|
|
end;
|
|
|
|
|
|
{ ****************** Unix Daemon and Windows Service Support }
|
|
|
|
const
|
|
// hardcoded to avoid linking mormot.core.rtti for GetEnumName()
|
|
_SERVICESTATE: array[TServiceState] of string[12] = (
|
|
'NotInstalled',
|
|
'Stopped',
|
|
'Starting',
|
|
'Stopping',
|
|
'Running',
|
|
'Resuming',
|
|
'Pausing',
|
|
'Paused',
|
|
'Failed',
|
|
'Error');
|
|
|
|
function ToText(st: TServiceState): PShortString; overload;
|
|
begin
|
|
result := @_SERVICESTATE[st];
|
|
end;
|
|
|
|
function ExtractExecutableName(const cmd: RawUtf8; posix: boolean): RawUtf8;
|
|
var
|
|
temp: RawUtf8;
|
|
argv: TParseCommandsArgs;
|
|
argc: integer;
|
|
begin
|
|
if (pcInvalidCommand in ParseCommandArgs(cmd, @argv, @argc, @temp, posix)) or
|
|
({%H-}argc = 0) then
|
|
result := ''
|
|
else
|
|
FastSetString(result, argv[0], StrLen(argv[0]));
|
|
end;
|
|
|
|
function ParseCommandArgs(const cmd: RawUtf8; argv: PParseCommandsArgs;
|
|
argc: PInteger; temp: PRawUtf8; posix: boolean): TParseCommands;
|
|
var
|
|
n: PtrInt;
|
|
state: set of (sWhite, sInArg, sInSQ, sInDQ, sSpecial, sBslash);
|
|
c: AnsiChar;
|
|
D, P: PAnsiChar;
|
|
begin
|
|
result := [pcInvalidCommand];
|
|
if argv <> nil then
|
|
argv[0] := nil;
|
|
if argc <> nil then
|
|
argc^ := 0;
|
|
if cmd = '' then
|
|
exit;
|
|
if argv = nil then
|
|
D := nil
|
|
else
|
|
begin
|
|
if temp = nil then
|
|
exit;
|
|
SetLength(temp^, length(cmd));
|
|
D := pointer(temp^);
|
|
end;
|
|
state := [];
|
|
n := 0;
|
|
P := pointer(cmd);
|
|
repeat
|
|
c := P^;
|
|
if D <> nil then
|
|
D^ := c;
|
|
inc(P);
|
|
case c of
|
|
#0:
|
|
begin
|
|
if sInSQ in state then
|
|
include(result, pcUnbalancedSingleQuote);
|
|
if sInDQ in state then
|
|
include(result, pcUnbalancedDoubleQuote);
|
|
exclude(result, pcInvalidCommand);
|
|
if argv <> nil then
|
|
argv[n] := nil;
|
|
if argc <> nil then
|
|
argc^ := n;
|
|
exit;
|
|
end;
|
|
#1 .. ' ':
|
|
begin
|
|
if state = [sInArg] then
|
|
begin
|
|
state := [];
|
|
if D <> nil then
|
|
begin
|
|
D^ := #0;
|
|
inc(D);
|
|
end;
|
|
continue;
|
|
end;
|
|
if state * [sInSQ, sInDQ] = [] then
|
|
continue;
|
|
end;
|
|
'\':
|
|
if posix and
|
|
(state * [sInSQ, sBslash] = []) then
|
|
if sInDQ in state then
|
|
begin
|
|
case P^ of
|
|
'"', '\', '$', '`':
|
|
begin
|
|
include(state, sBslash);
|
|
continue;
|
|
end;
|
|
end;
|
|
end
|
|
else if P^ = #0 then
|
|
begin
|
|
include(result, pcHasEndingBackSlash);
|
|
exit;
|
|
end
|
|
else
|
|
begin
|
|
if D <> nil then
|
|
D^ := P^;
|
|
inc(P);
|
|
end;
|
|
'^':
|
|
if not posix and
|
|
(state * [sInSQ, sInDQ, sBslash] = []) then
|
|
if PWord(P)^ = $0a0d then
|
|
begin
|
|
inc(P, 2);
|
|
continue;
|
|
end
|
|
else if P^ = #0 then
|
|
begin
|
|
include(result, pcHasEndingBackSlash);
|
|
exit;
|
|
end
|
|
else
|
|
begin
|
|
if D <> nil then
|
|
D^ := P^;
|
|
inc(P);
|
|
end;
|
|
'''':
|
|
if posix and
|
|
not(sInDQ in state) then
|
|
if sInSQ in state then
|
|
begin
|
|
exclude(state, sInSQ);
|
|
continue;
|
|
end
|
|
else if state = [] then
|
|
begin
|
|
if argv <> nil then
|
|
begin
|
|
argv[n] := D;
|
|
inc(n);
|
|
if n = high(argv^) then
|
|
exit;
|
|
end;
|
|
state := [sInSQ, sInArg];
|
|
continue;
|
|
end
|
|
else if state = [sInArg] then
|
|
begin
|
|
state := [sInSQ, sInArg];
|
|
continue;
|
|
end;
|
|
'"':
|
|
if not(sInSQ in state) then
|
|
if sInDQ in state then
|
|
begin
|
|
exclude(state, sInDQ);
|
|
continue;
|
|
end
|
|
else if state = [] then
|
|
begin
|
|
if argv <> nil then
|
|
begin
|
|
argv[n] := D;
|
|
inc(n);
|
|
if n = high(argv^) then
|
|
exit;
|
|
end;
|
|
state := [sInDQ, sInArg];
|
|
continue;
|
|
end
|
|
else if state = [sInArg] then
|
|
begin
|
|
state := [sInDQ, sInArg];
|
|
continue;
|
|
end;
|
|
'|',
|
|
'<',
|
|
'>':
|
|
if state * [sInSQ, sInDQ] = [] then
|
|
include(result, pcHasRedirection);
|
|
'&',
|
|
';':
|
|
if posix and
|
|
(state * [sInSQ, sInDQ] = []) then
|
|
begin
|
|
include(state, sSpecial);
|
|
include(result, pcHasJobControl);
|
|
end;
|
|
'`':
|
|
if posix and
|
|
(state * [sInSQ, sBslash] = []) then
|
|
include(result, pcHasSubCommand);
|
|
'(',
|
|
')':
|
|
if posix and
|
|
(state * [sInSQ, sInDQ] = []) then
|
|
include(result, pcHasParenthesis);
|
|
'$':
|
|
if posix and
|
|
(state * [sInSQ, sBslash] = []) then
|
|
if p^ = '(' then
|
|
include(result, pcHasSubCommand)
|
|
else
|
|
include(result, pcHasShellVariable);
|
|
'*',
|
|
'?':
|
|
if posix and
|
|
(state * [sInSQ, sInDQ] = []) then
|
|
include(result, pcHasWildcard);
|
|
end;
|
|
exclude(state, sBslash);
|
|
if state = [] then
|
|
begin
|
|
if argv <> nil then
|
|
begin
|
|
argv[n] := D;
|
|
inc(n);
|
|
if n = high(argv^) then
|
|
exit;
|
|
end;
|
|
state := [sInArg];
|
|
end;
|
|
if D <> nil then
|
|
inc(D);
|
|
until false;
|
|
end;
|
|
|
|
procedure TrimDualSpaces(var s: RawUtf8);
|
|
var
|
|
f, i: integer;
|
|
begin
|
|
f := 1;
|
|
repeat
|
|
i := PosEx(' ', s, f);
|
|
if i = 0 then
|
|
break;
|
|
delete(s, i, 1); // dual space -> single space
|
|
f := i;
|
|
until false;
|
|
TrimSelf(s);
|
|
end;
|
|
|
|
|
|
procedure InitializeUnit;
|
|
begin
|
|
{$ifdef ISFPC27}
|
|
SetMultiByteConversionCodePage(CP_UTF8);
|
|
SetMultiByteRTLFileSystemCodePage(CP_UTF8);
|
|
{$endif ISFPC27}
|
|
GlobalCriticalSection.Init;
|
|
ConsoleCriticalSection.Init;
|
|
InitializeSpecificUnit; // in mormot.core.os.posix/windows.inc files
|
|
TrimDualSpaces(OSVersionText);
|
|
TrimDualSpaces(OSVersionInfoEx);
|
|
TrimDualSpaces(BiosInfoText);
|
|
TrimDualSpaces(CpuInfoText);
|
|
OSVersionShort := ToTextOS(OSVersionInt32);
|
|
InitializeExecutableInformation;
|
|
JSON_CONTENT_TYPE_VAR := JSON_CONTENT_TYPE;
|
|
JSON_CONTENT_TYPE_HEADER_VAR := JSON_CONTENT_TYPE_HEADER;
|
|
NULL_STR_VAR := 'null';
|
|
BOOL_UTF8[false] := 'false';
|
|
BOOL_UTF8[true] := 'true';
|
|
// minimal stubs which will be properly implemented in mormot.core.log.pas
|
|
GetExecutableLocation := _GetExecutableLocation;
|
|
SetThreadName := _SetThreadName;
|
|
end;
|
|
|
|
procedure FinalizeUnit;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
with InternalGarbageCollection do
|
|
begin
|
|
Shutdown := true; // avoid nested initialization at shutdown
|
|
for i := Count - 1 downto 0 do
|
|
FreeAndNilSafe(Instances[i]); // before GlobalCriticalSection deletion
|
|
end;
|
|
ObjArrayClear(CurrentFakeStubBuffers);
|
|
Executable.Version.Free;
|
|
Executable.Command.Free;
|
|
FinalizeSpecificUnit; // in mormot.core.os.posix/windows.inc files
|
|
ConsoleCriticalSection.Done;
|
|
GlobalCriticalSection.Done;
|
|
{$ifndef NOEXCEPTIONINTERCEPT}
|
|
_RawLogException := nil;
|
|
RawExceptionIntercepted := true;
|
|
{$endif NOEXCEPTIONINTERCEPT}
|
|
end;
|
|
|
|
|
|
initialization
|
|
InitializeUnit;
|
|
|
|
finalization
|
|
FinalizeUnit;
|
|
|
|
end.
|
|
|