{ This file is a part of the Open Source Synopse mORMot framework 2, licensed under a MPL/GPL/LGPL three license - see LICENSE.md POSIX API calls for FPC, as used by mormot.core.os.pas } uses baseunix, unix, unixcp, unixtype, unixutil, // for TZSeconds - as used by sysutils anyway {$ifdef OSBSDDARWIN} sysctl, {$else} linux, syscall, {$endif OSBSDDARWIN} {$ifdef FPCUSEVERSIONINFO} // to be enabled in mormot.defines.inc fileinfo, // FPC 3.0 and up {$ifdef OSDARWIN} machoreader, // MACH-O executables {$else} elfreader, // ELF executables {$endif OSDARWIN} {$endif FPCUSEVERSIONINFO} errors, termio, dl, initc; // we statically link the libc for some raw calls {$ifdef UNICODE} 'mORMot assumes no UNICODE on POSIX, i.e. as TFileName = PChar = PUtf8Char' {$endif UNICODE} // define some raw text functions, to avoid linking mormot.core.text function IdemPChar(p, up: PUtf8Char): boolean; inline; var c, u: AnsiChar; begin // we know that p<>nil and up<>nil within this unit result := false; repeat u := up^; if u = #0 then break; inc(up); c := p^; inc(p); if c <> u then if (c >= 'a') and (c <= 'z') then begin dec(c, 32); if c <> u then exit; end else exit; until false; result := true; end; function IdemPChars(const s: RawUtf8; const up: array of PUtf8Char): boolean; var i: PtrInt; begin if s <> '' then begin result := true; for i := 0 to high(up) do if IdemPChar(pointer(s), up[i]) then exit; end; result := false; end; procedure FindNameValue(const s, up: RawUtf8; var res: RawUtf8); var p: PUtf8Char; L: PtrInt; begin p := pointer(s); while p <> nil do begin if IdemPChar(p, pointer(up)) then begin inc(p, length(up)); while (p^ <= ' ') and (p^ <> #0) do inc(p); // trim left L := 0; while p[L] > #13 do inc(L); while p[L - 1] = ' ' do dec(L); // trim right FastSetString(res, p, L); exit; end; p := GotoNextLine(p); end; res := ''; end; function GetNextCardinal(var P: PAnsiChar): PtrUInt; var c: cardinal; begin result := 0; while not (P^ in ['0'..'9']) do if P^ = #0 then exit else inc(P); repeat c := ord(P^) - 48; if c > 9 then break; result := result * 10 + c; inc(P); until false; end; function GetNextItem(var P: PAnsiChar): RawUtf8; var S: PAnsiChar; begin result := ''; while P^ <= ' ' do if P^ = #0 then exit else inc(P); S := P; repeat inc(P); until P^ <= ' '; FastSetString(result, S, P - S); end; procedure RawUtf8Append(var s: RawUtf8; p: PUtf8Char; l: PtrInt); var n: PtrInt; begin if l = 0 then exit; n := length(s); SetLength(s, n + l); MoveFast(p^, PByteArray(s)[n], l); end; function _fmt(const Fmt: string; const Args: array of const): RawUtf8; overload; begin result := RawUtf8(format(Fmt, Args)); // good enough (seldom called) end; procedure _fmt(const Fmt: string; const Args: array of const; var result: RawUtf8); overload; begin result := RawUtf8(format(Fmt, Args)); // good enough (seldom called) end; { ****************** Unicode, Time, File process } function LibraryOpen(const LibraryName: TFileName): TLibHandle; begin result := TLibHandle(dlopen(pointer(LibraryName), RTLD_LAZY)); end; procedure LibraryClose(Lib: TLibHandle); begin dlclose(pointer(Lib)); end; function LibraryResolve(Lib: TLibHandle; ProcName: PAnsiChar): pointer; begin result := dlsym(pointer(Lib), ProcName); end; function LibraryError: string; begin result := dlerror; end; { TIcuLibrary } procedure TIcuLibrary.Done; begin if icui18n <> nil then dlclose(icui18n); if icu <> nil then dlclose(icu); if icudata <> nil then dlclose(icudata); icu := nil; icudata := nil; icui18n := nil; @ucnv_open := nil; end; function TIcuLibrary.IsAvailable: boolean; begin if not Loaded then DoLoad; result := Assigned(ucnv_open); end; function IsWideStringManagerProperlyInstalled: boolean; const u: WideChar = #$2020; // should convert to dagger glyph #134 in CP 1252 var d: RawByteString; begin try widestringmanager.Unicode2AnsiMoveProc(@u, d, 1252, 1); result := (length(d) = 1) and (d[1] = #134); // the default RTL handler would just return '?' except result := false; end; end; procedure TIcuLibrary.DoLoad(const LibName: TFileName; Version: string); const NAMES: array[0..12] of string = ( 'ucnv_open', 'ucnv_close', 'ucnv_setSubstChars', 'ucnv_setFallback', 'ucnv_fromUChars', 'ucnv_toUChars', 'u_strToUpper', 'u_strToLower', 'u_strCompare', 'u_strCaseCompare', 'u_getDataDirectory', 'u_setDataDirectory', 'u_init'); {$ifdef OSANDROID} // https://developer.android.com/guide/topics/resources/internationalization ICU_VER: array[1..15] of string = ( '_3_8', '_4_2', '_44', '_46', '_48', '_50', '_51', '_53', '_55', '_56', '_58', '_60', '_63', '_66', '_68'); ICU_MAX = 80; ICU_MIN = 69; // previous versions are known and listed within ICU_VER[] SYSDATA: PAnsiChar = '/system/usr/icu'; {$else} ICU_MAX = 80; ICU_MIN = 44; SYSDATA: PAnsiChar = ''; {$endif OSANDROID} var i: integer; err: SizeInt; P: PPointer; {$ifndef OSDARWIN} so: string; {$endif OSDARWIN} v, vers: string; data: PAnsiChar; begin GlobalLock; try if Loaded then exit; Loaded := true; if LibName <> '' then begin icu := dlopen(pointer(LibName), RTLD_LAZY); if icu = nil then exit; end else begin {$ifdef OSDARWIN} // Mach OS has its own ICU set of libraries icu := dlopen('libicuuc.dylib', RTLD_LAZY); if icu <> nil then icui18n := dlopen('libicui18n.dylib', RTLD_LAZY); {$else} // libicudata should be loaded first because other two depend on it icudata := dlopen('libicudata.so', RTLD_LAZY); {$ifndef OSANDROID} if icudata = nil then begin // there is no link to the library -> try e.g. 'libicudata.so.67' if Version <> '' then icudata := dlopen(pointer('libicudata.so.' + Version), RTLD_LAZY); if icudata = nil then for i := ICU_MAX downto ICU_MIN do begin str(i, v); icudata := dlopen(pointer('libicudata.so.' + v), RTLD_LAZY); if icudata <> nil then begin Version := v; break; end; end; if icudata <> nil then so := '.' + Version; end; {$endif OSANDROID} if icudata <> nil then begin icu := dlopen(pointer('libicuuc.so' + so), RTLD_LAZY); if icu <> nil then icui18n := dlopen(pointer('libicui18n.so' + so), RTLD_LAZY); end; {$endif OSDARWIN} if icui18n = nil then begin // we did not find any ICU installed -> ensure iconv/RTL fallback is ok if not IsWideStringManagerProperlyInstalled then DisplayFatalError('ICU ' + CPU_ARCH_TEXT + ' is not available', 'Either install it or put cwstring in your uses clause as fallback'); Done; exit; end end; // ICU append a version prefix to all its functions e.g. ucnv_open_66 if (Version <> '') and (dlsym(icu, pointer('ucnv_open_' + Version)) <> nil) then vers := '_' + Version // matched the explicit version else begin {$ifdef OSANDROID} for i := high(ICU_VER) downto 1 do begin if dlsym(icu, pointer(NAMES[0] + ICU_VER[i])) <> nil then begin vers := ICU_VER[i]; break; end; end; if vers <> '' then {$endif OSANDROID} if dlsym(icu, 'ucnv_open') = nil then for i := ICU_MAX downto ICU_MIN do begin str(i, v); if dlsym(icu, pointer('ucnv_open_' + v)) <> nil then begin vers := '_' + v; break; end; end; end; P := @@ucnv_open; for i := 0 to high(NAMES) do begin P[i] := dlsym(icu, pointer(NAMES[i] + vers)); if P[i] = nil then begin @ucnv_open := nil; exit; end; end; data := u_getDataDirectory; if (data = nil) or (data^ = #0) then if SYSDATA <> '' then u_setDataDirectory(SYSDATA); err := 0; u_init(err); finally GlobalUnLock; end; end; function TIcuLibrary.ForceLoad(const LibName: TFileName; const Version: string): boolean; begin Done; Loaded := false; DoLoad(LibName, Version); result := Assigned(ucnv_open); end; function TIcuLibrary.ucnv(codepage: cardinal): pointer; var s: ShortString; err: SizeInt; {$ifdef CPUINTEL} mask: cardinal; {$endif CPUINTEL} begin if not IsAvailable then exit(nil); str(codepage, s); MoveFast(s[1], s[3], ord(s[0])); PWord(@s[1])^ := ord('c') + ord('p') shl 8; inc(s[0], 3); s[ord(s[0])] := #0; {$ifdef CPUINTEL} mask := GetMXCSR; SetMXCSR(mask or $0080 {MM_MaskInvalidOp} or $1000 {MM_MaskPrecision}); {$endif CPUINTEL} err := 0; result := ucnv_open(@s[1], err); if result <> nil then begin err := 0; ucnv_setSubstChars(result, '?', 1, err); ucnv_setFallback(result, true); end; {$ifdef CPUINTEL} SetMXCSR(mask); {$endif CPUINTEL} end; const // for CompareStringW() LOCALE_USER_DEFAULT = $400; NORM_IGNORECASE = 1 shl ord(coIgnoreCase); // [widestringmanager.coIgnoreCase] function CompareStringRTL(A, B: PWideChar; AL, BL, flags: integer): integer; var U1, U2: UnicodeString; // allocate two temporary strings begin // cwstring as fallback, using iconv on systems where ICU is not available SetString(U1, A, AL); SetString(U2, B, BL); result := widestringmanager.CompareUnicodeStringProc( U1, U2, TCompareOptions(flags)); end; function CompareStringW(locale, flags: DWORD; A: PWideChar; AL: integer; B: PWideChar; BL: integer): PtrInt; const CODE_POINT_ORDER = $8000; var err: SizeInt; begin if AL < 0 then AL := StrLenW(A); if BL < 0 then BL := StrLenW(B); err := 0; if icu.IsAvailable then if flags and NORM_IGNORECASE <> 0 then result := icu.u_strCaseCompare(A, AL, B, BL, CODE_POINT_ORDER, err) else result := icu.u_strCompare(A, AL, B, BL, true) else result := CompareStringRTL(A, B, AL, BL, flags); inc(result, 2); // caller would make -2 to get regular -1/0/1 comparison values end; function AnsiToWideRTL(CodePage: cardinal; A: PAnsiChar; W: PWideChar; AL, WL: PtrInt): PtrInt; var tmp: UnicodeString; begin // cwstring as fallback, using iconv on systems where ICU is not available widestringmanager.Ansi2UnicodeMoveProc(A, CodePage, tmp, AL); result := length(tmp); if result > WL then result := WL; MoveFast(pointer(tmp)^, W^, result * 2); end; function Unicode_AnsiToWide(A: PAnsiChar; W: PWideChar; LA, LW, CodePage: PtrInt): integer; var cnv: pointer; err: SizeInt; begin if CodePage = CP_UTF8 then exit(Utf8ToUnicode(W, A, LA)); cnv := icu.ucnv(CodePage); if cnv = nil then exit(AnsiToWideRTL(CodePage, A, W, LA, LW)); // fallback to cwstring/iconv err := 0; result := icu.ucnv_toUChars(cnv, W, LW, A, LA, err); if result < 0 then result := 0; icu.ucnv_close(cnv); end; function WideToAnsiRTL(CodePage: cardinal; W: PWideChar; A: PAnsiChar; WL, AL: PtrInt): PtrInt; var tmp: RawByteString; begin // cwstring as fallback, using iconv on systems where ICU is not available widestringmanager.Unicode2AnsiMoveProc(W, tmp, CodePage, WL); result := length(tmp); if result > AL then result := AL; MoveFast(pointer(tmp)^, A^, result); end; function Unicode_WideToAnsi(W: PWideChar; A: PAnsiChar; LW, LA, CodePage: PtrInt): integer; var cnv: pointer; err: SizeInt; begin if CodePage = CP_UTF8 then exit(UnicodeToUtf8(A, W, LW)); cnv := icu.ucnv(CodePage); if cnv = nil then exit(WideToAnsiRTL(CodePage, W, A, LW, LA)); // fallback to cwstring/iconv err := 0; result := icu.ucnv_fromUChars(cnv, A, LA, W, LW, err); if result < 0 then result := 0; icu.ucnv_close(cnv); end; function Unicode_InPlaceUpper(W: PWideChar; WLen: integer): integer; var err, i: SizeInt; begin if icu.IsAvailable then begin // call the accurate ICU library err := 0; result := icu.u_strToUpper(W, WLen, W, WLen, nil, err); end else begin // simple fallback code only handling 'a'..'z' -> 'A'..'Z' basic conversion for i := 0 to WLen - 1 do if ord(W[i]) in [ord('a')..ord('z')] then dec(W[i], 32); result := WLen; end; end; function Unicode_InPlaceLower(W: PWideChar; WLen: integer): integer; var err, i: SizeInt; begin if icu.IsAvailable then begin // call the accurate ICU library err := 0; result := icu.u_strToLower(W, WLen, W, WLen, nil, err); end else begin // simple fallback code only handling 'A'..'Z' -> 'a'..'z' basic conversion for i := 0 to WLen - 1 do if ord(W[i]) in [ord('A')..ord('Z')] then inc(W[i], 32); result := WLen; end; end; function GetDesktopWindow: PtrInt; begin result := 0; // fixed result on a window-abstracted system end; {$ifdef NODIRECTTHREADMANAGER} // try to stabilize MacOS/BSD pthreads API calls function GetCurrentThreadId: TThreadID; begin result := system.GetCurrentThreadID(); end; function TryEnterCriticalSection(var cs: TRTLCriticalSection): integer; begin result := system.TryEnterCriticalSection(cs); end; procedure EnterCriticalSection(var cs: TRTLCriticalSection); begin system.EnterCriticalSection(cs); end; procedure LeaveCriticalSection(var cs: TRTLCriticalSection); begin system.LeaveCriticalSection(cs); end; {$endif NODIRECTTHREADMANAGER} const HoursPerDay = 24; MinsPerHour = 60; SecsPerMin = 60; MinsPerDay = HoursPerDay * MinsPerHour; SecsPerDay = MinsPerDay * SecsPerMin; SecsPerHour = MinsPerHour * SecsPerMin; MilliSecsPerSec = 1000; MicroSecsPerSec = 1000000; MicroSecsPerMilliSec = 1000; NanoSecsPerMicroSec = 1000; NanoSecsPerMilliSec = 1000000; NanoSecsPerSec = 1000000000; const // Date Translation - see http://en.wikipedia.org/wiki/Julian_day D0 = 1461; D1 = 146097; D2 = 1721119; C1970 = 2440588; procedure JulianToGregorian(JulianDN: PtrUInt; out result: TSystemTime); {$ifdef HASINLINE}inline;{$endif} var YYear, XYear, Temp, TempMonth: PtrUInt; begin Temp := ((JulianDN - D2) * 4) - 1; JulianDN := Temp div D1; XYear := (Temp - (JulianDN * D1)) or 3; YYear := XYear div D0; Temp := (((XYear - (YYear * D0) + 4) shr 2) * 5) - 3; TempMonth := Temp div 153; result.Day := ((Temp - (TempMonth * 153)) + 5) div 5; if TempMonth >= 10 then begin inc(YYear); dec(TempMonth, 12 - 3); end else inc(TempMonth, 3); result.Month := TempMonth; result.Year := YYear + (JulianDN * 100); // initialize fake dayOfWeek - as used by FromGlobalTime() result.DayOfWeek := 0; end; procedure EpochToSystemTime(epoch: PtrUInt; out result: TSystemTime); var t: PtrUInt; begin t := epoch div SecsPerDay; JulianToGregorian(t + C1970, result); dec(epoch, t * SecsPerDay); t := epoch div SecsPerHour; result.Hour := t; dec(epoch, t * SecsPerHour); t := epoch div SecsPerMin; result.Minute := t; result.Second := epoch - t * SecsPerMin; end; {$ifdef OSDARWIN} // OSX has no clock_gettime() API type TTimebaseInfoData = record Numer: cardinal; Denom: cardinal; end; function mach_absolute_time: UInt64; cdecl external clib name 'mach_absolute_time'; function mach_continuous_time: UInt64; cdecl external clib name 'mach_continuous_time'; function mach_timebase_info(var TimebaseInfoData: TTimebaseInfoData): integer; cdecl external clib name 'mach_timebase_info'; var mach_timeinfo: TTimebaseInfoData; mach_timecoeff: double; mach_timenanosecond: boolean; // very likely to be TRUE on Intel CPUs procedure machtimetonanosec(var Value: Int64); inline; begin if not mach_timenanosecond then if mach_timeinfo.Denom = 1 then // integer resolution is enough Value := Value * mach_timeinfo.Numer else // use floating point to avoid potential overflow Value := round(Value * mach_timecoeff); end; procedure QueryPerformanceMicroSeconds(out Value: Int64); begin Value := mach_absolute_time; machtimetonanosec(Value); Value := Value div NanoSecsPerMicroSec; // ns to us end; function GetTickCount64: Int64; begin result := mach_absolute_time; machtimetonanosec(result); result := result div NanoSecsPerMilliSec; // ns to ms end; function GetUptimeSec: cardinal; var v: Int64; begin v := mach_continuous_time; machtimetonanosec(v); result := v div NanoSecsPerSec; // ns to s end; function UnixTimeUtc: TUnixTime; var tz: timeval; begin fpgettimeofday(@tz, nil); // from libc result := tz.tv_sec; end; function UnixMSTimeUtc: TUnixMSTime; var tz: timeval; begin fpgettimeofday(@tz, nil); result := (Int64(tz.tv_sec) * MilliSecsPerSec) + tz.tv_usec div MicroSecsPerMilliSec; // in milliseconds end; procedure GetSystemTime(out result: TSystemTime); var tz: timeval; begin fpgettimeofday(@tz, nil); EpochToSystemTime(tz.tv_sec, result); result.MilliSecond := tz.tv_usec div MicroSecsPerMilliSec; end; procedure GetLocalTime(out result: TSystemTime); var tz: timeval; begin fpgettimeofday(@tz, nil); // + unixutil.TZSeconds = UTC to local time conversion EpochToSystemTime(tz.tv_sec + TZSeconds, result); result.MilliSecond := tz.tv_usec div MicroSecsPerMilliSec; end; {$else} {$ifdef OSBSD} const {$ifdef OSFREEBSD} // see https://github.com/freebsd/freebsd/blob/master/sys/sys/time.h CLOCK_REALTIME = 0; CLOCK_MONOTONIC = 4; CLOCK_BOOTTIME = 5; CLOCK_REALTIME_COARSE = 10; // named CLOCK_REALTIME_FAST in FreeBSD 8.1+ CLOCK_MONOTONIC_COARSE = 12; {$else} // see https://github.com/openbsd/src/blob/master/sys/sys/_time.h#L63 CLOCK_REALTIME = 0; CLOCK_MONOTONIC = 3; CLOCK_BOOTTIME = 6; CLOCK_REALTIME_COARSE = CLOCK_REALTIME; // no FAST/COARSE version CLOCK_MONOTONIC_COARSE = CLOCK_MONOTONIC; {$endif OSFREEBSD} function clock_gettime(clk_id: cardinal; tp: ptimespec): integer; cdecl external clib name 'clock_gettime'; function clock_getres(clk_id: cardinal; tp: ptimespec): integer; cdecl external clib name 'clock_getres'; {$else} const CLOCK_REALTIME = 0; CLOCK_MONOTONIC = 1; CLOCK_REALTIME_COARSE = 5; // see http://lwn.net/Articles/347811 CLOCK_MONOTONIC_COARSE = 6; CLOCK_BOOTTIME = 7; // includes asleep time (2.6.39+) // libc's clock_gettime function uses vDSO (avoid syscall) while FPC by default // is compiled without FPC_USE_LIBC defined and do a syscall each time // GetTickCount64 fpc 2 494 563 op/sec // GetTickCount64 libc 119 919 893 op/sec function clock_gettime(clk_id: clockid_t; tp: ptimespec): cint; cdecl external clib name 'clock_gettime'; // LIBC_SUFFIX fails on CentOS 7 function gettimeofday(tp: ptimeval; tzp: ptimezone): cint; cdecl external clib name 'gettimeofday' + LIBC_SUFFIX; {$endif OSBSD} var // contains CLOCK_REALTIME_COARSE since kernel 2.6.32 CLOCK_REALTIME_FAST: integer = CLOCK_REALTIME; // contains CLOCK_MONOTONIC_COARSE since kernel 2.6.32 CLOCK_MONOTONIC_FAST: integer = CLOCK_MONOTONIC; // contains CLOCK_MONOTONIC_RAW since kernel 2.6.28 // - so that QueryPerformanceMicroSeconds() is not subject to NTP adjustments CLOCK_MONOTONIC_HIRES: integer = CLOCK_MONOTONIC; // contains CLOCK_BOOTTIME since kernel 2.6.39 CLOCK_UPTIME: integer = CLOCK_MONOTONIC; function UnixMSTimeUtc: TUnixMSTime; var r: timespec; begin clock_gettime(CLOCK_REALTIME_FAST, @r); // likely = CLOCK_REALTIME_COARSE // convert from nanoseconds into milliseconds result := QWord(PtrUInt(r.tv_nsec) div PtrUInt(NanoSecsPerMilliSec)) + QWord(r.tv_sec) * MilliSecsPerSec; end; function UnixTimeUtc: TUnixTime; var r: timespec; begin clock_gettime(CLOCK_REALTIME_FAST, @r); result := r.tv_sec; end; procedure QueryPerformanceMicroSeconds(out Value: Int64); var r : TTimeSpec; begin clock_gettime(CLOCK_MONOTONIC_HIRES, @r); // convert from nanoseconds into microseconds Value := QWord(PtrUInt(r.tv_nsec) div PtrUInt(NanoSecsPerMicroSec)) + QWord(r.tv_sec) * MicroSecsPerSec; end; procedure GetSystemTime(out result: TSystemTime); var r: timespec; begin // faster than fpgettimeofday() which makes a syscall and don't use vDSO clock_gettime(CLOCK_REALTIME_FAST, @r); EpochToSystemTime(r.tv_sec, result); result.MilliSecond := PtrUInt(r.tv_nsec) div PtrUInt(NanoSecsPerMilliSec); end; // c_timezone: longint external 'c' name 'timezone'; is broken and returns 0 procedure GetLocalTime(out result: TSystemTime); var r: timespec; begin // faster than fpgettimeofday() which makes a syscall and don't use vDSO clock_gettime(CLOCK_REALTIME_FAST, @r); // + unixutil.TZSeconds = UTC to local time conversion EpochToSystemTime(r.tv_sec + TZSeconds, result); result.MilliSecond := PtrUInt(r.tv_nsec) div PtrUInt(NanoSecsPerMilliSec); end; function GetTickCount64: Int64; var tp: timespec; begin clock_gettime(CLOCK_MONOTONIC_FAST, @tp); // likely = CLOCK_MONOTONIC_COARSE // convert from nanoseconds into milliseconds result := QWord(PtrUInt(tp.tv_nsec) div PtrUInt(NanoSecsPerMilliSec)) + QWord(tp.tv_sec) * MilliSecsPerSec; end; function GetUptimeSec: cardinal; var tp: timespec; begin tp.tv_sec := 0; clock_gettime(CLOCK_UPTIME, @tp); // convert from nanoseconds into milliseconds result := tp.tv_sec; end; {$endif OSDARWIN} function SetSystemTime(utctime: TUnixTime): boolean; var u: timeval; begin u.tv_sec := utctime; u.tv_usec := 0; result := fpsettimeofday(@u, nil) = 0; end; function UnixMSTimeUtcFast: TUnixMSTime; begin result := UnixMSTimeUtc; end; {$undef OSPTHREADS} {$undef HAS_PTHREADSETNAMENP} {$undef HAS_PTHREADSETAFFINITY} {$ifdef OSPTHREADSLIB} {$define OSPTHREADS} var {$ifdef OSLINUX} // pthread_setname_np for Linux https://stackoverflow.com/a/7989973/458259 {$define HAS_PTHREADSETNAMENP} pthread_setname_np: function(thread: pointer; name: PAnsiChar): integer; cdecl; // pthread_setaffinity_np has been tested on Linux only {$define HAS_PTHREADSETAFFINITY} pthread_setaffinity_np: function(thread: pointer; cpusetsize: SizeUInt; cpuset: pointer): integer; cdecl; {$endif OSLINUX} pthread_cancel: function(thread: pointer): integer; cdecl; pthread_mutex_init: function(mutex, attr: pointer): integer; cdecl; pthread_mutex_destroy: function(mutex: pointer): integer; cdecl; {$endif OSPTHREADSLIB} {$ifdef OSPTHREADSSTATIC} // note: pthread_setname_np() has no consistent API across POSIX systems {$define OSPTHREADS} {$ifdef OSDARWIN} // we specify link to clib='c' as in rtl/darwin/pthread.inc function pthread_cancel(thread: pointer): integer; cdecl; external clib name 'pthread_cancel'; function pthread_mutex_init(mutex, attr: pointer): integer; cdecl; external clib name 'pthread_mutex_init'; function pthread_mutex_destroy(mutex: pointer): integer; cdecl; external clib name 'pthread_mutex_destroy'; function pthread_mutex_lock(mutex: pointer): integer; cdecl; external clib name 'pthread_mutex_lock'; function pthread_mutex_trylock(mutex: pointer): integer; cdecl; external clib name 'pthread_mutex_trylock'; function pthread_mutex_unlock(mutex: pointer): integer; cdecl; external clib name 'pthread_mutex_unlock'; {$else} // just "external" without clib='c' as in rtl/*bsd/pthread.inc function pthread_cancel(thread: pointer): integer; cdecl; external; function pthread_mutex_init(mutex, attr: pointer): integer; cdecl; external; function pthread_mutex_destroy(mutex: pointer): integer; cdecl; external; function pthread_mutex_lock(mutex: pointer): integer; cdecl; external; function pthread_mutex_trylock(mutex: pointer): integer; cdecl; external; function pthread_mutex_unlock(mutex: pointer): integer; cdecl; external; {$endif OSDARWIN} {$endif OSPTHREADSSTATIC} function IsInitializedCriticalSection(var cs: TRTLCriticalSection): boolean; begin {$ifdef OSLINUX} result := cs.__m_kind <> 0; {$else} result := not IsZero(@cs, SizeOf(cs)); {$endif OSLINUX} end; {$ifdef OSPTHREADS} { TOSLightLock } procedure TOSLightLock.Init; begin FillCharFast(self, SizeOf(self), 0); // may be bigger than pthread struct {$ifdef OSPTHREADSLIB} if not Assigned(pthread_mutex_init) then EOSException.Create('TOSLightLock.Init: no pthread_mutex_init') else // no recursive attribute -> fast mutex {$endif OSPTHREADSLIB} pthread_mutex_init(@fMutex, nil); end; procedure TOSLightLock.Done; begin if IsInitializedCriticalSection(fMutex) then pthread_mutex_destroy(@fMutex); end; procedure TOSLightLock.Lock; begin pthread_mutex_lock(@fMutex); end; function TOSLightLock.TryLock: boolean; begin result := pthread_mutex_trylock(@fMutex) = 0; end; procedure TOSLightLock.UnLock; begin pthread_mutex_unlock(@fMutex); end; {$else} // fallback to plain recursive TRTLCriticalSection { TOSLightLock } procedure TOSLightLock.Init; begin InitCriticalSection(fMutex); end; procedure TOSLightLock.Done; begin DeleteCriticalSectionIfNeeded(fMutex); end; procedure TOSLightLock.Lock; begin EnterCriticalSection(fMutex); end; function TOSLightLock.TryLock: boolean; begin result := TryEnterCriticalSection(fMutex) <> 0; end; procedure TOSLightLock.UnLock; begin LeaveCriticalSection(fMutex); end; {$endif OSPTHREADS} procedure SetUnixThreadName(ThreadID: TThreadID; const Name: RawByteString); var // truncated to 16 non space chars (including #0) {%H-}trunc: array[0..15] of AnsiChar; i, L, c4: integer; begin if Name = '' then exit; L := 0; // trim unrelevant spaces and prefixes when filling the 16 chars i := 1; if Name[1] = 'T' then begin c4 := PCardinal(Name)^ and $dfdfdfdf; if (c4 = ord('T') + ord('S') shl 8 + ord('Q') shl 16 + ord('L') shl 24) or (c4 = ord('T') + ord('O') shl 8 + ord('R') shl 16 + ord('M') shl 24) then i := 5 else i := 2; end; while i <= length(Name) do begin if Name[i] > ' ' then begin trunc[L] := Name[i]; inc(L); if L = high(trunc) then break; end; inc(i); end; if L = 0 then exit; trunc[L] := #0; {$ifdef HAS_PTHREADSETNAMENP} // see https://stackoverflow.com/a/7989973 {$ifdef OSPTHREADSLIB} if Assigned(pthread_setname_np) then try pthread_setname_np(pointer(ThreadID), @trunc[0]); except // ignore any exception (pthread confusion with its static version?) @pthread_setname_np := nil; // don't continue that way end; {$endif OSPTHREADSLIB} {$endif HAS_PTHREADSETNAMENP} end; procedure RawSetThreadName(ThreadID: TThreadID; const Name: RawUtf8); begin if ThreadID <> MainThreadID then // don't change the main process name SetUnixThreadName(ThreadID, Name); // call pthread_setname_np() end; function RawKillThread(Thread: TThread): boolean; begin result := false; {$ifdef OSPTHREADSLIB} if Assigned(pthread_cancel) then try result := pthread_cancel(pointer(Thread.ThreadID)) = 0; except // ignore any exception (pthread confusion with its static version?) @pthread_cancel := nil; // don't continue that way end; {$endif OSPTHREADSLIB} {$ifdef OSPTHREADSSTATIC} result := pthread_cancel(pointer(Thread.ThreadID)) = 0; {$endif OSPTHREADSSTATIC} end; procedure ResetCpuSet(out CpuSet: TCpuSet); begin FillCharFast(CpuSet, SizeOf(CpuSet), 0); end; function SetThreadMaskAffinity(Thread: TThread; const Mask: TCpuSet): boolean; begin result := false; {$ifdef HAS_PTHREADSETAFFINITY} {$ifdef OSPTHREADSLIB} if (Thread <> nil) and Assigned(pthread_setaffinity_np) then try result := pthread_setaffinity_np( pointer(Thread.ThreadID), SizeOf(Mask), @Mask) = 0; except // ignore any exception (pthread confusion with its static version?) @pthread_setaffinity_np := nil; // don't continue that way end; {$endif OSPTHREADSLIB} {$endif HAS_PTHREADSETAFFINITY} end; {$ifdef HAS_PTHREADSETAFFINITY} function sched_getaffinity(pid: integer; cpusetsize: SizeUInt; cpuset: pointer): integer; cdecl external clib name 'sched_getaffinity'; {$endif HAS_PTHREADSETAFFINITY} function GetMaskAffinity(out CpuSet: TCpuSet): boolean; begin {$ifdef HAS_PTHREADSETAFFINITY} result := sched_getaffinity(0, SizeOf(CpuSet), @CpuSet) = 0; {$else} result := false; // unsupported by now {$endif HAS_PTHREADSETAFFINITY} end; {$ifndef NOEXCEPTIONINTERCEPT} function TSynLogExceptionContext.AdditionalInfo( out ExceptionNames: TPUtf8CharDynArray): cardinal; begin result := 0; // Windows/CLR specific by now end; var _RawLogException: TOnRawLogException; // FPC: intercept via the RaiseProc global variable {$define WITH_RAISEPROC} // RaiseProc redirection is implemented in main mormot.core.os.pas {$endif NOEXCEPTIONINTERCEPT} function GetFileNameFromUrl(const Uri: string): TFileName; begin result := ''; // no such native API on POSIX end; const faInvalidFile = faDirectory; faDirectoryMask = faDirectory; function FileDateToDateTime(const FileDate: TFileAge): TDateTime; begin if FileDate <= 0 then result := 0 else // + unixutil.TZSeconds = UTC to local time conversion result := Int64(FileDate + TZSeconds) / Int64(SecsPerDay) + Int64(UnixDelta); end; function FileAgeToDateTime(const FileName: TFileName): TDateTime; begin // faster to use POSIX time than RTL FileDateToDateTime(FileAge()) result := FileDateToDateTime(FileAgeToUnixTimeUtc(FileName)); // UTC to local end; function FileAgeToUnixTimeUtc(const FileName: TFileName; AllowDir: boolean): TUnixTime; var st: TStat; begin result := 0; if (FileName <> '') and (fpStat(pointer(FileName), st) = 0) and (AllowDir or (not FpS_ISDIR(st.st_mode))) then result := st.st_mtime; // as TUnixTime seconds, with no local conversion end; function FileHandleToUnixTimeUtc(F: THandle): TUnixTime; var st: TStat; begin result := 0; if ValidHandle(F) and (FpFStat(F, st) = 0) then result := st.st_mtime; end; function FileSetDateFromUnixUtc(const Dest: TFileName; Time: TUnixTime): boolean; var t: TUtimBuf; begin result := false; if (Dest = '') or (Time = 0) then exit; t.actime := Time; t.modtime := Time; result := FpUtime(pointer(Dest), @t) = 0; // direct syscall end; function FileSetDateFrom(const Dest: TFileName; SourceHandle: THandle): boolean; begin result := FileSetDateFromUnixUtc(Dest, FileHandleToUnixTimeUtc(SourceHandle)); end; function FileSetDateFrom(const Dest, Source: TFileName): boolean; begin result := FileSetDateFromUnixUtc(Dest, FileAgeToUnixTimeUtc(Source)); end; function FileSetDateFromWindowsTime(const Dest: TFileName; WinTime: integer): boolean; var dt: TDateTime; begin dt := WindowsFileTimeToDateTime(WinTime); result := (Dest <> '') and (dt <> 0) and (FileSetDate(Dest, DateTimeToFileDate(dt)) = 0); // with LocalToEpoch() end; function SearchRecToWindowsTime(const F: TSearchRec): integer; begin result := DateTimeToWindowsFileTime(FileDateToDateTime(F.Time)); end; function SearchRecToUnixTimeUtc(const F: TSearchRec): TUnixTime; begin result := F.Time; // raw POSIX FileDate is already in UTC seconds end; function FileAgeToWindowsTime(const FileName: TFileName): integer; begin result := DateTimeToWindowsFileTime(FileAgeToDateTime(FileName)); end; function FileIsWritable(const FileName: TFileName): boolean; begin result := (FileName <> '') and (fpaccess(pointer(FileName), W_OK) = 0); end; procedure FileSetHidden(const FileName: TFileName; ReadOnly: boolean); begin if FileName <> '' then if ReadOnly then fpchmod(pointer(FileName), S_IRUSR) else fpchmod(pointer(FileName), S_IRUSR or S_IWUSR); end; procedure FileSetSticky(const FileName: TFileName); begin fpchmod(FileName, S_IRUSR or S_IWUSR or S_IRGRP or S_IROTH or S_ISVTX); end; function FileSize(const FileName: TFileName): Int64; var st: TStat; begin if (FileName = '') or (fpStat(pointer(FileName), st) <> 0) or FpS_ISDIR(st.st_mode) then result := 0 else result := st.st_size; end; function FileExists(const FileName: TFileName): boolean; var st: TStat; begin result := (FileName <> '') and (fpStat(pointer(FileName), st) = 0) and not FpS_ISDIR(st.st_mode); end; function FileCreate(const aFileName: TFileName; aMode, aRights: integer): THandle; begin if aFileName = '' then result := 0 else if (aMode = 0) and (aRights = 0) then result := sysutils.FileCreate(aFileName) // direct call of the FPC RTL else begin if aRights = 0 then // use 644 / '-rw-r-r--' default POSIX file attributes aRights := S_IRUSR or S_IWUSR or S_IRGRP or S_IROTH; result := sysutils.FileCreate(aFileName, aMode, aRights); end; end; procedure StatTimeMS(const st: TStat; out time: TUnixMSTime); inline; begin time := QWord(st.st_mtime) * MilliSecsPerSec + // no local conversion needed // include milliseconds information {$ifdef OSLINUXANDROID} st.st_mtime_nsec div NanoSecsPerMilliSec; {$else} st.st_mtimensec div NanoSecsPerMilliSec; {$endif OSLINUXANDROID} end; function FileInfoByName(const FileName: TFileName; out FileSize: Int64; out FileTimestampUtc: TUnixMSTime): boolean; var st: TStat; begin result := fpStat(pointer(FileName), st) = 0; if not result then exit; FileSize := st.st_size; StatTimeMS(st, FileTimestampUtc); end; function FileSize(F: THandle): Int64; var st: TStat; begin if fpFstat(F, st) <> 0 then result := 0 else result := st.st_size; end; function FileSeek64(Handle: THandle; const Offset: Int64; Origin: cardinal): Int64; begin result := FPLSeek(Handle, Offset, Origin); end; function FileInfoByHandle(aFileHandle: THandle; FileId, FileSize: PInt64; LastWriteAccess, FileCreateDateTime: PUnixMSTime): boolean; var mtime, atime, ctime: Int64; lp: TStat; r: integer; begin r := FpFStat(aFileHandle, lp); result := r >= 0; if not result then exit; if FileId <> nil then FileId^ := lp.st_ino; if FileSize <> nil then FileSize^ := lp.st_size; if (LastWriteAccess = nil) and (FileCreateDateTime = nil) then exit; StatTimeMS(lp, mtime); if LastWriteAccess <> nil then LastWriteAccess^ := mtime; if FileCreateDateTime = nil then exit; // some FS don't populate all fields, so we use what we actually got {$ifdef OSOPENBSD} if (lp.st_birthtime <> 0) and (lp.st_birthtime < lp.st_ctime) then lp.st_ctime := lp.st_birthtime; {$endif OSOPENBSD} // ignore nanoseconds/Milliseconds for FileCreateDateTime ctime := Int64(lp.st_ctime) * MilliSecsPerSec; atime := Int64(lp.st_atime) * MilliSecsPerSec; if mtime <> 0 then if (ctime = 0) or (ctime > mtime) then ctime := mtime; if atime <> 0 then if (ctime = 0) or (ctime > atime) then ctime := atime; FileCreateDateTime^ := ctime; end; function FileIsExecutable(const FileName: TFileName): boolean; var st: TStat; begin result := (fpStat(pointer(FileName), st) = 0) and (st.st_mode and (S_IXUSR or S_IXGRP or S_IXOTH) <> 0) and not FpS_ISDIR(st.st_mode); end; function GetExecutableName(aAddress: pointer): TFileName; var dlinfo: dl_info; begin FillCharFast(dlinfo, sizeof(dlinfo), 0); dladdr(aAddress, @dlinfo); result := ExpandFileName(string(dlinfo.dli_fname)); end; function CopyFile(const Source, Target: TFileName; FailIfExists: boolean): boolean; var src, dst: THandleStream; begin result := false; if FileExists(Target) then if FailIfExists then exit else DeleteFile(Target); try src := TFileStreamEx.Create(Source, fmOpenReadShared); try dst := TFileStreamEx.Create(Target, fmCreate); try StreamCopyUntilEnd(src, dst); // faster than dst.CopyFrom() finally dst.Free; end; FileSetDateFrom(Target, src.Handle); finally src.Free; end; result := true; except result := false; end; end; function ValidHandle(Handle: THandle): boolean; begin result := PtrInt(Handle) >= 0; // 0=StdIn is a valid POSIX file descriptor end; function WaitReadPending(fd, timeout: integer): boolean; var p: TPollFD; // select() limits process to 1024 sockets in POSIX -> use poll() // https://moythreads.com/wordpress/2009/12/22/select-system-call-limitation begin p.fd := fd; p.events := POLLIN or POLLPRI; p.revents := 0; result := FpPoll(@p, 1, timeout) > 0; end; {$I-} procedure DisplayFatalError(const title, msg: RawUtf8); var err: ^Text; begin err := @StdErr; if title <> '' then writeln(err^, Executable.ProgramName, ': ', title); writeln(err^, Executable.ProgramName, ': ', msg); ioresult; end; {$I+} function FileOpenSequentialRead(const FileName: TFileName): integer; begin // SysUtils.FileOpen = fpOpen + fpFlock result := fpOpen(pointer(FileName), O_RDONLY); // no fpFlock() call end; function FileIsReadable(const aFileName: TFileName): boolean; var fd: integer; begin fd := fpOpen(pointer(aFileName), O_RDONLY); // no fpFlock() call result := ValidHandle(fd); if result then FpClose(fd); end; procedure SetEndOfFile(F: THandle); begin FpFtruncate(F, FPLseek(F, 0, SEEK_CUR)); end; procedure FlushFileBuffers(F: THandle); begin FpFsync(F); end; function GetLastError: integer; begin result := fpgeterrno; end; function IsSharedViolation(ErrorCode: integer): boolean; begin if ErrorCode = 0 then ErrorCode := fpgeterrno; result := ErrorCode = ESysEACCES; end; procedure SetLastError(error: integer); begin fpseterrno(error); end; function GetErrorText(error: integer): RawUtf8; begin result := StrError(error); // from FPC RTL: using a OS-specific array of const end; function TMemoryMap.DoMap(aCustomOffset: Int64): boolean; begin if aCustomOffset <> 0 then if (aCustomOffset and (SystemInfo.dwPageSize - 1)) <> 0 then raise EOSException.CreateFmt( 'DoMap(aCustomOffset=%d) incompatible with dwPageSize=%d', [aCustomOffset, SystemInfo.dwPageSize]); fBuf := fpmmap(nil, fBufSize, PROT_READ, MAP_SHARED, fFile, aCustomOffset); if fBuf = MAP_FAILED then begin fBuf := nil; result := false; end else result := true; end; procedure TMemoryMap.DoUnMap; begin if (fBuf <> nil) and (fBufSize > 0) and (fFile <> 0) then fpmunmap(fBuf, fBufSize); end; procedure SleepHiRes(ms: cardinal); var timeout: TTimespec; s: cardinal; begin if ms = 0 then // handle SleepHiRes(0) special case if SleepHiRes0Yield then begin // warning: reported as buggy by Alan on POSIX, and despitable by Linus // - from our testing, it gives worse performance than fpnanosleep() ThreadSwitch; // call e.g. POSIX libc's sched_yield API exit; end else begin timeout.tv_sec := 0; timeout.tv_nsec := 10000; // 10us is around timer resolution on modern HW end else begin s := ms div MilliSecsPerSec; timeout.tv_sec := s; timeout.tv_nsec := (ms - s * MilliSecsPerSec) * NanoSecsPerMilliSec; end; fpnanosleep(@timeout, nil) // no retry loop on ESysEINTR (as with regular RTL's Sleep) end; procedure SwitchToThread; var timeout: Ttimespec; begin // nanosleep() seems better than FPC RTL ThreadSwitch = POSIX libc sched_yield timeout.tv_sec := 0; timeout.tv_nsec := 10; // empirically identified on a recent Linux Kernel // note: nanosleep() adds a few dozen of microsecs for context switching fpnanosleep(@timeout, nil); end; {$undef HASEVENTFD} {$ifdef OSLINUX} {$ifdef CPUX64} {$define HASEVENTFD} {$endif CPUX64} {$ifdef CPUX86} {.$define HASEVENTFD} // untested {$endif CPUX86} {$ifdef CPUAARCH64} {.$define HASEVENTFD} // untested {$endif CPUAARCH64} {$endif OSLINUX} { TSynEvent } constructor TSynEvent.Create; begin {$ifdef HASEVENTFD} fFD := LinuxEventFD({nonblocking=}false, {semaphore=}false); if fFD = 0 then // fallback to PRTLEvent on oldest kernel {$endif HASEVENTFD} fHandle := RTLEventCreate; end; destructor TSynEvent.Destroy; begin {$ifdef HASEVENTFD} if fFD <> 0 then begin LinuxEventFDWrite(fFD, 1); // release the lock or do nothing fpClose(fFD); end else {$endif HASEVENTFD} RTLEventDestroy(fHandle); inherited Destroy; end; procedure TSynEvent.ResetEvent; begin {$ifdef HASEVENTFD} if fFD = 0 then // no need to reset the eventfd() handle {$endif HASEVENTFD} RTLEventResetEvent(fHandle); end; procedure TSynEvent.SetEvent; begin {$ifdef HASEVENTFD} if fFD <> 0 then LinuxEventFDWrite(fFD, 1) else {$endif HASEVENTFD} RTLEventSetEvent(fHandle); end; procedure TSynEvent.WaitFor(TimeoutMS: integer); begin {$ifdef HASEVENTFD} if fFD <> 0 then begin if WaitReadPending(fFD, TimeoutMS) then // = LinuxEventFDWait() LinuxEventFDRead(fFD); end else {$endif HASEVENTFD} RTLEventWaitFor(fHandle, TimeoutMS); end; procedure TSynEvent.WaitForEver; begin {$ifdef HASEVENTFD} if fFD <> 0 then LinuxEventFDRead(fFD) else {$endif HASEVENTFD} RTLEventWaitFor(fHandle); end; procedure InitializeCriticalSection(var cs : TRTLCriticalSection); begin InitCriticalSection(cs); end; procedure DeleteCriticalSection(var cs : TRTLCriticalSection); begin DoneCriticalSection(cs); end; function GetFileOpenLimit(hard: boolean): integer; var limit: TRLIMIT; begin if fpgetrlimit(RLIMIT_NOFILE, @limit) = 0 then if hard then result := limit.rlim_max else result := limit.rlim_cur else result := -1; end; function SetFileOpenLimit(max: integer; hard: boolean): integer; var limit: TRLIMIT; begin result := -1; if fpgetrlimit(RLIMIT_NOFILE, @limit) <> 0 then exit; if (hard and (integer(limit.rlim_max) = max)) or (not hard and (integer(limit.rlim_cur) = max)) then exit(max); // already to the expected value if hard then limit.rlim_max := max else limit.rlim_cur := max; if fpsetrlimit(RLIMIT_NOFILE, @limit) = 0 then result := GetFileOpenLimit(hard); end; {$ifdef OSLINUX} { the systemd API is Linux-specific } { TSystemD } procedure TSystemD.DoLoad; var p: PPointer; i, j: PtrInt; const NAMES: array[0..5] of PAnsiChar = ( 'sd_listen_fds', 'sd_is_socket_unix', 'sd_journal_print', 'sd_journal_sendv', 'sd_notify', 'sd_watchdog_enabled'); begin GlobalLock; if not tested then begin systemd := dlopen(LIBSYSTEMD_PATH, RTLD_LAZY); if systemd <> nil then begin p := @@listen_fds; for i := 0 to high(NAMES) do begin p^ := dlsym(systemd, NAMES[i]); if p^ = nil then begin p := @@listen_fds; for j := 0 to i do begin p^ := nil; inc(p); end; break; end; inc(p); end; end; tested := true; end; GlobalUnLock; end; function TSystemD.IsAvailable: boolean; begin if not tested then DoLoad; result := Assigned(listen_fds); end; function TSystemD.ProcessIsStartedBySystemd: boolean; begin result := IsAvailable and // note: for example on Ubuntu 20.04 INVOCATION_ID is always defined // from the other side PPID 1 can be in case we run under docker of started // by init.d so let's verify both (fpgetppid() = 1) and (fpGetenv(ENV_INVOCATION_ID) <> nil); end; procedure TSystemD.Done; begin if systemd <> nil then begin dlclose(systemd); systemd := nil; end; end; {$ifdef HASEVENTFD} const EFD_SEMAPHORE = $00000001; EFD_NONBLOCK = O_NONBLOCK; EFD_CLOEXEC = O_CLOEXEC; // exists since Kernel 2.6.27 {$ifdef CPUX64} syscall_nr_eventfd2 = 290; {$endif CPUX64} {$ifdef CPUX86} syscall_nr_eventfd2 = 328; {$endif CPUX86} {$ifdef CPUAARCH64} syscall_nr_eventfd2 = 356; {$endif CPUAARCH64} function eventfd(initval, flags: cardinal): integer; inline; begin result := do_syscall(syscall_nr_eventfd2, TSysParam(initval), TSysParam(flags)); end; function LinuxEventFD(nonblocking, semaphore: boolean): integer; var flags: cardinal; begin result := 0; if KernelRevision < $02061b then exit; // not available prior to kernel 2.6.27 flags := 0; if nonblocking then flags := EFD_NONBLOCK; if semaphore then flags := flags or EFD_SEMAPHORE; result := eventfd(0, flags); if not ValidHandle(result) then result := 0; end; {$else} function LinuxEventFD(nonblocking, semaphore: boolean): integer; begin result := 0; // non implemented (not tested, infact) on this CPU end; {$endif HASEVENTFD} function LinuxEventFDRead(fd: integer): Int64; begin { If EFD_SEMAPHORE was specified and the eventfd counter has a nonzero value, then a read returns 8 bytes containing the value 1, and the counter's value is decremented by 1 } result := 0; if do_syscall(syscall_nr_read, fd, TSysParam(@result), 8) <> 8 then result := -1; end; procedure LinuxEventFDWrite(fd: integer; count: QWord); begin if count <> 0 then do_syscall(syscall_nr_write, fd, TSysParam(@count), SizeOf(count)); end; function LinuxEventFDWait(fd: integer; ms: integer): boolean; begin result := WaitReadPending(fd, ms); end; {$endif OSLINUX} // we bypass crt.pp since this unit cancels the SIGINT signal procedure AllocConsole; begin StdOut := StdOutputHandle; end; var TextAttr: integer = 255; // always change the color at startup procedure TextColorCmd(Color: TConsoleColor; var s: TShort8); const TERM_CTRL: string[8] = '04261537'; begin s[0] := #0; if (ord(Color) = TextAttr) or not StdOutIsTTY then exit; TextAttr := ord(Color); s := #27'[0;3#m'; if ord(Color) >= 8 then s[3] := '1'; s[6] := TERM_CTRL[(ord(Color) and 7) + 1]; end; procedure TextColorAppend(Color: TConsoleColor; var s: RawUtf8); var c: TShort8; begin TextColorCmd(Color, c); RawUtf8Append(s, @c[1], ord(c[0])); end; procedure TextColor(Color: TConsoleColor); var c: TShort8; begin TextColorCmd(Color, c); if c[0] <> #0 then fpwrite(StdOutputHandle, @c[1], ord(c[0])); // single syscall end; procedure TextBackground(Color: TConsoleColor); begin // not implemented yet - but not much needed either end; var ConsoleCriticalSection: TOSLock; const PosixLineFeed: AnsiChar = #10; procedure ConsoleWrite(const Text: RawUtf8; Color: TConsoleColor; NoLineFeed, NoColor: boolean); var s: RawUtf8; begin // pre-compute the whole chars to be sent to the console if not NoColor then TextColorAppend(Color, s); RawUtf8Append(s, pointer(Text), length(Text)); if not NoLineFeed then RawUtf8Append(s, @PosixLineFeed, 1); if not NoColor then TextColorAppend(ccLightGray, s); // display whole line in a single syscall ConsoleCriticalSection.Lock; FileWriteAll(StdOutputHandle, pointer(s), length(s)); // UTF-8 console ConsoleCriticalSection.UnLock; end; function UnixKeyPending: boolean; begin result := WaitReadPending(StdInputHandle, 0); end; {$I-} procedure ConsoleWaitForEnterKey; var c: AnsiChar; begin if GetCurrentThreadID = MainThreadID then begin SynDaemonIntercept; // intercept ^C and SIGQUIT - do nothing if already set repeat if IsMultiThread then CheckSynchronize(100) else Sleep(100); if SynDaemonTerminated <> 0 then break; if UnixKeyPending then repeat c := #0; if FpRead(StdInputHandle, c, 1) <> 1 then break; if c in [#10, #13] then exit; until false; until false; end else ReadLn; ioresult; end; {$I+} function ConsoleStdInputLen: integer; begin if fpioctl(StdInputHandle, FIONREAD, @result) < 0 then result := 0; end; function Utf8ToConsole(const S: RawUtf8): RawByteString; begin result := S; // expect a UTF-8 console under Linux/BSD end; {$ifdef FPCUSEVERSIONINFO} // FPC 3.0+ if enabled in .inc / project options function TFileVersion.RetrieveInformationFromFileName: boolean; var VI: TVersionInfo; TI: integer; begin result := false; if fFileName = '' then exit; VI := TVersionInfo.Create; try try // extract information - VI.Load() may raise EResNotFound if (fFileName <> '') and (fFileName <> ParamStr(0)) then VI.Load(fFileName) else VI.Load(HInstance); // load info for currently running program result := VI.FixedInfo.FileVersion[0] <> 0; // set extracted version numbers SetVersion(VI.FixedInfo.FileVersion[0], VI.FixedInfo.FileVersion[1], VI.FixedInfo.FileVersion[2], VI.FixedInfo.FileVersion[3]); // detect translation if VI.VarFileInfo.Count > 0 then with VI.VarFileInfo.Items[0] do LanguageInfo := _fmt('%.4x%.4x', [language, codepage]); if LanguageInfo = '' then begin // take first language TI := 0; if VI.StringFileInfo.Count > 0 then LanguageInfo := VI.StringFileInfo.Items[0].Name end else begin // look for language index TI := VI.StringFileInfo.Count - 1; while (TI >= 0) and (CompareText(VI.StringFileInfo.Items[TI].Name, LanguageInfo) <> 0) do dec(TI); if TI < 0 then begin TI := 0; // revert to first translation LanguageInfo := VI.StringFileInfo.Items[TI].Name; end; end; with VI.StringFileInfo.Items[TI] do begin CompanyName := Values['CompanyName']; FileDescription := Values['FileDescription']; FileVersion := Values['FileVersion']; InternalName := Values['InternalName']; LegalCopyright := Values['LegalCopyright']; OriginalFilename := Values['OriginalFilename']; ProductName := Values['ProductName']; ProductVersion := Values['ProductVersion']; Comments := Values['Comments']; end; except // trap EResNotFound exception from VI.Load() end; finally VI.Free; end; end; {$else} function TFileVersion.RetrieveInformationFromFileName: boolean; begin result := false; // nothing to be done end; {$endif FPCUSEVERSIONINFO} procedure GetUserHost(out User, Host: RawUtf8); begin Host := RawUtf8(GetHostName); if Host = '' then Host := RawUtf8(GetEnvironmentVariable('HOSTNAME')); User := RawUtf8(GetEnvironmentVariable('LOGNAME')); // POSIX if User = '' then User := RawUtf8(GetEnvironmentVariable('USER')); end; function GetEnvFolder(const name: string; var folder: TFileName; writable: boolean): boolean; begin folder := GetEnvironmentVariable(name); if folder <> '' then if writable and not IsDirectoryWritable(folder) then folder := '' else folder := IncludeTrailingPathDelimiter(folder); result := folder <> ''; end; function WritableFolder(const parent, sub: TFileName; var folder: TFileName): boolean; begin result := false; if not IsDirectoryWritable(parent) then exit; folder := EnsureDirectoryExists(parent + sub); if folder = '' then exit; if IsDirectoryWritable(folder) then result := true else folder := ''; end; procedure _ComputeSystemPath(kind: TSystemPath; var result: TFileName); begin result := ''; // "out result" param is not enough for FPC case kind of spLog: // try '/var/log/' if not WritableFolder('/var/log/', TFileName(Executable.ProgramName), result) and // try '/log' not WritableFolder(Executable.ProgramFilePath, 'log', result) then // fallback to '$TMP/-log' - spUserData/$HOME is no option result := EnsureDirectoryExists(format('%s%s-log', [GetSystemPath(spTemp), Executable.ProgramName])); // warning: $HOME is reported wrong with sudo for spUserData/spUserDocuments spUserData: // try $XDG_CACHE_HOME if not GetEnvFolder('XDG_CACHE_HOME', result, {writable=}true) and // try '$HOME/.cache' not WritableFolder(GetSystemPath(spUserDocuments), '.cache', result) then // fallback to '$TMP/' WritableFolder(GetSystemPath(spTemp), TFileName(Executable.User), result); spTemp: begin // try $TMPDIR (POSIX standard) and $TMP and $TEMP if GetEnvFolder('TMPDIR', result, {writable=}true) or GetEnvFolder('TMP', result, {writable=}true) or GetEnvFolder('TEMP', result, {writable=}true) then exit; // try /tmp result := '/tmp/'; if not IsDirectoryWritable(result) then // fallback to /var/tmp result := '/var/tmp/'; end; else // POSIX requires a value for the $HOME environment variable GetEnvFolder('HOME', result, {writable=}false); end; end; function _GetSystemStoreAsPem(CertStore: TSystemCertificateStore): RawUtf8; var files: TRawUtf8DynArray; f: PtrInt; begin // see https://go.dev/src/crypto/x509/root_unix.go as reference case CertStore of scsRoot: result := StringFromFirstFile([ {$ifdef OSLINUXANDROID} '/etc/ssl/certs/ca-certificates.crt', // Debian/Gentoo '/etc/pki/tls/certs/ca-bundle.crt', // Fedora/RHEL 6 '/etc/ssl/ca-bundle.pem', // OpenSUSE '/etc/pki/tls/cacert.pem', // OpenELEC '/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem', // CentOS/RHEL 7 '/etc/ssl/cert.pem' // Alpine Linux {$else} '/usr/local/etc/ssl/cert.pem', // FreeBSD '/etc/ssl/cert.pem', // OpenBSD '/usr/local/share/certs/ca-root-nss.crt', // DragonFly '/etc/openssl/certs/ca-certificates.crt' // NetBSD {$endif OSLINUXANDROID} ]); scsCA: begin files := StringFromFolders([ {$ifdef OSLINUXANDROID} '/etc/ssl/certs', // Debian/SLES10/SLES11 '/etc/pki/tls/certs', // Fedora/RHEL '/system/etc/security/cacerts' // Android {$else} '/etc/ssl/certs', // FreeBSD 12.2+ '/usr/local/share/certs', // FreeBSD '/etc/openssl/certs' // NetBSD {$endif OSLINUXANDROID} ]); for f := 0 to length(files) - 1 do if (PosEx('-----BEGIN', files[f]) <> 0) and IsAnsiCompatible(files[f]) and (PosEx(files[f], result) = 0) then // append PEM files once result := result + #10 + files[f]; end; end; end; const // on POSIX, we store the SMBIOS data in a local cache for non-root users SMB_CACHE = '/var/tmp/.synopse.smb'; SMB_FLAGS = $010003ff; // assume 3.0 SMB is good enough // local storage of fallback UUID UUID_CACHE = '/var/tmp/.synopse.uid'; // note: /var/tmp is cleaned up by systemd after 30 days so we set S_ISVTX // see https://systemd.io/TEMPORARY_DIRECTORIES {$ifdef CPUINTEL} const // potential location of the SMBIOS buffer pointers within a 64KB fixed frame SMB_START = $000f0000; SMB_STOP = $00100000; function GetSmbEfiMem: RawByteString; forward; // Linux/BSD dedicated versions function SearchSmbios(const mem: RawByteString; var info: TRawSmbiosInfo): PtrUInt; forward; // implemented later in mormot.core.os.pas function GetRawSmbiosFromMem(var info: TRawSmbiosInfo): boolean; var mem: RawByteString; addr: PtrUInt; {$ifdef OSLINUX} fromsysfs: boolean; {$endif OSLINUX} begin result := false; {$ifdef OSLINUX} // on Linux, first try from sysfs tables fromsysfs := false; mem := StringFromFile('/sys/firmware/dmi/tables/smbios_entry_point', true); if mem <> '' then fromsysfs := true else {$endif OSLINUX} // then try to read system EFI entries mem := GetSmbEfiMem; if mem = '' then // last fallback to raw memory reading (won't work on modern/EFI systems) mem := ReadSystemMemory(SMB_START, SMB_STOP - SMB_START); if mem = '' then exit; addr := SearchSmbios(mem, info); if addr = 0 then exit; {$ifdef OSLINUX} if fromsysfs then info.data := StringFromFile('/sys/firmware/dmi/tables/DMI', {nosize=}true) else {$endif OSLINUX} info.data := ReadSystemMemory(addr, info.Length); result := info.data <> ''; end; {$endif CPUINTEL} function _GetRawSmbios(var info: TRawSmbiosInfo): boolean; begin {$ifdef CPUINTEL} result := GetRawSmbiosFromMem(info); if result then exit; {$else} // do not mess with low-level RAM buffer scanning on ARM/AARCH64 result := false; // untested and reported as clearly faulty on some platforms {$endif CPUINTEL} PCardinal(@info)^ := SMB_FLAGS; // mark as retrieved from cache info.Data := StringFromFile(SMB_CACHE); // cache is better than PosixInject if (info.Data <> '') and (CompressSynLZ(info.Data, false) = '') then info.Data := ''; // tampered file if info.Data = '' then if Assigned(PosixInject.GetSmbiosData) then begin info.Data := PosixInject.GetSmbiosData; // e.g. from mormot.core.os.mac if info.Data <> '' then PCardinal(@info)^ := SMB_FLAGS - 1; // mark retrieved from PosixInject end; if info.Data = '' then exit; info.Length := length(info.Data); result := true; end; procedure PosixInjectSmbiosInfo(var info: TSmbiosBasicInfos); var i: TSmbiosBasicInfo; begin if Assigned(PosixInject.GetSmbios) then // e.g. from mormot.core.os.mac for i := succ(low(i)) to high(i) do if info[i] = '' then info[i] := PosixInject.GetSmbios(i); end; procedure _AfterDecodeSmbios(var info: TRawSmbiosInfo); var s: RawByteString; begin // complete information e.g. from mormot.core.os.mac if Assigned(PosixInject.GetSmbios) then PosixInjectSmbiosInfo(_Smbios); // check if require persistence after some HW changes if (PCardinal(@info)^ = SMB_FLAGS) or (CompressSynLZGetHash32(StringFromFile(SMB_CACHE)) = Hash32(info.Data)) then exit; // cache raw SMBIOS data for non-root users s := info.Data; CompressSynLZ(s, true); // SynLZ + Hash32 to avoid tampered file FileFromString(s, SMB_CACHE); FileSetSticky(SMB_CACHE); DeleteFile(UUID_CACHE); // this file is now superfluous and maybe inconsistent end; function SeemsRealPointer(p: pointer): boolean; begin // let the GPF happen silently in the kernel (validated on Linux only) result := (PtrUInt(p) > 65535) and (fpaccess(p, F_OK) <> 0) and (fpgeterrno <> ESysEFAULT); end; const DT_UNKNOWN = 0; // need to call fpstat() if this is returned (depends on FS) DT_FIFO = 1; DT_CHR = 2; DT_DIR = 4; DT_BLK = 6; DT_REG = 8; DT_LNK = 10; DT_SOCK = 12; DT_WHT = 14; function PosixFileNames(const Folder: TFileName; Recursive: boolean): TRawUtf8DynArray; var n: PtrInt; root: TFileName; procedure DoFolder(const subpath: TFileName); var d: pDir; e: pDirent; pl, el: PtrInt; fn: RawUtf8; begin d := FpOpendir(root + subpath); // (much) faster alternative to FindFirst() if d = nil then exit; pl := length(subpath); if pl <> 0 then inc(pl); repeat e := FpReaddir(d^); // FPC RTL use getdents64 syscall on Linux and BSD :) if e = nil then break; // fn := [subpath + '/'] + e^.d_name el := StrLen(@e^.d_name); FastSetString(fn, pl + el); if pl <> 0 then begin MoveFast(pointer(subpath)^, pointer(fn)^, pl - 1); PByteArray(fn)[pl - 1] := ord('/'); end; MoveFast(e^.d_name, PByteArray(fn)[pl], el); // handle this entry case e.d_type of DT_UNKNOWN, // assume modern FS over BSD or Linux Kernel >= 2.6.4 DT_REG: begin if n = 0 then // generous initial result capacity SetLength(result, 128) else if n = length(result) then SetLength(result, NextGrow(n)); result[n] := fn; inc(n); end; DT_DIR: if Recursive and (e^.d_name[0] <> '.') then DoFolder(fn); end; until false; FpClosedir(d^); end; begin result := nil; n := 0; root := IncludeTrailingPathDelimiter(Folder); DoFolder(''); if n <> 0 then DynArrayFakeLength(result, n); end; {$ifdef OSBSDDARWIN} function IsValidPid(pid: cardinal): boolean; begin result := pid <> 0; end; function EnumAllProcesses: TCardinalDynArray; begin result := nil; // not implemented yet on BSD/Darwin // - fpsysctl with CTL_KERN + KERN_PROC + KERN_PROC_ALL is highly OS dependent // and headers are over-complicated so almost impossible to safely use in FPC: // https://github.com/apple-opensource/xnu/blob/master/bsd/sys/sysctl.h#L975 // https://stackoverflow.com/a/6945542/458259 // - kvm_openfiles / kvm_getprocs may be a good option: // https://kaashif.co.uk/2015/06/18/how-to-get-a-list-of-processes-on-openbsd-in-c end; function EnumProcessName(PID: cardinal): RawUtf8; begin result := ''; // not implemented yet on BSD/Darwin // use fpsysctl with CTL_KERN + KERN_PROC + KERN_PROC_PID // https://man.openbsd.org/sysctl.2#KERN_PROC_PID // another trouble is that the name is likely to be truncated to 16 chars // because it is defined p_comm[MAXCOMLEN + 1] in the very complex headers end; function _IsDebuggerPresent: boolean; begin // rough detection for FPC on BSD (not yet working because of EnumProcessName) result := PosEx('lazarus', LowerCase(EnumProcessName(FpGetppid))) <> 0; end; function GetParentProcess(PID: cardinal): cardinal; begin if PID = 0 then result := FpGetppid // we have a system call for the currrent process :) else result := 0; // not implemented yet on BSD/Darwin end; function fpsysctlhwint(hwid: cint): Int64; var mib: array[0..1] of cint; len: cint; begin result := 0; mib[0] := CTL_HW; mib[1] := hwid; len := SizeOf(result); fpsysctl(pointer(@mib), 2, @result, @len, nil, 0); end; function fpsysctlhwstr(hwid: cint; var temp: ShortString): PUtf8Char; var mib: array[0..1] of cint; len: cint; begin mib[0] := CTL_HW; mib[1] := hwid; FillCharFast(temp, SizeOf(temp), 0); // ShortString as 0-terminated buffer len := SizeOf(temp); fpsysctl(pointer(@mib), 2, @temp, @len, nil, 0); if temp[0] <> #0 then result := @temp else result := nil; end; function fpsysctlbynamehwstr(name: PAnsiChar; var temp: ShortString): PUtf8Char; var len: cint; begin FillCharFast(temp, SizeOf(temp), 0); // ShortString as 0-terminated buffer len := SizeOf(temp); FPsysctlbyname(name, @temp, @len, nil, 0); if temp[0] <> #0 then result := @temp else result := nil; end; type TLoadAvg = array[0..2] of double; function getloadavg(var loadavg: TLoadAvg; nelem: integer): integer; cdecl external clib name 'getloadavg'; function RetrieveLoadAvg: RawUtf8; var avg: TLoadAvg; begin if getloadavg(avg, 3) = 3 then result := _fmt('%g %g %g', [avg[0], avg[1], avg[2]]) else result := ''; end; {$ifdef OSFREEBSD} const KENV_GET = 0; KENV_SET = 1; function kenv(action: integer; name, value: PAnsiChar; len: integer): integer; cdecl external clib name 'kenv'; function GetSmbEfiMem: RawByteString; var tmp: array[byte] of AnsiChar; xaddr: PtrUInt; begin result := ''; if kenv(KENV_GET, 'hint.smbios.0.mem', @tmp, SizeOf(tmp)) < 0 then exit; xaddr := PosixParseHex32(@tmp); // typical value is '0xf05b0' if xaddr <> 0 then result := ReadSystemMemory(xaddr, 1024); // 32 bytes is enough end; const _KNOWN: array[0..14] of record id: TSmbiosBasicInfo; fn: RawUtf8; end = ( (id: sbiBiosVendor; fn: 'bios.vendor'), (id: sbiBiosVersion; fn: 'bios.version'), (id: sbiBiosDate; fn: 'bios.reldate'), (id: sbiManufacturer; fn: 'system.maker'), (id: sbiProductName; fn: 'system.product'), (id: sbiVersion; fn: 'system.version'), (id: sbiSerial; fn: 'system.serial'), (id: sbiUuid; fn: 'system.uuid'), (id: sbiSku; fn: 'system.sku'), (id: sbiFamily; fn: 'system.family'), (id: sbiBoardManufacturer; fn: 'planar.maker'), (id: sbiBoardProductName; fn: 'planar.product'), (id: sbiBoardVersion; fn: 'planar.version'), (id: sbiBoardSerial; fn: 'planar.serial'), (id: sbiBoardAssetTag; fn: 'planar.tag') ); procedure _DirectSmbiosInfo(out info: TSmbiosBasicInfos); var i: PtrInt; tmp: array[byte] of AnsiChar; begin for i := 0 to high(_KNOWN) do with _KNOWN[i] do if kenv(KENV_GET, PAnsiChar('smbios.' + fn), @tmp, SizeOf(tmp)) >= 0 then info[id] := TrimU(tmp); end; {$else} // help is needed to implement those on buggy Mac OS // may fallback to PosixInject wrappers from mormot.core.os.mac function GetSmbEfiMem: RawByteString; begin result := ''; end; {$ifdef OSDARWIN} function ReadSystemMemory(address, size: PtrUInt): RawByteString; begin result := ''; end; {$endif OSDARWIN} procedure _DirectSmbiosInfo(out info: TSmbiosBasicInfos); begin end; {$endif OSFREEBSD} function GetMemoryInfo(out info: TMemoryInfo; withalloc: boolean): boolean; begin FillCharFast(info, SizeOf(info), 0); info.memtotal := SystemMemorySize; // retrieved at startup info.memfree := info.memtotal - fpsysctlhwint(HW_USERMEM); result := info.memtotal <> 0;// avoid div per 0 exception if result then info.percent := ((info.memtotal - info.memfree) * 100) div info.memtotal; end; procedure XorOSEntropy(var e: THash512Rec); var mem: TMemoryInfo; avg: TLoadAvg absolute mem; us: Int64 absolute mem.vmtotal; // =0 after above GetMemoryInfo() guid: THash128Rec absolute mem.filetotal; // also = 0 begin //some minimal OS entropy we could get for BSD/Darwin QueryPerformanceMicroSeconds(us); e.i[0] := e.i[0] xor us; e.i[1] := e.i[1] xor GetTickCount64; getloadavg(avg, 3); DefaultHasher128(@e.h1, @avg, SizeOf(avg)); // may be AesNiHash128 GetMemoryInfo(mem, {withalloc=}false); DefaultHasher128(@e.h2, @mem, SizeOf(mem)); {$ifdef OSDARWIN} // FPC CreateGuid calls /dev/urandom which is not advised guid.Lo := mach_absolute_time; // monotonic clock in nanoseconds guid.Hi := mach_continuous_time; {$else} CreateGuid(guid.guid); // use e.g. FreeBSD syscall or /dev/urandom {$endif OSDARWIN} QueryPerformanceMicroSeconds(us); // should have changed in-between crcblocks(@e.h3, @mem, SizeOf(mem) shr 4); // another algo end; {$else} // Linux-specific code function IsValidPid(pid: cardinal): boolean; var status, tgid: RawUtf8; begin result := false; if pid = 0 then exit; status := StringFromFile('/proc/' + IntToStr(pid) + '/status', {nosize=}true); // ensure is a real process, not a thread // https://www.memsql.com/blog/the-curious-case-of-thread-groups-identifiers FindNameValue(status, 'TGID:', tgid); result := GetCardinal(pointer(tgid)) = pid; end; function EnumAllProcesses: TCardinalDynArray; var d: pDir; e: pDirent; n: integer; pid: cardinal; begin result := nil; d := FpOpendir('/proc'); // (much) faster alternative to FindFirst() if d = nil then exit; n := 0; SetLength(result, 128); repeat e := FpReaddir(d^); // FPC RTL uses direct getdents syscall on Linux/BSD :) if e = nil then break; if (e.d_type in [DT_UNKNOWN, DT_DIR]) and (e.d_name[0] in ['1'..'9']) then begin pid := GetCardinal(@e.d_name[0]); if (pid <> 0) and IsValidPid(pid) then AddInteger(TIntegerDynArray(result), n, pid); end; until false; FpClosedir(d^); if n = 0 then result := nil else DynArrayFakeLength(result, n); end; var tryprocexe: boolean = true; function EnumProcessName(PID: cardinal): RawUtf8; var proc: TFileName; cmdline: RawUtf8; begin proc := '/proc/' + IntToStr(PID); if tryprocexe then begin // need to be root to follow /proc/[pid]/exe result := fpReadLink(proc + '/exe'); if result <> '' then exit; end; cmdline := StringFromFile(proc + '/cmdline', {nosize=}true); // set of strings separated by null bytes -> exe is the first argument FastSetString(result, pointer(cmdline), StrLen(pointer(cmdline))); if result <> '' then tryprocexe := false; // no need to try again next time end; function GetParentProcess(PID: cardinal): cardinal; var status, ppid: RawUtf8; begin if PID = 0 then result := FpGetppid // we have a system call for the current process :) else begin result := 0; status := StringFromFile('/proc/' + IntToStr(PID) + '/status', {nosize=}true); if status = '' then exit; // no such process FindNameValue(status, 'PPID:', ppid); result := GetCardinal(pointer(ppid)); end; end; function _IsDebuggerPresent: boolean; var status, tracerpid: RawUtf8; begin status := StringFromFile('/proc/self/status', {nosize=}true); FindNameValue(status, 'TRACERPID:', tracerpid); result := (tracerpid <> '0'); end; function RetrieveLoadAvg: RawUtf8; begin // the libc parses this file anyway :) result := TrimU(StringFromFile('/proc/loadavg', {nosize=}true)); end; function FindMemInfo(const meminfo, up: RawUtf8): PtrUInt; var v: RawUtf8; begin FindNameValue(meminfo, up, v); result := GetCardinal(pointer(v)) shl 10; // from KB to bytes end; function GetMemoryInfo(out info: TMemoryInfo; withalloc: boolean): boolean; var proc: RawUtf8; P: PUtf8Char; begin result := false; FillCharFast(info, SizeOf(info), 0); // sysinfo() syscall has not enough information: use /proc sysfiles proc := StringFromFile('/proc/meminfo', {hasnosize=}true); if proc = '' then exit; info.memtotal := FindMemInfo(proc, 'MEMTOTAL:'); info.memfree := FindMemInfo(proc, 'MEMAVAILABLE:'); // MemFree is too low info.filetotal := FindMemInfo(proc, 'SWAPTOTAL:'); info.filefree := FindMemInfo(proc, 'SWAPFREE:'); // note: Windows-like virtual memory information is not available under Linux info.vmtotal := FindMemInfo(proc, 'COMMITLIMIT:'); info.vmfree := FindMemInfo(proc, 'MEMFREE:'); if info.memfree = 0 then // kernel < 3.14 may not have the MemAvailable field info.memfree := info.vmfree + FindMemInfo(proc, 'BUFFERS:') + FindMemInfo(proc, 'CACHED:') + FindMemInfo(proc, 'SRECLAIMABLE:') - FindMemInfo(proc, 'SHMEM:'); if info.memtotal <> 0 then begin info.percent := ((info.memtotal - info.memfree) * 100) div info.memtotal; result := true; end; if not withalloc then exit; // GetHeapStatus is only about current thread -> use /proc/[pid]/statm proc := StringFromFile('/proc/self/statm', {hasnosize=}true); P := pointer(proc); info.allocreserved := GetNextCardinal(P) * SystemInfo.dwPageSize; // VmSize info.allocused := GetNextCardinal(P) * SystemInfo.dwPageSize; // VmRSS end; procedure DoHash128File(var h: THash128; const filename: TFileName); var s: RawByteString; begin s := StringFromFile(filename, {nosize=}true); DefaultHasher128(@h, pointer(s), length(s)); // maybe AesNiHash128 end; procedure XorOSEntropy(var e: THash512Rec); var si: TSysInfo; // Linuxism rt: TTimeSpec; // with nanoseconds resolution begin clock_gettime(CLOCK_MONOTONIC_HIRES, @rt); DefaultHasher128(@e.h0, @rt, SizeOf(rt)); // maybe AesNiHash128 SysInfo(@si); // uptime + loadavg + meminfo + numprocess DefaultHasher128(@e.h0, @si, SizeOf(si)); // detailed CPU execution context and timing from Linux kernel DoHash128File(e.h0, '/proc/self/statm'); DoHash128File(e.h0, '/proc/self/stat'); DoHash128File(e.h1, '/proc/stat'); clock_gettime(CLOCK_UPTIME, @rt); DefaultHasher128(@e.h2, @rt, SizeOf(rt)); // maybe AesNiHash128 // read-only 122-bit random UUID text '6fd5a44b-35f4-4ad4-a9b9-6b9be13e1fe9' DoHash128File(e.h2, '/proc/sys/kernel/random/uuid'); DoHash128File(e.h3, '/proc/sys/kernel/random/boot_id'); clock_gettime(CLOCK_MONOTONIC_HIRES, @rt); // should have changed in-between DefaultHasher128(@e.h3, @rt, SizeOf(rt)); end; {$ifdef OSANDROID} procedure _DirectSmbiosInfo(out info: TSmbiosBasicInfos); begin end; {$else} // pure Linux const // note: reading some of the /sys/class/dmi/id/* files may require root access _KNOWN: array[0..15] of record id: TSmbiosBasicInfo; fn: string; end = ( (id: sbiBiosVendor; fn: 'bios_vendor'), (id: sbiBiosVersion; fn: 'bios_version'), (id: sbiBiosDate; fn: 'bios_date'), (id: sbiBiosRelease; fn: 'bios_release'), (id: sbiManufacturer; fn: 'sys_vendor'), (id: sbiProductName; fn: 'product_name'), (id: sbiVersion; fn: 'product_version'), (id: sbiSerial; fn: 'product_serial'), (id: sbiUuid; fn: 'product_uuid'), (id: sbiSku; fn: 'product_sku'), (id: sbiFamily; fn: 'product_family'), (id: sbiBoardManufacturer; fn: 'board_vendor'), (id: sbiBoardProductName; fn: 'board_name'), (id: sbiBoardVersion; fn: 'board_version'), (id: sbiBoardSerial; fn: 'board_serial'), (id: sbiBoardAssetTag; fn: 'board_asset_tag') ); procedure _DirectSmbiosInfo(out info: TSmbiosBasicInfos); var i: PtrInt; begin for i := 0 to high(_KNOWN) do with _KNOWN[i] do info[id] := TrimU(StringFromFile('/sys/class/dmi/id/' + fn, {nosize=}true)); // note: /var/lib/dbus/machine-id and /etc/machine-id are SW generated from // random at system install so do NOT match sbiUuid HW DMI value - see // https://www.freedesktop.org/software/systemd/man/machine-id.html end; {$endif OSANDROID} {$endif OSBSDDARWIN} var __IsDebuggerPresent: (idpUntested, idpNone, idpPresent); function IsDebuggerPresent: boolean; begin if __IsDebuggerPresent = idpUntested then if _IsDebuggerPresent then __IsDebuggerPresent := idpPresent else __IsDebuggerPresent := idpNone; result := __IsDebuggerPresent = idpPresent; end; {$ifndef OSDARWIN} // on POSIX systems, /dev/mem may be available from root // but sometimes even root can't access it on hardened systems function ReadSystemMemory(address, size: PtrUInt): RawByteString; var mem: cInt; map: PAnsiChar; off: PtrUInt; begin result := ''; if size > 4 shl 20 then exit; // read up to 4MB mem := FpOpen('/dev/mem', O_RDONLY, 0); if mem <= 0 then exit; // Fpmmap() is more complex but works around problems using plain read() calls off := address mod SystemInfo.dwPageSize; map := Fpmmap(nil, off + size, PROT_READ, MAP_SHARED, mem, address - off); if map <> MAP_FAILED then begin FastSetRawByteString(result, map + off, size); Fpmunmap(map, off + size); end; FpClose(mem); end; {$endif OSDARWIN} procedure DirectSmbiosInfo(out info: TSmbiosBasicInfos); begin // retrieve OS-dependent information _DirectSmbiosInfo(info); // normalize some entries info[sbiUuid] := LowerCase(info[sbiUuid]); // some missing info may have retrieved at startup of this unit if info[sbiCpuVersion] = '' then info[sbiCpuVersion] := CpuInfoText; // e.g. from mormot.core.os.mac if Assigned(PosixInject.GetSmbios) then PosixInjectSmbiosInfo(info); end; function RetrieveSystemTimes(out IdleTime, KernelTime, UserTime: Int64): boolean; begin result := false; // should call e.g. RetrieveLoadAvg() instead end; function RetrieveProcessInfo(PID: cardinal; out KernelTime, UserTime: Int64; out WorkKB, VirtualKB: cardinal): boolean; begin result := false; end; function TProcessInfo.Init: boolean; begin FillCharFast(self, SizeOf(self), 0); result := false; end; function TProcessInfo.Start: boolean; begin result := false; end; function TProcessInfo.PerProcess(PID: cardinal; Now: PDateTime; out Data: TSystemUseData; var PrevKernel, PrevUser: Int64): boolean; begin result := false; end; function TProcessInfo.PerSystem(out Idle, Kernel, User: single): boolean; var P: PUtf8Char; U, K, I, S: cardinal; begin // see http://www.linuxhowtos.org/System/procstat.htm result := false; P := pointer(StringFromFile('/proc/stat', {nosize=}true)); if P = nil then exit; // e.g. 'cpu 3418147 18140 265232 6783435 12184 0 34219 0 0 0' U := GetNextCardinal(P){=user} + GetNextCardinal(P){=nice}; K := GetNextCardinal(P){=system}; I := GetNextCardinal(P){=idle}; S := U + K + I; result := S <> 0; if not result then exit; Kernel := {%H-}SimpleRoundTo2Digits((K * 100) / S); User := {%H-}SimpleRoundTo2Digits((U * 100) / S); Idle := 100 - Kernel - User; // ensure sum is always 100% end; { TODO : use a diff approach for TProcessInfo.PerSystem on Linux? } function FillSystemRandom(Buffer: PByteArray; Len: integer; AllowBlocking: boolean): boolean; var rd, dev: integer; begin result := false; if Len <= 0 then exit; dev := FileOpenSequentialRead('/dev/urandom'); // non blocking on Linux + BSD if (dev <= 0) and AllowBlocking then dev := FileOpenSequentialRead('/dev/random'); // may block until got entropy if dev > 0 then try rd := 32; // read up to 256 bits - see "man urandom" Usage paragraph if Len <= 32 then rd := Len; result := (FileRead(dev, Buffer[0], rd) = rd); if result and (Len > 32) then RandomBytes(@Buffer[32], Len - 32); // simple gsl_rng_taus2 padding finally FileClose(dev); end; if not result then // OS API call failed -> fallback to our Lecuyer's gsl_rng_taus2 generator RandomBytes(pointer(Buffer), Len); end; function GetDiskInfo(var aDriveFolderOrFile: TFileName; out aAvailableBytes, aFreeBytes, aTotalBytes: QWord): boolean; var fs: tstatfs; begin if aDriveFolderOrFile = '' then aDriveFolderOrFile := '.'; FillCharFast(fs, SizeOf(fs), 0); result := fpStatFS(aDriveFolderOrFile, @fs) = 0; aAvailableBytes := QWord(fs.bavail) * QWord(fs.bsize); aFreeBytes := aAvailableBytes; // no user Quota involved here aTotalBytes := QWord(fs.blocks) * QWord(fs.bsize); end; function GetDiskPartitions: TDiskPartitions; var mounts, fs, mnt, typ: RawUtf8; p: PUtf8Char; fn: TFileName; n: integer; av, fr, tot: QWord; begin // see https://github.com/gagern/gnulib/blob/master/lib/mountlist.c result := nil; {$ifdef OSLINUXANDROID} mounts := StringFromFile('/proc/self/mounts', {hasnosize=}true); if mounts = '' then {$endif OSLINUXANDROID} mounts := StringFromFile('/etc/mtab', {hasnosize=}true); n := 0; p := pointer(mounts); if p <> nil then // e.g. Darwin has no /etc/mtab :( repeat fs := GetNextItem(p); mnt := GetNextItem(p); typ := GetNextItem(p); if (fs <> '') and (fs <> 'rootfs') and not IdemPChar(pointer(fs), '/DEV/LOOP') and (mnt <> '') and (mnt <> '/mnt') and (typ <> '') and not IdemPChars(mnt, ['/PROC/', '/SYS/', '/RUN/']) and not IdemPChars(typ, ['AUTOFS', 'PROC', 'SUBFS', 'DEBUGFS', 'DEVPTS', 'FUSECTL', 'MQUEUE', 'RPC-PIPEFS', 'SYSFS', 'DEVFS', 'KERNFS', 'IGNORE', 'NONE', 'TMPFS', 'SECURITYFS', 'RAMFS', 'ROOTFS', 'DEVTMPFS', 'HUGETLBFS', 'ISO9660']) then begin fn := mnt; if GetDiskInfo(fn, av, fr, tot) and (tot > 1 shl 20) then begin //writeln('fs=',fs,' mnt=',mnt,' typ=',typ, ' av=',av,' fr=',fr,' tot=',tot); if n = length(result) then SetLength(result, NextGrow(n)); if length(fs) > 24 then fs := copy(fs, 1, 24) + '..'; result[n].name := fs; result[n].mounted := fn; result[n].size := tot; inc(n); end; end; p := GotoNextLine(p); until p = nil; SetLength(result, n); end; {$ifdef OSBSDDARWIN} {$define USEMPROTECT} {$else} {$ifdef OSANDROID} {$define USEMPROTECT} {$endif OSANDROID} {$endif OSBSDDARWIN} {$ifdef USEMPROTECT} function mprotect(Addr: Pointer; Len: size_t; Prot: integer): integer; cdecl external clib name 'mprotect'; {$endif USEMPROTECT} function SynMProtect(addr: pointer; size: size_t; prot: integer): integer; begin result := -1; {$ifdef UNIX} {$ifdef USEMPROTECT} result := mprotect(addr, size, prot); {$else} if Do_SysCall(syscall_nr_mprotect, TSysParam(addr), size, prot) >= 0 then result := 0; {$endif USEMPROTECT} {$endif UNIX} end; procedure PatchCode(Old, New: pointer; Size: PtrInt; Backup: pointer; LeaveUnprotected: boolean); var PageSize: PtrUInt; AlignedAddr: pointer; i: PtrInt; ProtectedResult, ProtectedMemory: boolean; begin if Backup <> nil then for i := 0 to Size - 1 do // do not use Move() here PByteArray(Backup)^[i] := PByteArray(Old)^[i]; PageSize := SystemInfo.dwPageSize; AlignedAddr := Pointer((PtrUInt(Old) div SystemInfo.dwPageSize) * SystemInfo.dwPageSize); while PtrUInt(Old) + PtrUInt(Size) >= PtrUInt(AlignedAddr) + PageSize do inc(PageSize, SystemInfo.dwPageSize); ProtectedResult := SynMProtect( AlignedAddr, PageSize, PROT_READ or PROT_WRITE or PROT_EXEC) = 0; ProtectedMemory := not ProtectedResult; if ProtectedMemory then ProtectedResult := SynMProtect( AlignedAddr, PageSize, PROT_READ or PROT_WRITE) = 0; if ProtectedResult then try for i := 0 to Size - 1 do // do not use Move() here PByteArray(Old)^[i] := PByteArray(New)^[i]; if not LeaveUnprotected and ProtectedMemory then SynMProtect(AlignedAddr, PageSize, PROT_READ or PROT_EXEC); except // we ignore any exception here - it should work anyway end; end; const STUB_SIZE = 65536; // 16*4 KB (4 KB = memory granularity) // on most platforms, Compute_FAKEVMT is run once with all JITted stubs // on i386, it needs ArgsSizeInStack adjustement, but only 24 bytes per method {$ifdef CPUARM} var StubCallAllocMemLastStart: PtrUInt; // avoid unneeded fpmmap() calls function StubCallAllocMem(const Size, flProtect: DWORD): pointer; const STUB_RELJMP = {$ifdef CPUARM} $7fffff {$else} $7fffffff {$endif}; // rel jmp STUB_INTERV = STUB_RELJMP + 1; // try to reserve in closed stub interval STUB_ALIGN = QWord($ffffffffffff0000); // align to STUB_SIZE var start, stop, stub, dist: PtrUInt; begin stub := PtrUInt(ArmFakeStubAddr); // = @TInterfacedObjectFake.ArmFakeStub if StubCallAllocMemLastStart <> 0 then start := StubCallAllocMemLastStart else begin start := stub - STUB_INTERV; if start > stub then start := 0; // avoid range overflow start := start and STUB_ALIGN; end; stop := stub + STUB_INTERV; if stop < stub then stop := high(PtrUInt); stop := stop and STUB_ALIGN; while start < stop do begin // try whole -STUB_INTERV..+STUB_INTERV range inc(start, STUB_SIZE); result := fpmmap(pointer(start), STUB_SIZE, flProtect, MAP_PRIVATE or MAP_ANONYMOUS, -1, 0); if result <> MAP_FAILED then begin // close enough for a 24/32-bit relative jump? dist := abs(stub - PtrUInt(result)); if dist < STUB_RELJMP then begin StubCallAllocMemLastStart := start; exit; end else fpmunmap(result, STUB_SIZE); end; end; result := MAP_FAILED; // error end; {$else} // other platforms (Intel+Arm64) use plain Kernel call and PtrInt jump function StubCallAllocMem(const Size, flProtect: DWORD): pointer; begin result := fpmmap(nil, STUB_SIZE, flProtect, MAP_PRIVATE OR MAP_ANONYMOUS, -1, 0); end; {$endif CPUARM} { ****************** Unix Daemon and Windows Service Support } // Linux/POSIX signal interception var SynDaemonIntercepted: boolean; SynDaemonInterceptLog: TSynLogProc; procedure DoShutDown(Sig: integer; Info: PSigInfo; Context: PSigContext); cdecl; var level: TSynLogLevel; si_code: integer; text: string[4]; // code below has no memory allocation begin if Assigned(SynDaemonInterceptLog) then begin case Sig of SIGQUIT: text := 'QUIT'; SIGTERM: text := 'TERM'; SIGINT: text := 'INT'; SIGABRT: text := 'ABRT'; else text := 'SIG'; end; if Sig = SIGTERM then // polite quit level := sllInfo else // abort after panic level := sllExceptionOS; if Info = nil then si_code := 0 else si_code := Info^.si_code; SynDaemonInterceptLog(level, 'SynDaemonIntercepted received SIG%=% si_code=%', [text, Sig, si_code], nil); end; SynDaemonTerminated := Sig; end; procedure SynDaemonIntercept(const onlog: TSynLogProc); var sa: SigactionRec; begin // note: SIGFPE/SIGSEGV/SIGBUS/SIGILL are handled by the RTL if SynDaemonIntercepted then exit; GlobalLock; try if SynDaemonIntercepted then exit; SynDaemonInterceptLog := onlog; FillCharFast(sa, SizeOf(sa), 0); sa.sa_handler := @DoShutDown; fpSigaction(SIGQUIT, @sa, nil); fpSigaction(SIGTERM, @sa, nil); fpSigaction(SIGINT, @sa, nil); fpSigaction(SIGABRT, @sa, nil); SynDaemonIntercepted := true; // flag set AFTER interception finally GlobalUnLock; end; end; var SigPipeDisabled: boolean; // process-wide thread-safe flag // TO INVESTIGATE: we may use per-thread signal masking instead // http://www.microhowto.info/howto/ignore_sigpipe_without_affecting_other_threads_in_a_process.html procedure DoNothing(Sig: integer; Info: PSigInfo; Context: PSigContext); cdecl; begin end; procedure SigPipeIntercept; var sa: SigactionRec; begin if SigPipeDisabled then exit; // quickly return if already done GlobalLock; try if SigPipeDisabled then exit; FillCharFast(sa, SizeOf(sa), 0); sa.sa_handler := @DoNothing; fpSigaction(SIGPIPE, @sa, nil); SigPipeDisabled := true; // flag set AFTER disabling it finally GlobalUnLock; end; end; type TPasswd = record pw_name: PAnsiChar; // user name pw_passwd: PAnsiChar; // encrypted password pw_uid: TUid; // user uid pw_gid: TGid; // user gid // following fields are not consistent on BSD or Linux, but not needed end; PPasswd = ^TPasswd; // retrieve information of a given user by name function getpwnam(name: PAnsiChar): PPasswd; cdecl external clib name 'getpwnam'; // sets the supplementary group IDs for the calling process function setgroups(n: size_t; groups: PGid): integer; cdecl external clib name 'setgroups'; function setuid(uid: TUid): integer; cdecl external clib name 'setuid'; function setgid(gid: TGid): integer; cdecl external clib name 'setgid'; // changes the root directory of the calling process function chroot(rootpath: PAnsiChar): integer; cdecl external clib name 'chroot'; function DropPriviledges(const UserName: RawUtf8): boolean; var pwnam: PPasswd; begin result := false; pwnam := getpwnam(pointer(UserName)); if (pwnam = nil) or ((setgid(pwnam.pw_gid) <> 0) and (fpgeterrno <> ESysEPERM)) or ((setuid(pwnam.pw_uid) <> 0) and (fpgeterrno <> ESysEPERM)) then exit; result := true; end; function ChangeRoot(const FolderName: RawUtf8): boolean; begin result := (FolderName <> '') and (FpChdir(pointer(FolderName)) = 0) and (chroot('.') = 0); end; function RunUntilSigTerminatedPidFile(ensureWritable: boolean): TFileName; var pidpath: TFileName; begin pidpath := RunUntilSigTerminatedPidFilePath; if pidpath = '' then pidpath := Executable.ProgramFilePath; if not ensureWritable then begin result := Format('%s.%s.pid', [pidpath, Executable.ProgramName]); if FileExists(result) then exit; end; if not IsDirectoryWritable(pidpath) then // if the executable folder is not writable, use the temporary folder pidpath := GetSystemPath(spTemp); result := Format('%s.%s.pid', [pidpath, Executable.ProgramName]); end; function RunUntilSigTerminatedState: TServiceState; begin if FileExists(RunUntilSigTerminatedPidFile(false)) then result := ssRunning else result := ssStopped; end; function RunUntilSigTerminatedForKill(waitseconds: integer): boolean; var pid: PtrInt; pidfilename: TFileName; tix: Int64; begin result := false; pidfilename := RunUntilSigTerminatedPidFile; pid := GetInteger(pointer(StringFromFile(pidfilename))); if pid <= 0 then exit; if fpkill(pid, SIGTERM) <> 0 then // polite quit if fpgeterrno <> ESysESRCH then exit else // ESysESRCH = no such process -> try to delete the .pid file if DeleteFile(pidfilename) then begin result := true; // process crashed or hard reboot -> nothing to kill exit; end; if waitseconds <= 0 then begin result := true; exit; end; tix := GetTickCount64 + waitseconds * MilliSecsPerSec; repeat // RunUntilSigTerminated() below should delete the .pid file sleep(10); if not FileExists(pidfilename) then result := true; until result or (GetTickCount64 > tix); if not result then fpkill(pid, SIGKILL); // murder with finesse end; procedure CleanAfterFork; begin fpUMask(0); // reset file mask chdir('/'); // avoid locking current directory Close(input); AssignFile(input, '/dev/null'); ReWrite(input); Close(output); AssignFile(output, '/dev/null'); ReWrite(output); Close(stderr); end; procedure RunUntilSigTerminated(daemon: TObject; dofork: boolean; const start, stop: TThreadMethod; const onlog: TSynLogProc; const servicename: string); var pid, sid: TPID; pidfilename: TFileName; s: AnsiString; const TXT: array[boolean] of string[4] = ('run', 'fork'); begin SynDaemonIntercept(onlog); if dofork then begin pidfilename := RunUntilSigTerminatedPidFile; pid := GetInteger(pointer(StringFromFile(pidfilename))); if pid > 0 then if (fpkill(pid, 0) = 0) or not DeleteFile(pidfilename) then raise EOSException.CreateFmt( '%s.CommandLine Fork failed: %s is already forked as pid=%d', [ClassNameShort(daemon)^, Executable.ProgramName, PtrInt(pid)]); pid := fpFork; if pid < 0 then raise EOSException.CreateFmt( '%s.CommandLine Fork failed', [ClassNameShort(daemon)^]); if pid > 0 then // main program - just terminate exit; // clean forked instance sid := fpSetSID; if sid < 0 then // new session (process group) created? raise EOSException.CreateFmt( '%s.CommandLine SetSID failed', [ClassNameShort(daemon)^]); CleanAfterFork; // create local .[Executable.ProgramName].pid file pid := fpgetpid; str(pid, s); FileFromString(s, pidfilename); end; try if Assigned(onlog) then onlog(sllNewRun, 'Start % /% %', [servicename, TXT[dofork], Executable.Version.DetailedOrVoid], nil); Start; while SynDaemonTerminated = 0 do if GetCurrentThreadID = MainThreadID then CheckSynchronize(100) else Sleep(100); finally if Assigned(onlog) then onlog(sllNewRun, 'Stop /% from Sig=%', [TXT[dofork], SynDaemonTerminated], nil); try Stop; finally if dofork and (pidfilename <> '') then begin DeleteFile(pidfilename); if Assigned(onlog) then onlog(sllTrace, 'RunUntilSigTerminated: deleted file %', [pidfilename], nil); end; end; end; end; function RunInternal(args: PPAnsiChar; waitfor: boolean; const env: TFileName; options: TRunOptions): integer; var pid: TPID; e: array[0..511] of PAnsiChar; // max 512 environment variables envpp: PPAnsiChar; P: PAnsiChar; n: PtrInt; begin {$ifdef FPC} {$if (defined(BSD) or defined(SUNOS)) and defined(FPC_USE_LIBC)} pid := FpvFork; {$else} pid := FpFork; {$ifend} {$else} 'only FPC is supported yet'; {$endif FPC} if pid < 0 then begin // fork failed result := -1; exit; end; if pid = 0 then begin // we are in child process -> switch to new executable if not waitfor then // don't share the same console CleanAfterFork; envpp := envp; if env <> '' then begin n := 0; result := -ESysE2BIG; if (roEnvAddExisting in options) and (envpp <> nil) then begin while envpp^ <> nil do begin if PosChar(envpp^, #10) = nil then begin // filter to add only single-line variables if n = high(e) - 1 then exit; e[n] := envpp^; inc(n); end; inc(envpp); end; end; P := pointer(env); // env follows Windows layout 'n1=v1'#0'n2=v2'#0#0 while P^ <> #0 do begin if n = high(e) - 1 then exit; e[n] := P; // makes POSIX compatible inc(n); inc(P, StrLen(P) + 1); end; e[n] := nil; // end with null envpp := @e; end; FpExecve(args^, args, envpp); FpExit(127); end; if waitfor then begin result := WaitProcess(pid); if result = 127 then // execv() failed in child process result := -result; end else // fork success (don't wait for the child process to fail) result := 0; end; function RunProcess(const path, arg1: TFileName; waitfor: boolean; const arg2, arg3, arg4, arg5, env: TFileName; options: TRunOptions): integer; var a: array[0..6] of PAnsiChar; // assume no UNICODE on POSIX, i.e. as TFileName begin a[0] := pointer(path); a[1] := pointer(arg1); a[2] := pointer(arg2); a[3] := pointer(arg3); a[4] := pointer(arg4); a[5] := pointer(arg5); a[6] := nil; // end pointer list with null result := RunInternal(@a, waitfor, env, options); end; function RunCommand(const cmd: TFileName; waitfor: boolean; const env: TFileName; options: TRunOptions; parsed: PParseCommands): integer; var temp: RawUtf8; err: TParseCommands; a: TParseCommandsArgs; begin err := ParseCommandArgs(cmd, @a, nil, @temp); if parsed <> nil then parsed^ := err; if err = [] then // no need to spawn the shell for simple commands result := RunInternal(a, waitfor, env, options) else if err * PARSECOMMAND_ERROR <> [] then // no system call for clearly invalid command line result := -ESysEPERM else begin // execute complex commands via the shell a[0] := '/bin/sh'; a[1] := '-c'; a[2] := pointer(cmd); a[3] := nil; result := RunInternal(@a, waitfor, env, options); end; end; function RunRedirect(const cmd: TFileName; exitcode: PInteger; const onoutput: TOnRedirect; waitfordelayms: cardinal; setresult: boolean; const env, wrkdir: TFileName; options: TRunOptions): RawByteString; var // notes: - FPC popen() allows access to the pid whereas clib popen() won't // - env and options params are not supported by popen() so are ignored f: file; fd: THandle; pid, res, wr: cint; n, l: TSsize; wait: cardinal; endtix: Int64; tmp: array[word] of byte; // 64KB stack buffer function RedirectOutput(flush: boolean; var redir: RawByteString): boolean; var u: RawUtf8; begin result := false; // return false on pipe closed if WaitReadPending(fd, wait) then begin n := fpread(fd, tmp, SizeOf(tmp)); if n < 0 then exit; // pipe closed = execution finished if setresult and (n <> 0) then begin if redir = '' then FastSetString(RawUtf8(redir), @tmp, n) // assume CP_UTF8 else begin SetLength(redir, l + n); // append MoveFast(tmp, PByteArray(redir)[l], n); end; inc(l, n); end; if Assigned(onoutput) then begin FastSetString(u, @tmp, n); // console output is likely UTF-8 on POSIX if onoutput(u, pid) and not flush then endtix := 1; // returned true: force kill() on abort end; end else if Assigned(onoutput) and // idle onoutput('', pid) and not flush then endtix := 1; // returned true to abort -> kill() result := true; end; begin result := ''; if wrkdir <> '' then ChDir(wrkdir); if popen(f, cmd, 'r') <> 0 then // fork and launch cmd - env is ignored by now exit; fd := TFileRec(f).Handle; pid := pcint(@TFileRec(f).userdata[2])^; // see popen() from Unix.pp if Assigned(onoutput) then onoutput('', pid); wait := 200; if waitfordelayms = INFINITE then endtix := 0 else begin endtix := GetTickCount64 + waitfordelayms; if waitfordelayms < wait then wait := waitfordelayms; end; l := 0; repeat if not RedirectOutput({flush=}false, result) then break; // pipe closed = execution finished if (endtix <> 0) and (GetTickCount64 > endtix) then begin // abort process execution after timeout or onoutput()=true if RunAbortTimeoutSecs > 0 then begin // try gracefull death if (ramSigTerm in RunAbortMethods) and (fpkill(pid, SIGTERM) = 0) then begin endtix := GetTickCount64 + RunAbortTimeoutSecs * 1000; repeat wr := FpWaitPid(pid, @res, WNOHANG); // 0 = no state change RedirectOutput({flush=}true, result); // continue redirection if (wr <> 0) or (GetTickCount64 > endtix) then break; SleepHiRes(5); until false; if wr = pid then // <0 for error break; // gracefully ended end; end; // force process termination fpkill(pid, SIGKILL); pid := 0; break; end; until false; res := pclose(f); if exitcode <> nil then if pid = 0 then exitcode^ := -1 else exitcode^ := res; end; { ****************** Gather Operating System Information } {$ifdef OSANDROID} function GetSmbEfiMem: RawByteString; begin result := ''; end; const getpagesize = 4096; {$else} function getpagesize: integer; cdecl external clib name 'getpagesize'; {$endif OSANDROID} {$ifdef OSLINUX} function get_nprocs: integer; cdecl external clib name 'get_nprocs'; procedure SetLinuxDistrib(const release: RawUtf8); var distrib: TOperatingSystem; rel, dist: RawUtf8; begin rel := UpperCase(release); for distrib := osArch to high(distrib) do begin dist := UpperCase(OS_NAME[distrib]); if PosEx(dist, rel) > 0 then begin OS_KIND := distrib; break; end; end; end; function clock_gettime_c(clk_id: clockid_t; tp: ptimespec): cint; begin // FPC only knows the regular clocks: convert to the *_FAST version case clk_id of // 1 ms resolution is good enough for milliseconds-based RTL functions CLOCK_REALTIME: clk_id := CLOCK_REALTIME_FAST; CLOCK_MONOTONIC: clk_id := CLOCK_MONOTONIC_FAST; // no CLOCK_MONOTONIC_RAW redirect because it doesn't match CLOCK_MONOTONIC // and cthreads.pp forces pthread_condattr_setclock(CLOCK_MONOTONIC_RAW) end; // it is much faster to not use the Linux syscall but the libc vDSO call result := clock_gettime(clk_id, tp); end; function gettimeofday_c(tp: ptimeval; tzp: ptimezone): cint; begin // it is much faster to not use the Linux syscall but the libc vDSO call result := gettimeofday(tp, tzp); end; function GetSmbEfiMem: RawByteString; var efi, addr: RawUtf8; xaddr: cardinal; begin // retrieve raw EFI information from systab result := ''; xaddr := 0; efi := StringFromFile('/sys/firmware/efi/systab', {nosize=}true); if efi = '' then efi := StringFromFile('/proc/efi/systab', {nosize=}true); // old Linux<2.6.6 if efi = '' then exit; FindNameValue(efi, 'SMBIOS', addr); xaddr := PosixParseHex32(pointer(addr)); if xaddr <> 0 then result := ReadSystemMemory(xaddr, 32); // 32 bytes is enough end; // on Android, /sys/class/net is not readable from the standard user :( function _GetSystemMacAddress: TRawUtf8DynArray; var SR: TSearchRec; fn: TFileName; f: RawUtf8; begin result := nil; if FindFirst('/sys/class/net/*', faDirectory, SR) <> 0 then exit; repeat if (SR.Name <> 'lo') and not IdemPChar(pointer(SR.Name), 'DOCKER') and SearchRecValidFolder(SR) then begin fn := '/sys/class/net/' + SR.Name; f := StringFromFile(fn + '/flags', {nosize=}true); if (length(f) > 2) and // e.g. '0x40' or '0x1043' (PosixParseHex32(pointer(f)) and {IFF_LOOPBACK:}8 = 0) then begin f := TrimU(StringFromFile(fn + '/address', {nosize=}true)); if f <> '' then begin SetLength(result, length(result) + 1); result[high(result)] := f; end; end; end; until FindNext(SR) <> 0; FindClose(SR); end; {$endif OSLINUX} {$ifdef CPUARM3264} // POSIX libc is faster than FPC RTL or our pascal code function libc_strlen(s: PAnsiChar): SizeInt; cdecl external clib name 'strlen'; function libc_memmove(dst, src: pointer; n: SizeInt): pointer; cdecl external clib name 'memmove'; function libc_memset(dst: pointer; c: integer; n: SizeInt): pointer; cdecl external clib name 'memset'; function StrLenLibc(s: PAnsiChar): SizeInt; begin if s = nil then result := PtrUInt(s) else result := libc_strlen(s); end; procedure MoveFastLibC(const source; var dest; count: SizeInt); begin if (@dest <> @source) and (count > 0) then libc_memmove(@dest, @source, count); end; procedure FillCharLibC(var dest; count: PtrInt; value: byte); begin if (@dest <> nil) and (count > 0) then libc_memset(@dest, value, count); end; {$ifdef OSLINUXANDROID} procedure RetrieveCpuInfoArm; begin if CpuFeatures = [] then begin // fallback to /proc/cpuinfo "Features:" text if PosEx(' aes', CpuInfoFeatures) >= 0 then include(CpuFeatures, ahcAes); if PosEx(' pmull', CpuInfoFeatures) >= 0 then include(CpuFeatures, ahcPmull); if PosEx(' sha1', CpuInfoFeatures) >= 0 then include(CpuFeatures, ahcSha1); if PosEx(' sha2', CpuInfoFeatures) >= 0 then include(CpuFeatures, ahcSha2); if PosEx(' crc32', CpuInfoFeatures) >= 0 then include(CpuFeatures, ahcCrc32); end; end; {$endif OSLINUXANDROID} {$endif CPUARM3264} function Hex2Dec(c: AnsiChar): integer; inline; begin result := ord(c); case c of '0'..'9': dec(result, ord('0')); 'A'..'Z': dec(result, ord('A') - 10); 'a'..'z': dec(result, ord('a') - 10); else result := -1; end; end; // this function is published in interface section for mormot.net.sock.posix.inc function PosixParseHex32(p: PAnsiChar): integer; var v0, v1: integer; begin result := 0; p := StrScan(p, 'x'); if p = nil then exit; repeat inc(p); // points to trailing 'x' at start v0 := Hex2Dec(p^); if v0 < 0 then break; // not in '0'..'9','a'..'f' inc(p); v1 := Hex2Dec(p^); if v1 < 0 then begin result := (result shl 4) or v0; // only one char left break; end; result := (result shl 8) or (v0 shl 4) or v1; until false; end; procedure ParseHex(p: PAnsiChar; b: PByte; n: integer); var v0, v1: integer; begin repeat // caller ensured p<>nil and b<>nil and n>0 v0 := Hex2Dec(p^); if v0 < 0 then break; // not in '0'..'9','a'..'f' inc(p); v1 := Hex2Dec(p^); if v1 < 0 then break; inc(p); b^ := (v0 shl 4) or v1; inc(b); dec(n); until n = 0; end; procedure ParseHex32Add(p: PAnsiChar; var result: TIntegerDynArray); var v: integer; begin v := PosixParseHex32(p); if v <> 0 then AddInteger(result, v, {nodup=}true); end; function ParseLine(P: PUtf8Char): PUtf8Char; begin if P <> nil then P := strscan(P, ':'); result := P; if P = nil then exit; repeat inc(P); until (P^ = #0) or (P^ > ' '); result := P; while not (ord(P^) in [0, 10, 13]) do begin if P^ < ' ' then P^ := ' '; // change any tab into space inc(P); end; P^ := #0; // make asciiz end; function ParseInt(P: PUtf8Char): integer; begin P := ParseLine(P); if (P <> nil) and (P^ in ['0'..'9']) then result := GetCardinal(P) else result := -1; end; {$ifdef CPUAARCH64} {$ifdef OSLINUXANDROID} // AARCH64 armv8.o is only validated on Linux // (it should work on other POSIX ABI, but was reported to fail) {$define ARMV8STATIC} {$L ..\..\static\aarch64-linux\armv8.o} // ARMv8 crc32c Linux code function crc32carm64(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal; external; function crc32arm64(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal; external; function crc32cby4arm64(crc, value: cardinal): cardinal; external; procedure crc32blockarm64(crc128, data128: PBlock128); external; procedure crc32blocksarm64(crc128, data128: PBlock128; count: integer); external; {$endif OSLINUXANDROID} {$endif CPUAARCH64} procedure InitializeSpecificUnit; var P: PAnsiChar; modname, beg: PUtf8Char; uts: UtsName; {$ifndef NODIRECTTHREADMANAGER} tm: TThreadManager; {$endif NODIRECTTHREADMANAGER} {$ifndef OSDARWIN} {$ifdef CPUARM3264} act, aci: TIntegerDynArray; i: PtrInt; {$endif CPUARM3264} tp: timespec; {$endif OSDARWIN} {$ifdef OSBSDDARWIN} temp1, temp2: ShortString; {$else} hw, cache, cpuinfo: PUtf8Char; proccpuinfo, release, prod: RawUtf8; procid, phyid, phyndx: integer; phy: TIntegerDynArray; {$ifdef OSLINUX} prodver, dist: RawUtf8; SR: TSearchRec; si: TSysInfo; // Linuxism function GetSysFile(const fn: TFileName): RawUtf8; begin result := TrimU(StringFromFile(fn, true)); if result = 'Default string' then // e.g. on ProxMox containers or VMs result := ''; end; {$endif OSLINUX} {$endif OSBSDDARWIN} begin // retrieve Kernel and Hardware information StdOutIsTTY := not IsDebuggerPresent and (IsATTY(StdOutputHandle) = 1) and IdemPChars(RawUtf8(GetEnvironmentVariable('TERM')), [ 'XTERM', 'SCREEN', 'TMUX', 'RXVT', 'LINUX', 'CYGWIN']); modname := nil; fpuname(uts); {$ifdef OSBSDDARWIN} // pure FreeBSD NetBSD MacOS branch SystemInfo.dwNumberOfProcessors := fpsysctlhwint(HW_NCPU); beg := fpsysctlhwstr(HW_MACHINE, temp1); {$ifdef OSDARWIN} if strscan(beg, ' ') = nil then // e.g. from a Parallels VM beg := fpsysctlhwstr(HW_MODEL, temp1); modname := fpsysctlbynamehwstr('machdep.cpu.brand_string', temp2); {$endif OSDARWIN} FastSetString(BiosInfoText, beg, StrLen(beg)); if modname = nil then modname := fpsysctlhwstr(HW_MODEL, temp2); with uts do OSVersionText := sysname + '-' + release + ' ' + version; {$ifdef OSDARWIN} // pure MACOS branch CpuCache[1].LineSize := fpsysctlhwint(HW_CACHELINE); CpuCache[1].Size := fpsysctlhwint(HW_L1DCACHESIZE); CpuCache[2].LineSize := fpsysctlhwint(HW_CACHELINE); CpuCache[2].Size := fpsysctlhwint(HW_L2CACHESIZE); CpuCache[3].LineSize := fpsysctlhwint(HW_CACHELINE); CpuCache[3].Size := fpsysctlhwint(HW_L3CACHESIZE); CpuCacheSize := CpuCache[3].Size; if CpuCacheSize = 0 then CpuCacheSize := CpuCache[2].Size; if CpuCacheSize = 0 then CpuCacheSize := CpuCache[1].Size; if CpuCacheSize <> 0 then _fmt('L1=%s L2=%s L3=%s', [_oskb(CpuCache[1].Size), _oskb(CpuCache[2].Size), _oskb(CpuCache[3].Size)], CpuCacheText); SystemMemorySize := fpsysctlhwint(HW_MEMSIZE); {$else} SystemMemorySize := fpsysctlhwint(HW_PHYSMEM); {$endif OSDARWIN} {$else} {$ifdef OSANDROID} // pure ANDROID branch release := GetSystemProperty('ro.build.version.release'); prod := TrimU(RawUtf8(GetSystemProperty('ro.product.brand') + ' ' + GetSystemProperty('ro.product.name') + ' ' + GetSystemProperty('ro.product.device'))); {$else} // pure LINUX branch GetSystemMacAddress := _GetSystemMacAddress; if Sysinfo(@si) = 0 then SystemMemorySize := si.totalram * si.mem_unit; prod := TrimU(GetSysFile('/sys/class/dmi/id/sys_vendor') + ' ' + GetSysFile('/sys/class/dmi/id/product_name')); if prod <> '' then begin // e.g. 'QEMU KVM Virtual Machine' or 'LENOVO 20HES23B0U' prodver := GetSysFile('/sys/class/dmi/id/product_version'); if prodver <> '' then prod := prod + ' ' + prodver; end else // return e.g. 'Raspberry Pi 3 Model B Rev 1.2' prod := GetSysFile('/proc/device-tree/model'); FindNameValue(StringFromFile('/etc/os-release', true), 'PRETTY_NAME=', release); if (release <> '') and (release[1] = '"') then release := copy(release, 2, length(release) - 2); TrimSelf(release); if release = '' then begin FindNameValue(StringFromFile('/etc/lsb-release', true), 'DISTRIB_DESCRIPTION=', release); if (release <> '') and (release[1] = '"') then release := copy(release, 2, length(release) - 2); end; if (release = '') and (FindFirst('/etc/*-release', faAnyFile, SR) = 0) then begin release := SR.Name; // 'redhat-release' 'SuSE-release' if IdemPChar(pointer(release), 'LSB-') and (FindNext(SR) = 0) then release := SR.Name; release := split(release, '-'); dist := split(StringFromFile('/etc/' + SR.Name, true), #10); if (dist <> '') and (PosExChar('=', dist) = 0) and (PosExChar(' ', dist) > 0) then // e.g. 'Red Hat Enterprise Linux Server release 6.7 (Santiago)' SetLinuxDistrib(dist) else dist := ''; FindClose(SR); end; if (release <> '') and (OS_KIND = osLinux) then begin SetLinuxDistrib(release); if (OS_KIND = osLinux) and ({%H-}dist <> '') then begin SetLinuxDistrib(dist); release := dist; end; if (OS_KIND = osLinux) and ((PosEx('RH', release) > 0) or (PosEx('Red Hat', release) > 0)) then OS_KIND := osRedHat; end; {$endif OSANDROID} BiosInfoText := prod; hw := nil; cache := nil; SystemInfo.dwNumberOfProcessors := 0; proccpuinfo := StringFromFile('/proc/cpuinfo', true); procid := -1; cpuinfo := pointer(proccpuinfo); while cpuinfo <> nil do begin beg := cpuinfo; cpuinfo := GotoNextLine(cpuinfo); if IdemPChar(beg, 'PROCESSOR') then if beg^ = 'P' then modname := ParseLine(beg) // Processor : ARMv7 else begin // loop over all "processor : 0 .. 1 .. 2" lines to count the CPUs inc(SystemInfo.dwNumberOfProcessors); procid := ParseInt(beg); if procid >= integer(SystemInfo.dwNumberOfProcessors) then procid := -1; // paranoid end else if IdemPChar(beg, 'MODEL NAME') then modname := ParseLine(beg) else if IdemPChar(beg, 'FEATURES') or IdemPChar(beg, 'FLAGS') then CpuInfoFeatures := ParseLine(beg) else if IdemPChar(beg, 'HARDWARE') then hw := ParseLine(beg) else if IdemPChar(beg, 'CACHE SIZE') then cache := ParseLine(beg) {$ifdef CPUARM3264} else if IdemPChar(beg, 'CPU IMPLEMENTER') then ParseHex32Add(beg, aci) else if IdemPChar(beg, 'CPU PART') then ParseHex32Add(beg, act) {$endif CPUARM3264} else if IdemPChar(beg, 'PHYSICAL ID') then begin phyid := ParseInt(beg); // in practice, may be 0,3,... and not 0,1,... if phyid < 0 then continue; phyndx := IntegerScanIndex(pointer(phy), CpuSockets, phyid); if phyndx < 0 then begin AddInteger(phy, CpuSockets, phyid); SetLength(CpuSocketsMask, CpuSockets); phyndx := CpuSockets - 1; end; if (procid >= 0) and (procid < SizeOf(TCpuSet) shl 3) then SetBitPtr(@CpuSocketsMask[phyndx], procid); end; end; {$ifdef CPUARM3264} if act <> nil then // CPU part/implementer are more detailed than model name begin proccpuinfo := ''; for i := 0 to high(aci) do // there should be a single implementer proccpuinfo := proccpuinfo + ArmCpuImplementerName(ArmCpuImplementer(aci[i]), aci[i]) + ' '; proccpuinfo := proccpuinfo + ArmCpuTypeName(ArmCpuType(act[0]), act[0]); for i := 1 to high(act) do // but there may be several parts/models proccpuinfo := proccpuinfo + ' / ' + ArmCpuTypeName(ArmCpuType(act[i]), act[i]); modname := pointer(proccpuinfo); end; RetrieveCpuInfoArm; {$endif CPUARM3264} if hw <> nil then begin prod := hw; if BiosInfoText = '' then BiosInfoText := prod else BiosInfoText := BiosInfoText + ' (' + prod + ')'; end else if BiosInfoText = '' then BiosInfoText := 'Generic ' + CPU_ARCH_TEXT + ' system'; // noname computer if cache <> nil then begin CpuCacheText := TrimU(cache); CpuCacheSize := GetNextCardinal(cache); while cache^ = ' ' do inc(cache); case upcase(cache^) of 'K': CpuCacheSize := CpuCacheSize shl 10; 'M': CpuCacheSize := CpuCacheSize shl 20; 'G': CpuCacheSize := CpuCacheSize shl 30; end; end; SystemInfo.release := release; {$endif OSBSDDARWIN} SystemInfo.dwPageSize := getpagesize; // call libc API if CpuCacheSize <> 0 then _fmt('[%s]', [_oskb(CpuCacheSize)], CpuInfoText); if CpuSockets = 0 then CpuSockets := 1; SystemInfo.uts.release := uts.Release; SystemInfo.uts.sysname := uts.Sysname; SystemInfo.uts.version := uts.Version; P := @uts.release[0]; KernelRevision := GetNextCardinal(P) shl 16 + GetNextCardinal(P) shl 8 + GetNextCardinal(P); OSVersion32.os := OS_KIND; MoveByOne(@KernelRevision, @OSVersion32.utsrelease, 3); // 24-bit with SystemInfo.uts do OSVersionText := sysname + ' ' + release; if SystemInfo.release <> '' then OSVersionText := SystemInfo.release + ' - ' + OSVersionText; {$ifdef OSANDROID} OSVersionText := 'Android (' + OSVersionText + ')'; {$else} {$ifdef OSLINUX} if SystemInfo.dwNumberOfProcessors = 0 then // e.g. QEMU limited /proc/cpuinfo SystemInfo.dwNumberOfProcessors := get_nprocs; {$endif OSLINUX} {$endif OSANDROID} if SystemInfo.dwNumberOfProcessors = 0 then SystemInfo.dwNumberOfProcessors := 1; if modname = nil then CpuInfoText := _fmt('%d x generic ' + CPU_ARCH_TEXT + ' cpu %s', [SystemInfo.dwNumberOfProcessors, CpuInfoText]) else CpuInfoText := _fmt('%d x %s %s (' + CPU_ARCH_TEXT + ')', [SystemInfo.dwNumberOfProcessors, modname, CpuInfoText]); // intialize supported APIs TimeZoneLocalBias := -GetLocalTimeOffset; {$ifndef NODIRECTTHREADMANAGER} // for inlined RTL calls (avoid one level of redirection) GetThreadManager(tm); @GetCurrentThreadId := @tm.GetCurrentThreadId; @TryEnterCriticalSection := @tm.TryEnterCriticalSection; @EnterCriticalSection := @tm.EnterCriticalSection; @LeaveCriticalSection := @tm.LeaveCriticalSection; {$endif NODIRECTTHREADMANAGER} {$ifdef OSDARWIN} OSVersionText := ToText(OSVersion32) + ' (' + OSVersionText + ')'; mach_timebase_info(mach_timeinfo); mach_timecoeff := mach_timeinfo.Numer / mach_timeinfo.Denom; mach_timenanosecond := (mach_timeinfo.Numer = 1) and (mach_timeinfo.Denom = 1); {$else} // try Linux kernel 2.6.32+ or FreeBSD 8.1+ fastest clocks if clock_gettime(CLOCK_REALTIME_COARSE, @tp) = 0 then CLOCK_REALTIME_FAST := CLOCK_REALTIME_COARSE; if clock_gettime(CLOCK_MONOTONIC_COARSE, @tp) = 0 then CLOCK_MONOTONIC_FAST := CLOCK_MONOTONIC_COARSE; {$ifdef OSLINUX} if clock_gettime(CLOCK_MONOTONIC_RAW, @tp) = 0 then CLOCK_MONOTONIC_HIRES := CLOCK_MONOTONIC_RAW; {$endif OSLINUX} if clock_gettime(CLOCK_BOOTTIME, @tp) = 0 then CLOCK_UPTIME := CLOCK_BOOTTIME; if (clock_gettime(CLOCK_REALTIME_FAST, @tp) <> 0) or // paranoid check (clock_gettime(CLOCK_MONOTONIC_FAST, @tp) <> 0) or (clock_gettime(CLOCK_MONOTONIC_HIRES, @tp) <> 0) then raise EOSException.CreateFmt( 'clock_gettime() not supported by %s kernel - errno=%d', [PAnsiChar(@uts.release), fpgeterrno]); // direct access to the pthread library if possible (Linux only) {$ifdef OSPTHREADSLIB} // mutex_lock() is blocking when dlopen run from a .so: cthreads uses both // static and dynamic linking, which is really confusing to our code // -> we don't open libpthread but we get its symbol pthread := dlopen('libpthread.so.0', RTLD_LAZY); if pthread <> nil then begin {$ifdef HAS_PTHREADSETNAMENP} @pthread_setname_np := dlsym(pthread, 'pthread_setname_np'); {$endif HAS_PTHREADSETNAMENP} {$ifdef HAS_PTHREADSETAFFINITY} @pthread_setaffinity_np := dlsym(pthread, 'pthread_setaffinity_np'); {$endif HAS_PTHREADSETAFFINITY} @pthread_cancel := dlsym(pthread, 'pthread_cancel'); @pthread_mutex_init := dlsym(pthread, 'pthread_mutex_init'); @pthread_mutex_destroy := dlsym(pthread, 'pthread_mutex_destroy'); @pthread_mutex_trylock := dlsym(pthread, 'pthread_mutex_trylock'); @pthread_mutex_lock := dlsym(pthread, 'pthread_mutex_lock'); @pthread_mutex_unlock := dlsym(pthread, 'pthread_mutex_unlock'); end; {$endif OSPTHREADSLIB} // some ARM/AARCH64 specific initialization {$ifdef CPUARM3264} StrLen := @StrLenLibc; // libc version is faster than plain pascal or RTL code MoveFast := @MoveFastLibC; FillCharFast := @FillCharLibC; {$ifdef ARMV8STATIC} if ahcCrc32 in CpuFeatures then try if (crc32cby4arm64(0, 1) = 3712330424) and (crc32carm64(0, @SystemInfo, SizeOf(SystemInfo)) = crc32cfast(0, @SystemInfo, SizeOf(SystemInfo))) then begin crc32c := @crc32carm64; DefaultHasher := @crc32carm64; InterningHasher := @crc32carm64; crc32cby4 := @crc32cby4arm64; crcblock := @crc32blockarm64; crcblocks := @crc32blocksarm64; end; if crc32arm64(0, @SystemInfo, SizeOf(SystemInfo)) = crc32fast(0, @SystemInfo, SizeOf(SystemInfo)) then crc32 := @crc32arm64; except exclude(CpuFeatures, ahcCrc32); // crc32 was actually not supported end; {$endif ARMV8STATIC} {$endif CPUARM3264} {$ifdef CPUX64} {$ifdef OSLINUX} {$ifndef NOPATCHRTL} // redirect some syscall FPC RTL functions to faster vDSO libc variant {$ifndef FPC_USE_LIBC} RedirectCode(@Linux.clock_gettime, @clock_gettime_c); // will avoid syscall e.g. for events timeout in cthreads.pp RedirectCode(@fpgettimeofday, @gettimeofday_c); {$endif FPC_USE_LIBC} {$endif NOPATCHRTL} {$endif OSLINUX} {$endif CPUX64} {$endif OSDARWIN} end; procedure FinalizeSpecificUnit; begin {$ifdef OSPTHREADSLIB} if pthread <> nil then dlclose(pthread); {$endif OSPTHREADSLIB} {$ifdef OSLINUX} // systemd API is Linux-specific sd.Done; {$endif OSLINUX} icu.Done; SynDaemonInterceptLog := nil; end;