/// 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 }

type
  /// allow to customize the possible line feeds
  TLineFeed = (
    lfSystem,
    lfCR,
    lfCRLF);

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}
  /// convert a TLineFeed value into its UTF-8 text representation
  LINE_FEED: array[TLineFeed] of string[3] = (CRLF, #10, #13#10);

  /// 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;

const // some time conversion constants with Milli/Micro/NanoSec resolution
  SecsPerHour  = SecsPerMin * MinsPerHour; // missing in oldest Delphi
  SecsPerDay   = SecsPerMin * MinsPerDay;
  SecsPerWeek  = 7 * SecsPerDay;
  SecsPerMonth = 2629746; // rough approximation of SecsPerDay * 365.2425 / 12
  SecsPerYear  = 12 * SecsPerMonth;

  MilliSecsPerSec      = 1000;
  MilliSecsPerSecShl   = 10; // 1 shl 10 = 1024 = rough approximation of 1000
  MilliSecsPerMin      = MilliSecsPerSec  * SecsPerMin;
  MilliSecsPerHour     = MilliSecsPerMin  * MinsPerHour;
  MilliSecsPerDay      = MilliSecsPerHour * HoursPerDay;
  MicroSecsPerMilliSec = 1000;
  MicroSecsPerSec      = MicroSecsPerMilliSec * MilliSecsPerSec;
  NanoSecsPerMicroSec  = 1000;
  NanoSecsPerMilliSec  = NanoSecsPerMicroSec  * MicroSecsPerMilliSec;
  NanoSecsPerSec       = NanoSecsPerMilliSec  * MilliSecsPerSec;


{ ****************** 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():
    // ! c.RawParams := CsvToRawUtf8DynArray('-o file.txt --y -v -t 1', ' ');
    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}

type
  /// define how GetComputerUuid() computes its 128-bit UUID identifier
  // - set of potential sources, which may be excluded from computation
  TGetComputerUuid = set of (
    gcuSmbios,
    gcuSmbiosData,
    gcuSmbiosText,
    gcuCpuFeatures,
    gcuCpuInfoText,
    gcuBiosInfoText,
    gcuMacAddress);

/// 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 (if
// disable is void), 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
// - you can specify some HW sources to be ignored during the calculation
procedure GetComputerUuid(out uuid: TGuid; disable: TGetComputerUuid = []); overload;

/// retrieve a genuine 128-bit UUID identifier for this computer
// - returns GetSmbios(sbiUuid) if available
// - if no GetSmbios(sbiUuid) is available, will compute a genuine hash from HW
// and store it on a local file for the next access (if disable is void)
// - you can specify some HW sources to be ignored during the calculation
function GetComputerUuid(disable: TGetComputerUuid = []): RawUtf8; overload;


{ ****************** 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;
    /// read a Windows Registry content as length-specified buffer after ReadOpen()
    // - returns the number of bytes written to Data
    function ReadMax(const entry: SynUnicode; data: pointer; maxdatalen: DWORD): DWORD;
    /// 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;

{ some Windows API redefined here for Delphi and FPC consistency }

type
  TTimeZoneName = array[0..31] of WideChar;
  TTimeZoneInformation = record
    Bias: integer;
    StandardName: TTimeZoneName;
    StandardDate: TSystemTime;
    StandardBias: integer;
    DaylightName: TTimeZoneName;
    DaylightDate: TSystemTime;
    DaylightBias: integer;
  end;

  TDynamicTimeZoneInformation = record
    TimeZone: TTimeZoneInformation; // XP information
    TimeZoneKeyName: array[0..127] of WideChar;
    DynamicDaylightTimeDisabled: boolean;
  end;

function GetTimeZoneInformation(var info: TTimeZoneInformation): DWORD;
  stdcall; external kernel32;

/// allow to change the current system time zone on Windows
// - don't use this low-level function but the high-level mormot.core.search
// TSynTimeZone.ChangeOperatingSystemTimeZone method
// - will use the proper API before and after Vista, if needed
// - raise EOSException on failure
procedure SetSystemTimeZone(const info: TDynamicTimeZoneInformation);

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;

  PUNICODE_STRING = ^UNICODE_STRING;
  UNICODE_STRING = packed record
    Length: word;
    MaximumLength: word;
    {$ifdef CPUX64}
    _align: array[0..3] of byte;
    {$endif CPUX64}
    Buffer: PWideChar;
  end;

  /// 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 calling SystemTimeToTzSpecificLocalTime Windows API
// - note: FileTimeToLocalFileTime is not to be involved here
// - 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; Code: integer = 0);

/// 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;

/// ensure all \ / path delimiters are normalized into the current OS expectation
// - this function works in-place on an UTF-8 string instance
procedure NormalizeFileNameU(var FileName: RawUtf8);

/// 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, check faReadOnly and 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()
// - if FileName is a folder/directory, then returned FileSize equals -1
// - use a single Operating System call, so is faster than FileSize + FileAge
function FileInfoByName(const FileName: TFileName; out FileSize: Int64;
  out FileTimestampUtc: TUnixMSTime): boolean; overload;

/// 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;

/// get low-level file information, in a cross-platform way
// - is a wrapper around FileInfoByHandle() function
// - returns true on success
function FileInfoByName(const FileName: TFileName; FileId, FileSize: PInt64;
  LastWriteAccess, FileCreateDateTime: PUnixMSTime): boolean; overload;

/// 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;

/// check if a given file is a symbolic link
function FileIsSymLink(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;

/// create a symbolic link named SymLink pointing to the Target file
// - won't work with directories, a non existing target file, or on Windows XP
// - need specific (admin) priviledges on Windows, or developper mode enabled
function FileSymLink(const SymLink, Target: TFileName): boolean;

/// prompt the user for an error message to notify an unexpected issue
// - in practice, text encoding is better to be plain 7-bit ASCII
// - on Windows, will allocate a console if needed and write to STD_ERROR_HANDLE
// - on POSIX, will send the output to StdErrorHandle (=2)
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;

/// 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; overload;

/// just a wrapper around EnsureDirectoryExists(NormalizeFileName(Directory))
function NormalizeDirectoryExists(const Directory: TFileName;
  RaiseExceptionOnCreationFailure: ExceptionClass = nil): TFileName; overload;

/// 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;

/// always true on POSIX, may be false for a plain Windows GUI application
function HasConsole: boolean;
  {$ifdef OSPOSIX} inline; {$endif OSPOSIX}

/// 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;

/// write some text to the console using the current color
// - similar to writeln() but redirect to ConsoleWrite(NoColor=true)
procedure ConsoleWriteRaw(const Text: RawUtf8; NoLineFeed: boolean = false); overload;

/// append a line feed to the console
// - similar to writeln but redirect to ConsoleWrite() with proper thread safety
procedure ConsoleWriteLn;

/// 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;

type
  /// optional callback used by PosixFileNames()
  // - same signature as mormot.core.search MatchAnyP()
  TOnPosixFileName = function(opaque: pointer; name: PUtf8Char; namelen: PtrInt): 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
// - an optional callback can be supplied, used e.g. by the FileNames() function
// in mormot.core.search to efficiently implement name mask search with a TMatch
function PosixFileNames(const Folder: TFileName; Recursive: boolean;
  OnFile: TOnPosixFileName = nil; OnFileOpaque: pointer = nil;
  ExcludesDir: boolean = false): 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 ASMX64}
    procedure ReadOnlyLockSpin;
    {$endif 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 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 TSynVarData;
    /// 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}

/// Windows-specific RunCommand() function returning raw TProcessInformation
function RunCommandWin(const cmd: TFileName; waitfor: boolean;
  var processinfo: TProcessInformation; const env: TFileName = '';
  options: TRunOptions = []; waitfordelayms: cardinal = INFINITE;
  redirected: PRawByteString = nil; const onoutput: TOnRedirect = nil;
  const wrkdir: TFileName = ''): integer;

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(MilliSecsPerDay) + 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 FileInfoByName(const FileName: TFileName; FileId, FileSize: PInt64;
  LastWriteAccess, FileCreateDateTime: PUnixMSTime): boolean;
var
  h: THandle;
begin
  result := false;
  h := FileOpenSequentialRead(FileName); // = plain fpOpen() on POSIX
  if not ValidHandle(h) then
    exit;
  result := FileInfoByHandle(h, FileId, FileSize, LastWriteAccess, FileCreateDateTime);
  FileClose(h);
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;
var
  i, j: PtrInt;
begin
  result := FileName;
  j := Pos(InvertedPathDelim, result);
  if j <> 0 then
    for i := j to length(result) do
      if result[i] = InvertedPathDelim then
        result[i] := PathDelim;
end;

procedure NormalizeFileNameU(var FileName: RawUtf8);
var
  i: PtrInt;
  p: PAnsiChar;
begin
  i := PosExChar(InvertedPathDelim, FileName);
  if i = 0 then
    exit;
  p := UniqueRawUtf8(FileName);
  inc(p, i - 1);
  repeat
    if p^ = InvertedPathDelim then
      p^ := PathDelim;
    inc(p);
  until p^ = #0;
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 DisplayFatalError(const title, msg: RawUtf8);
const
  CRLFU: RawUtf8 = CRLF; // avoid any unexpected code page issue
var
  u: RawUtf8;
begin
  if title <> '' then
  begin
    SetLength(u, length(Title) + 1);
    FillCharFast(pointer(u)^, length(u), ord('-'));
    u := CRLFU + title + CRLFU + u + CRLFU + CRLFU + msg + CRLFU;
  end
  else
    u := msg + CRLFU;
  ConsoleErrorWrite(u);
end;

procedure DisplayError(const fmt: string; const args: array of const);
var
  msg: string;
begin
  msg := Format(fmt, args);
  DisplayFatalError('', RawUtf8(msg));
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
  h: THandle;
  size: Int64;
  read, pos: PtrInt;
  tmp: array[0..$7fff] of AnsiChar; // 32KB stack buffer
begin
  result := '';
  if FileName = '' then
    exit;
  h := FileOpenSequentialRead(FileName); // = plain fpOpen() on POSIX
  if not ValidHandle(h) then
    exit;
  if HasNoSize then
  begin
    pos := 0;
    repeat
      read := FileRead(h, 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(h);
    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(h, pointer(result), size) then
        result := ''; // error reading
    end;
  end;
  FileClose(h);
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 := Random31; // avoid paranoid overflow
  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
  an, ae, bn, be: TFileName;
begin
  // code below is not very fast, but correct ;)
  an := GetFileNameWithoutExt(string(A), @ae);
  bn := GetFileNameWithoutExt(string(B), @be);
  result := AnsiCompareFileName(ae, be);
  if result = 0 then
    // if both extensions matches, compare by filename
    result := AnsiCompareFileName(an, bn);
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;
  n: integer;
  dir: TFileName;
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;
  h: 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
  h := FileCreate(fn);
  if not ValidHandle(h) then
    exit; // a file can't be created
  result := true;
  if (idwWriteSomeContent in flags) and // some pointers and hash
     (FileWrite(h, Executable, SizeOf(Executable)) <> SizeOf(Executable)) then
    result := false;
  FileClose(h);
  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
  h: THandle;
begin
  result := false;
  // Memory-mapped file access does not go through the cache manager so
  // using FileOpenSequentialRead() is pointless here
  h := FileOpen(aFileName, fmOpenReadShared);
  if not ValidHandle(h) then
    exit;
  result := Map(h);
  if not result then
    FileClose(h);
  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
    exit;
  UnlockResource(HGlobal); // only needed outside of Windows
  FreeResource(HGlobal);
  HGlobal := 0;
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
  aligned: 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
  aligned := 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(aligned, 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;

procedure ConsoleWriteRaw(const Text: RawUtf8; NoLineFeed: boolean);
begin
  ConsoleWrite(Text, ccLightGray, NoLineFeed, {nocolor=}true);
end;

procedure ConsoleWriteLn;
begin
  ConsoleWrite(CRLF, ccLightGray, {nolinefeed=}true, {nocolor=}true);
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] in [',', '|'] 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_DESCR) .. high(CLK_DESCR)] 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
          if fDescArg = nil then
            result := result + 'Unexpected "' + fRawParams[i] + '" argument' + fLineFeed
          else
            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);
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)  // start with LongSwitch
      else if CompareMemSmall(pointer(s), pointer(ShortSwitch), length(ShortSwitch)) then
        swlen[i] := length(ShortSwitch) // start with 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; disable: TGetComputerUuid);
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 not (gcuSmbios in disable) and
     (_Smbios[sbiUuid] <> '') and
     TryStringToGUID('{' + string(_Smbios[sbiUuid]) + '}', uuid) then
    exit;
  // did we already compute (and persist) this UUID?
  if disable = [] then // we persist a fully-qualified UUID only
  begin
    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;
  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
  s := CPU_ARCH_TEXT;
  crc128c(pointer(s), length(s), u.b); // rough starting point
  {$ifdef CPUINTELARM}
  if not (gcuCpuFeatures in disable) then
    crc128c(@CpuFeatures, SizeOf(CpuFeatures), u.b); // override
  {$endif CPUINTELARM}
  if (RawSmbios.Data <> '') and // some bios have no uuid but some HW info
     not (gcuSmbiosData in disable) then
    crc32c128(@u.b, pointer(RawSmbios.Data), length(RawSmbios.Data));
  n := 0;
  if not (gcuSmbiosText in disable) then
    for i := 0 to length(_Smbios) - 1 do // some of _Smbios[] may be set
      crctext(PRawUtf8Array(@_Smbios)[i]);
  if not (gcuCpuInfoText in disable) then
    crctext(CpuCacheText);
  if not (gcuBiosInfoText in disable) then
    crctext(BiosInfoText);
  if not (gcuCpuInfoText in disable) then
    crctext(CpuInfoText);
  if Assigned(GetSystemMacAddress) and
     not (gcuMacAddress in disable) then
    // from mormot.net.sock or mormot.core.os.posix.inc for Linux only
    mac := GetSystemMacAddress;
  if mac <> nil then
    // MAC should make it unique at least over the local network
    for i := 0 to high(mac) do
      crctext(mac[i])
  else
  begin
    // unpersisted fallback if mormot.net.sock is not included (very unlikely)
    crctext(Executable.Host);
    exit; // unpersisted
  end;
  if fn <> '' then // disable = []
    // 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;

var
  _GetComputerUuid: RawUtf8;

function GetComputerUuid(disable: TGetComputerUuid): RawUtf8;
var
  u: TGuid;
begin
  if disable = [] then
  begin
    result := _GetComputerUuid; // try from cache
    if result <> '' then
      exit;
  end;
  GetComputerUuid(u, disable);
  result := RawUtf8(LowerCase(copy(GUIDToString(u), 2, 36)));
  if disable <> [] then
    exit; // cache fully-qualified UUID only
  GlobalLock;
  _GetComputerUuid := result;
  GlobalUnLock;
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 ASMX64}

procedure TRWLock.ReadOnlyLock;
// stack frame is required (at least on Windows) since it may call SwitchToThread
var
  backup: pointer; // better than push/pop since we have a stack frame
asm
        {$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
        mov     qword ptr [backup], rcx
        call    SwitchToThread
        mov     rcx, qword ptr [backup] // restore for the wait loop
        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 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 Padding[i].VType and VTYPE_STATIC <> 0 then
      VarClearProc(Padding[i].Data);
  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]); // safe copy
  {$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
  result := false;
  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;
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
    exit;
  if Index >= fPaddingUsedCount then
    fPaddingUsedCount := Index + 1;
  variant(Padding[Index]) := Value;
end;

function TSynLocker.GetPointer(Index: integer): pointer;
begin
  result := nil;
  if cardinal(Index) < cardinal(fPaddingUsedCount) then
  {$ifdef HASFASTTRYFINALLY}
  try
  {$else}
  begin
  {$endif HASFASTTRYFINALLY}
    RWLock(cReadOnly);
    with Padding[Index].Data do
      if VType = varUnknown then
        result := VUnknown;
  {$ifdef HASFASTTRYFINALLY}
  finally
  {$endif HASFASTTRYFINALLY}
    RWUnLock(cReadOnly);
  end;
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(variant(Data), varUnknown);
        VAny := 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
  result := 0;
  if cardinal(Index) <= high(Padding) then
    try
      RWLock(cWrite);
      with Padding[Index] do
      begin
        if Index < fPaddingUsedCount then
          VariantToInt64(variant(Data), result)
        else
          fPaddingUsedCount := Index + 1;
        variant(Data) := Int64(result + Increment);
      end;
    finally
      RWUnLock(cWrite);
    end;
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 := variant(Data)
        else
          fPaddingUsedCount := Index + 1;
        variant(Data) := Value;
      end;
    finally
      RWUnLock(cWrite);
    end;
end;

function TSynLocker.LockedPointerExchange(Index: integer; Value: pointer): pointer;
begin
  result := nil;
  if cardinal(Index) <= high(Padding) then
    try
      RWLock(cWrite);
      with Padding[Index] do
      begin
        if Index < fPaddingUsedCount then
          if VType = varUnknown then
            result := VAny
          else
            VarClearProc(Data)
        else
          fPaddingUsedCount := Index + 1;
        VType := varUnknown;
        VAny := Value;
      end;
    finally
      RWUnLock(cWrite);
    end;
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: PtrInt;
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.

