{ This file is part of the Free Pascal run time library. Copyright (c) 1999-2000 by Florian Klaempfl member of the Free Pascal development team Sysutils unit for linux See the file COPYING.FPC, included in this distribution, for details about the copyright. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. **********************************************************************} {$IFNDEF FPC_DOTTEDUNITS} unit sysutils; {$ENDIF FPC_DOTTEDUNITS} interface {$MODE objfpc} {$MODESWITCH OUT} {$IFDEF UNICODERTL} {$MODESWITCH UNICODESTRINGS} {$ELSE} {$H+} {$ENDIF} {$modeswitch typehelpers} {$modeswitch advancedrecords} {$if (defined(BSD) or defined(SUNOS)) and defined(FPC_USE_LIBC)} {$define USE_VFORK} {$endif} {$DEFINE HAS_FILEGETDATETIMEINFO} {$DEFINE OS_FILESETDATEBYNAME} {$DEFINE HAS_SLEEP} {$DEFINE HAS_OSERROR} {$DEFINE HAS_OSCONFIG} {$DEFINE HAS_TEMPDIR} {$DEFINE HASUNIX} {$DEFINE HASCREATEGUID} {$DEFINE HAS_OSUSERDIR} {$DEFINE HAS_LOCALTIMEZONEOFFSET} {$DEFINE HAS_GETTICKCOUNT64} // this target has an fileflush implementation, don't include dummy {$DEFINE SYSUTILS_HAS_FILEFLUSH_IMPL} { used OS file system APIs use ansistring } {$define SYSUTILS_HAS_ANSISTR_FILEUTIL_IMPL} { OS has an ansistring/single byte environment variable API } {$define SYSUTILS_HAS_ANSISTR_ENVVAR_IMPL} {$IFDEF FPC_DOTTEDUNITS} uses {$IFDEF LINUX}LinuxApi,{$ENDIF} {$IFDEF FreeBSD}freebsd,{$ENDIF} UnixApi.Base, UnixApi.Unix,UnixApi.Errors,System.SysConst,UnixApi.Types; {$ELSE FPC_DOTTEDUNITS} uses {$IFDEF LINUX}linux,{$ENDIF} {$IFDEF FreeBSD}freebsd,{$ENDIF} baseunix, Unix,errors,sysconst,Unixtype; {$ENDIF FPC_DOTTEDUNITS} {$IF defined(LINUX) or defined(FreeBSD)} {$DEFINE HAVECLOCKGETTIME} {$ENDIF} {$IF defined(DARWIN)} {$DEFINE HAS_ISFILENAMECASEPRESERVING} {$DEFINE HAS_ISFILENAMECASESENSITIVE} {$DEFINE USE_FUTIMES} {$ENDIF} {$if defined(LINUX)} {$if sizeof(clong)<8} {$DEFINE USE_STATX} {$DEFINE USE_UTIMENSAT} {$endif sizeof(clong)<=4} {$DEFINE USE_FUTIMES} {$endif} { Include platform independent interface part } {$i sysutilh.inc} Function AddDisk(const path:string) : Byte; { the following is Kylix compatibility stuff, it should be moved to a special compatibilty unit (FK) } const RTL_SIGINT = 0; RTL_SIGFPE = 1; RTL_SIGSEGV = 2; RTL_SIGILL = 3; RTL_SIGBUS = 4; RTL_SIGQUIT = 5; RTL_SIGLAST = RTL_SIGQUIT; RTL_SIGDEFAULT = -1; type TSignalState = (ssNotHooked, ssHooked, ssOverridden); function InquireSignal(RtlSigNum: Integer): TSignalState; procedure AbandonSignalHandler(RtlSigNum: Integer); procedure HookSignal(RtlSigNum: Integer); procedure UnhookSignal(RtlSigNum: Integer; OnlyIfHooked: Boolean = True); implementation {$IFDEF FPC_DOTTEDUNITS} Uses {$ifdef android} dl, {$endif android} {$ifdef FPC_USE_LIBC}System.InitC{$ELSE}UnixApi.SysCall{$ENDIF}, UnixApi.Utils; {$ELSE FPC_DOTTEDUNITS} Uses {$ifdef android} dl, {$endif android} {$ifdef FPC_USE_LIBC}initc{$ELSE}Syscall{$ENDIF}, unixutil; {$ENDIF FPC_DOTTEDUNITS} type tsiginfo = record oldsiginfo: sigactionrec; hooked: boolean; end; const rtlsig2ossig: array[RTL_SIGINT..RTL_SIGLAST] of byte = (SIGINT,SIGFPE,SIGSEGV,SIGILL,SIGBUS,SIGQUIT); { to avoid linking in all this stuff in every program, as it's unlikely to be used by anything but libraries } signalinfoinited: boolean = false; var siginfo: array[RTL_SIGINT..RTL_SIGLAST] of tsiginfo; oldsigfpe: SigActionRec; external name '_FPC_OLDSIGFPE'; oldsigsegv: SigActionRec; external name '_FPC_OLDSIGSEGV'; oldsigbus: SigActionRec; external name '_FPC_OLDSIGBUS'; oldsigill: SigActionRec; external name '_FPC_OLDSIGILL'; procedure defaultsighandler; external name '_FPC_DEFAULTSIGHANDLER'; procedure installdefaultsignalhandler(signum: Integer; out oldact: SigActionRec); external name '_FPC_INSTALLDEFAULTSIGHANDLER'; function InternalInquireSignal(RtlSigNum: Integer; out act: SigActionRec; frominit: boolean): TSignalState; begin result:=ssNotHooked; if (RtlSigNum<>RTL_SIGDEFAULT) and (RtlSigNum check whether the code addresses inside the descriptors match, rather than the descriptors themselves } if (ppointer(act.sa_handler)^=ppointer(@defaultsighandler)^) then {$endif} result:=ssHooked else result:=ssOverridden; end else if IsLibrary then begin { library -> signals have not been hooked by system init code } exit end else begin { program -> signals have been hooked by system init code } if (byte(RtlSigNum) in [RTL_SIGFPE,RTL_SIGSEGV,RTL_SIGILL,RTL_SIGBUS]) then begin {$if not defined(aix) and (not defined(linux) or not defined(cpupowerpc64) or (defined(_call_elf) and (_call_elf = 2)))} if (pointer(act.sa_handler)=pointer(@defaultsighandler)) then {$else} if (ppointer(act.sa_handler)^=ppointer(@defaultsighandler)^) then {$endif} result:=ssHooked else result:=ssOverridden; { return the original handlers as saved by the system unit (the current call to sigaction simply returned our system unit's installed handlers) } case RtlSigNum of RTL_SIGFPE: act:=oldsigfpe; RTL_SIGSEGV: act:=oldsigsegv; RTL_SIGILL: act:=oldsigill; RTL_SIGBUS: act:=oldsigbus; end; end else begin { these are not hooked in the startup code } result:=ssNotHooked; end end end end; end; procedure initsignalinfo; var i: Integer; begin for i:=RTL_SIGINT to RTL_SIGLAST do siginfo[i].hooked:=(InternalInquireSignal(i,siginfo[i].oldsiginfo,true)=ssHooked); signalinfoinited:=true; end; function InquireSignal(RtlSigNum: Integer): TSignalState; var act: SigActionRec; begin if not signalinfoinited then initsignalinfo; result:=InternalInquireSignal(RtlSigNum,act,false); end; procedure AbandonSignalHandler(RtlSigNum: Integer); begin if not signalinfoinited then initsignalinfo; if (RtlSigNum<>RTL_SIGDEFAULT) and (RtlSigNumRTL_SIGDEFAULT) then begin lowsig:=RtlSigNum; highsig:=RtlSigNum; end else begin { we don't hook SIGINT and SIGQUIT by default } lowsig:=RTL_SIGFPE; highsig:=RTL_SIGBUS; end; { install the default rtl signal handler for the selected signal(s) } for i:=lowsig to highsig do begin installdefaultsignalhandler(rtlsig2ossig[i],siginfo[i].oldsiginfo); siginfo[i].hooked:=true; end; end; procedure UnhookSignal(RtlSigNum: Integer; OnlyIfHooked: Boolean = True); var act: SigActionRec; lowsig, highsig, i: Integer; begin if not signalinfoinited then initsignalinfo; if (RtlSigNum<>RTL_SIGDEFAULT) then begin lowsig:=RtlSigNum; highsig:=RtlSigNum; end else begin { we don't hook SIGINT and SIGQUIT by default } lowsig:=RTL_SIGFPE; highsig:=RTL_SIGBUS; end; for i:=lowsig to highsig do begin if not OnlyIfHooked or (InquireSignal(i)=ssHooked) then begin { restore the handler that was present when we hooked the signal, if we hooked it at one time or another. If the user doesn't want this, they have to call AbandonSignalHandler() first } if siginfo[i].hooked then act:=siginfo[i].oldsiginfo else begin fillchar(act,sizeof(act),0); pointer(act.sa_handler):=pointer(SIG_DFL); end; if (fpsigaction(rtlsig2ossig[i],@act,nil)=0) then siginfo[i].hooked:=false; end; end; end; {$Define OS_FILEISREADONLY} // Specific implementation for Unix. {$DEFINE FPC_FEXPAND_TILDE} { Tilde is expanded to home } {$DEFINE FPC_FEXPAND_GETENVPCHAR} { GetEnv result is a PAnsiChar } { Include platform independent implementation part } {$define executeprocuni} {$i sysutils.inc} { Include SysCreateGUID function } {$i suuid.inc} function GetTickCount64: QWord; var tp: TTimeVal; {$IFDEF HAVECLOCKGETTIME} ts: TTimeSpec; {$ENDIF} begin {$IFDEF HAVECLOCKGETTIME} if clock_gettime(CLOCK_MONOTONIC, @ts)=0 then begin Result := (Int64(ts.tv_sec) * 1000) + (ts.tv_nsec div 1000000); exit; end; {$ENDIF} fpgettimeofday(@tp, nil); Result := (Int64(tp.tv_sec) * 1000) + (tp.tv_usec div 1000); end; {**************************************************************************** File Functions ****************************************************************************} Function DoFileLocking(Handle: Longint; Mode: Integer) : Longint; var lockop: cint; lockres: cint; closeres: cint; lockerr: cint; begin DoFileLocking:=Handle; {$ifdef beos} {$else} if (Handle>=0) then begin {$if defined(solaris) or defined(aix)} { Solaris' & AIX' flock is based on top of fcntl, which does not allow exclusive locks for files only opened for reading nor shared locks for files opened only for writing. If no locking is specified, we normally need an exclusive lock. So create an exclusive lock for fmOpenWrite and fmOpenReadWrite, but only a shared lock for fmOpenRead (since an exclusive lock is not possible in that case) } if ((mode and (fmShareCompat or fmShareExclusive or fmShareDenyWrite or fmShareDenyRead or fmShareDenyNone)) = 0) then begin if ((mode and (fmOpenRead or fmOpenWrite or fmOpenReadWrite)) = fmOpenRead) then mode := mode or fmShareDenyWrite else mode := mode or fmShareExclusive; end; {$endif solaris} case (mode and (fmShareCompat or fmShareExclusive or fmShareDenyWrite or fmShareDenyRead or fmShareDenyNone)) of fmShareCompat, fmShareExclusive: lockop:=LOCK_EX or LOCK_NB; fmShareDenyWrite, fmShareDenyNone: lockop:=LOCK_SH or LOCK_NB; else begin { fmShareDenyRead does not exit under *nix, only shared access (similar to fmShareDenyWrite) and exclusive access (same as fmShareExclusive) } repeat closeres:=FpClose(Handle); until (closeres<>-1) or (fpgeterrno<>ESysEINTR); DoFileLocking:=-1; exit; end; end; repeat lockres:=fpflock(Handle,lockop); until (lockres=0) or (fpgeterrno<>ESysEIntr); lockerr:=fpgeterrno; { Only return an error if locks are working and the file was already locked. Not if locks are simply unsupported (e.g., on Angstrom Linux you always get ESysNOLCK in the default configuration) } if (lockres<>0) and ((lockerr=ESysEAGAIN) or (lockerr=EsysEDEADLK)) then begin repeat closeres:=FpClose(Handle); until (closeres<>-1) or (fpgeterrno<>ESysEINTR); DoFileLocking:=-1; exit; end; end; {$endif not beos} end; Function FileOpenNoLocking (Const FileName : RawbyteString; Mode : Integer) : Longint; Function IsHandleDirectory(Handle : Longint) : boolean; Var Info : Stat; begin Result := (fpFStat(Handle, Info)<0) or fpS_ISDIR(info.st_mode); end; Var SystemFileName: RawByteString; fd,LinuxFlags : longint; begin LinuxFlags:=0; case (Mode and (fmOpenRead or fmOpenWrite or fmOpenReadWrite)) of fmOpenRead : LinuxFlags:=LinuxFlags or O_RdOnly; fmOpenWrite : LinuxFlags:=LinuxFlags or O_WrOnly; fmOpenReadWrite : LinuxFlags:=LinuxFlags or O_RdWr; end; SystemFileName:=ToSingleByteFileSystemEncodedFileName(FileName); repeat fd:=fpOpen (pointer(SystemFileName),LinuxFlags); until (fd<>-1) or (fpgeterrno<>ESysEINTR); { Do not allow to open directories with FileOpen. This would cause weird behavior of TFileStream.Size, TMemoryStream.LoadFromFile etc. } if (fd<>-1) and IsHandleDirectory(fd) then begin fpClose(fd); fd:=feInvalidHandle; end; FileOpenNoLocking:=fd; end; Function FileOpen (Const FileName : RawbyteString; Mode : Integer) : Longint; begin FileOpen:=FileOpenNoLocking(FileName, Mode); if (Mode and fmShareNoLocking)=0 then FileOpen:=DoFileLocking(FileOpen, Mode); end; function FileFlush(Handle: THandle): Boolean; begin Result:= fpfsync(handle)=0; end; Function FileCreate (Const FileName : RawByteString) : Longint; Var SystemFileName: RawByteString; begin SystemFileName:=ToSingleByteFileSystemEncodedFileName(FileName); repeat FileCreate:=fpOpen(pointer(SystemFileName),O_RdWr or O_Creat or O_Trunc); until (FileCreate<>-1) or (fpgeterrno<>ESysEINTR); end; Function FileCreate (Const FileName : RawByteString;Rights : Longint) : Longint; Var SystemFileName: RawByteString; begin SystemFileName:=ToSingleByteFileSystemEncodedFileName(FileName); repeat FileCreate:=fpOpen(pointer(SystemFileName),O_RdWr or O_Creat or O_Trunc,Rights); until (FileCreate<>-1) or (fpgeterrno<>ESysEINTR); end; Function FileCreate (Const FileName : RawByteString; ShareMode : Longint; Rights:LongInt ) : Longint; Var fd: Longint; begin { if the file already exists and we can't open it using the requested ShareMode (e.g. exclusive sharing), exit immediately so that we don't first empty the file and then check whether we can lock this new file (which we can by definition) } fd:=FileOpenNoLocking(FileName,ShareMode); { the file exists, check whether our locking request is compatible } if (fd>=0) then begin if ((ShareMode and fmShareNoLocking)=0) then Result:=DoFileLocking(fd,ShareMode) else Result:=0; FileClose(fd); { Can't lock -> abort } if Result<0 then exit; end; { now create the file } Result:=FileCreate(FileName,Rights); if ((ShareMode and fmShareNoLocking)=0) then Result:=DoFileLocking(Result,ShareMode); end; Function FileRead (Handle : Longint; out Buffer; Count : longint) : Longint; begin repeat FileRead:=fpRead (Handle,Buffer,Count); until (FileRead<>-1) or (fpgeterrno<>ESysEINTR); end; Function FileWrite (Handle : Longint; const Buffer; Count : Longint) : Longint; begin repeat FileWrite:=fpWrite (Handle,Buffer,Count); until (FileWrite<>-1) or (fpgeterrno<>ESysEINTR); end; Function FileSeek (Handle,FOffset,Origin : Longint) : Longint; Var I : Int64; begin I:=FileSeek(Handle,int64(FOffset),Origin); if I>High(Longint) then Raise EInOutError.CreateFmt(SErrPosToBigForLongint,[I]); result:=I; end; Function FileSeek (Handle : Longint; FOffset : Int64; Origin : Longint) : Int64; begin FileSeek:=fplSeek (Handle,FOffset,Origin); end; Procedure FileClose (Handle : Longint); var res: cint; begin repeat res:=fpclose(Handle); until (res<>-1) or (fpgeterrno<>ESysEINTR); end; Function FileTruncate (Handle: THandle; Size: Int64) : boolean; var res: cint; begin if (SizeOf (TOff) < 8) (* fpFTruncate only supporting signed 32-bit size *) and (Size > high (longint)) then FileTruncate := false else begin repeat res:=fpftruncate(Handle,Size); until (res<>-1) or (fpgeterrno<>ESysEINTR); FileTruncate:=res>=0; end; end; Function FileAge (Const FileName : RawByteString): Int64; Var Info : Stat; SystemFileName: RawByteString; {$ifdef USE_STATX} Infox : TStatx; {$endif USE_STATX} begin SystemFileName:=ToSingleByteFileSystemEncodedFileName(FileName); {$ifdef USE_STATX} { first try statx } if {$ifdef FPC_USE_LIBC} (@statx<>nil) and {$endif} (statx(AT_FDCWD,PAnsiChar(SystemFileName),0,STATX_MTIME or STATX_MODE,Infox)>=0) and not(fpS_ISDIR(Infox.stx_mode)) then begin Result:=Infox.stx_mtime.tv_sec; exit; end; {$endif USE_STATX} If (fpstat(PAnsiChar(SystemFileName),Info)<0) or fpS_ISDIR(info.st_mode) then exit(-1) else Result:=info.st_mtime; end; function FileGetDateTimeInfo(const FileName: string; out DateTime: TDateTimeInfoRec; FollowLink: Boolean = True): Boolean; var FN : AnsiString; st: tstat; {$IFDEF USE_STATX} stx : tstatx; flags : Integer; const STATXMASK = STATX_MTIME or STATX_ATIME or STATX_CTIME; {$ENDIF} begin FN:=FileName; {$ifdef USE_STATX} flags:=0; if Not FollowLink then Flags:=AT_SYMLINK_NOFOLLOW; if {$ifdef FPC_USE_LIBC} (@statx<>nil) and {$endif} (statx(AT_FDCWD,PAnsiChar(FN),FLags,STATXMASK, stx)>=0) then begin DateTime.Data:=stx; Exit(True); end; {$else} if (FollowLink and (fpstat(FN,st) = 0)) or (not FollowLink and (fplstat(fn, st) = 0)) then begin DateTime.Data:=st; Result := True; end; {$endif} end; Function LinuxToWinAttr (const FN : RawByteString; Const Info : Stat) : Longint; Var LinkInfo : Stat; nm : RawByteString; begin Result:=faArchive; If fpS_ISDIR(Info.st_mode) then Result:=Result or faDirectory; nm:=ExtractFileName(FN); If (Length(nm)>=2) and (nm[1]='.') and (nm[2]<>'.') then Result:=Result or faHidden; If (Info.st_Mode and S_IWUSR)=0 Then Result:=Result or faReadOnly; If fpS_ISSOCK(Info.st_mode) or fpS_ISBLK(Info.st_mode) or fpS_ISCHR(Info.st_mode) or fpS_ISFIFO(Info.st_mode) Then Result:=Result or faSysFile; If fpS_ISLNK(Info.st_mode) Then begin Result:=Result or faSymLink; // Windows reports if the link points to a directory. if (fpstat(PAnsiChar(FN),LinkInfo)>=0) and fpS_ISDIR(LinkInfo.st_mode) then Result := Result or faDirectory; end; end; {$ifdef USE_STATX} Function LinuxToWinAttr (const FN : RawByteString; Const Info : TStatx) : Longint; Var LinkInfo : Stat; nm : RawByteString; begin Result:=faArchive; If fpS_ISDIR(Info.stx_mode) then Result:=Result or faDirectory; nm:=ExtractFileName(FN); If (Length(nm)>=2) and (nm[1]='.') and (nm[2]<>'.') then Result:=Result or faHidden; If (Info.stx_Mode and S_IWUSR)=0 Then Result:=Result or faReadOnly; If fpS_ISSOCK(Info.stx_mode) or fpS_ISBLK(Info.stx_mode) or fpS_ISCHR(Info.stx_mode) or fpS_ISFIFO(Info.stx_mode) Then Result:=Result or faSysFile; If fpS_ISLNK(Info.stx_mode) Then begin Result:=Result or faSymLink; // Windows reports if the link points to a directory. { as we are only interested in the st_mode field here, we do not need to use statx } if (fpstat(PAnsiChar(FN),LinkInfo)>=0) and fpS_ISDIR(LinkInfo.st_mode) then Result := Result or faDirectory; end; end; {$endif USE_STATX} function FileGetSymLinkTarget(const FileName: RawByteString; out SymLinkRec: TRawbyteSymLinkRec): Boolean; var Info : Stat; SystemFileName: RawByteString; begin SystemFileName:=ToSingleByteFileSystemEncodedFileName(FileName); if (fplstat(SystemFileName,Info)>=0) and fpS_ISLNK(Info.st_mode) then begin FillByte(SymLinkRec, SizeOf(SymLinkRec), 0); SymLinkRec.TargetName:=fpreadlink(SystemFileName); if fpstat(pointer(SystemFileName), Info) < 0 then raise EDirectoryNotFoundException.Create(SysErrorMessage(GetLastOSError)); SymLinkRec.Attr := LinuxToWinAttr(SystemFileName, Info); SymLinkRec.Size := Info.st_size; SymLinkRec.Mode := Info.st_mode; Result:=True; end else Result:=False; end; Function FileExists (Const FileName : RawByteString; FollowLink : Boolean) : Boolean; var Info : Stat; SystemFileName: RawByteString; isdir: Boolean; begin // Do not call fpAccess with an empty name. (Valgrind will complain) if Filename='' then Exit(False); SystemFileName:=ToSingleByteFileSystemEncodedFileName(FileName); // Don't use stat. It fails on files >2 GB. // Access obeys the same access rules, so the result should be the same. FileExists:=fpAccess(pointer(SystemFileName),F_OK)=0; { we need to ensure however that we aren't dealing with a directory } isdir:=False; if FileExists then begin if (fpstat(pointer(SystemFileName),Info)>=0) and fpS_ISDIR(Info.st_mode) then begin FileExists:=False; isdir:=True; end; end; { if we shall not follow the link we only need to check for a symlink if the target file itself should not exist } if not FileExists and not isdir and not FollowLink then FileExists:=(fplstat(pointer(SystemFileName),Info)>=0) and fpS_ISLNK(Info.st_mode); end; Function DirectoryExists (Const Directory : RawByteString; FollowLink : Boolean) : Boolean; Var Info : Stat; SystemFileName: RawByteString; exists: Boolean; begin SystemFileName:=ToSingleByteFileSystemEncodedFileName(Directory); exists:=fpstat(pointer(SystemFileName),Info)>=0; DirectoryExists:=exists and fpS_ISDIR(Info.st_mode); { if we shall not follow the link we only need to check for a symlink if the target directory itself should not exist } if not exists and not FollowLink then DirectoryExists:=(fplstat(pointer(SystemFileName),Info)>=0) and fpS_ISLNK(Info.st_mode); end; { assumes that pattern and name have the same code page } Function FNMatch(const Pattern,Name:string):Boolean; Var LenPat,LenName : longint; function NameUtf8CodePointLen(index: longint): longint; var MaxLookAhead: longint; begin MaxLookAhead:=LenName-Index+1; { abs so that in case of an invalid sequence, we count this as one codepoint } NameUtf8CodePointLen:=abs(Utf8CodePointLen(pansichar(@Name[index]),MaxLookAhead,true)); { if the sequence was incomplete, use the incomplete sequence as codepoint } if NameUtf8CodePointLen=0 then NameUtf8CodePointLen:=MaxLookAhead; end; procedure GoToLastByteOfUtf8CodePoint(var j: longint); begin inc(j,NameUtf8CodePointLen(j)-1); end; { input: i: current position in pattern (start of utf-8 code point) j: current position in name (start of utf-8 code point) update_i_j: should i and j be changed by the routine or not output: i: if update_i_j, then position of last matching part of code point in pattern, or first non-matching code point in pattern. Otherwise the same value as on input. j: if update_i_j, then position of last matching part of code point in name, or first non-matching code point in name. Otherwise the same value as on input. result: true if match, false if no match } function CompareUtf8CodePoint(var i,j: longint; update_i_j: boolean): Boolean; var bytes, new_i, new_j: longint; begin bytes:=NameUtf8CodePointLen(j); new_i:=i; new_j:=j; { ensure that a part of an UTF-8 codepoint isn't interpreted as '*' or '?' } repeat dec(bytes); Result:= (new_j<=LenName) and (new_i<=LenPat) and (Pattern[new_i]=Name[new_j]); inc(new_i); inc(new_j); until not(Result) or (bytes=0); if update_i_j then begin i:=new_i; j:=new_j; end; end; Function DoFNMatch(i,j:longint):Boolean; Var UTF8, Found : boolean; Begin Found:=true; { ensure that we don't skip partial characters in UTF-8-encoded strings } UTF8:=StringCodePage(Name)=CP_UTF8; While Found and (i<=LenPat) Do Begin Case Pattern[i] of '?' : begin Found:=(j<=LenName); if UTF8 then GoToLastByteOfUtf8CodePoint(j); end; '*' : Begin {find the next character in pattern, different of ? and *} while Found do begin inc(i); if i>LenPat then Break; case Pattern[i] of '*' : ; '?' : begin if j>LenName then begin DoFNMatch:=false; Exit; end; if UTF8 then GoToLastByteOfUtf8CodePoint(j); inc(j); end; else Found:=false; end; end; Assert((i>LenPat) or ( (Pattern[i]<>'*') and (Pattern[i]<>'?') )); { Now, find in name the character which i points to, if the * or ? wasn't the last character in the pattern, else, use up all the chars in name } Found:=false; if (i<=LenPat) then begin repeat {find a letter (not only first !) which maches pattern[i]} if UTF8 then begin while (j<=LenName) and ((name[j]<>pattern[i]) or not CompareUtf8CodePoint(i,j,false)) do begin GoToLastByteOfUtf8CodePoint(j); inc(j); end; end else begin while (j<=LenName) and (name[j]<>pattern[i]) do inc (j); end; if (jLenName, not j>=LenName. That's because when we 'need to look further' and j = LenName then loop must not terminate. } until (j>LenName); end else begin j:=LenName;{we can stop} Found:=true; end; end; #128..#255: begin Found:=(j<=LenName) and (pattern[i]=name[j]); if Found and UTF8 then begin { ensure that a part of an UTF-8 codepoint isn't matched with '*' or '?' } Found:=CompareUtf8CodePoint(i,j,true); { at this point, either Found is false (and we'll stop), or both pattern[i] and name[j] are the end of the current code point and equal } end end else {not a wildcard character in pattern} Found:=(j<=LenName) and (pattern[i]=name[j]); end; inc(i); inc(j); end; DoFnMatch:=Found and (j>LenName); end; Begin {start FNMatch} LenPat:=Length(Pattern); LenName:=Length(Name); FNMatch:=DoFNMatch(1,1); End; Type TUnixFindData = Record NamePos : LongInt; {to track which search this is} DirPtr : Pointer; {directory pointer for reading directory} SearchSpec : RawbyteString; SearchType : Byte; {0=normal, 1=open will close, 2=only 1 file} SearchAttr : Longint; {attribute we are searching for} End; PUnixFindData = ^TUnixFindData; Procedure InternalFindClose(var Handle: Pointer); var D: PUnixFindData absolute Handle; begin If D=Nil then Exit; if D^.SearchType=0 then begin if D^.dirptr<>nil then fpclosedir(pdir(D^.dirptr)^); end; Dispose(D); D:=nil; end; Function FindGetFileInfo(const s: RawByteString; var f: TAbstractSearchRec; var Name: RawByteString):boolean; Var {$ifdef USE_STATX} stx : {$ifdef FPC_DOTTEDUNITS}LinuxApi.{$else}linux.{$endif}tstatx; {$endif USE_STATX} st : BU.stat; WinAttr : longint; begin {$ifdef USE_STATX} {$ifdef FPC_USE_LIBC} if (@statx=nil) then FindGetFileInfo:=false else {$endif} if Assigned(f.FindHandle) and ( (PUnixFindData(F.FindHandle)^.searchattr and faSymlink) > 0) then FindGetFileInfo:=statx(AT_FDCWD,pointer(s),AT_SYMLINK_NOFOLLOW,STATX_ALL,stx)=0 else begin FindGetFileInfo:=statx(AT_FDCWD,pointer(s),0,STATX_ALL,stx)=0; end; if FindGetFileInfo then begin WinAttr:=LinuxToWinAttr(s,stx); FindGetFileInfo:=(WinAttr and Not(PUnixFindData(f.FindHandle)^.searchattr))=0; if FindGetFileInfo then begin Name:=ExtractFileName(s); f.Attr:=WinAttr; f.Size:=stx.stx_Size; f.Mode:=stx.stx_mode; f.Time:=stx.stx_mtime.tv_sec; FindGetFileInfo:=true; end; end { no statx? try stat } else if fpgeterrno=ESysENOSYS then {$endif USE_STATX} begin if Assigned(f.FindHandle) and ( (PUnixFindData(F.FindHandle)^.searchattr and faSymlink) > 0) then FindGetFileInfo:=(fplstat(pointer(s),st)=0) else FindGetFileInfo:=(fpstat(pointer(s),st)=0); if not FindGetFileInfo then exit; WinAttr:=LinuxToWinAttr(s,st); FindGetFileInfo:=(WinAttr and Not(PUnixFindData(f.FindHandle)^.searchattr))=0; if FindGetFileInfo then begin Name:=ExtractFileName(s); f.Attr:=WinAttr; f.Size:=st.st_Size; f.Mode:=st.st_mode; f.Time:=st.st_mtime; FindGetFileInfo:=true; end; end; end; // Returns the FOUND filename. Error code <> 0 if no file found Function InternalFindNext (var Rslt : TAbstractSearchRec; var Name : RawByteString) : Longint; Var DirName : RawByteString; FName, SName : RawBytestring; Found, Finished : boolean; p : pdirent; UnixFindData : PUnixFindData; Begin Result:=-1; UnixFindData:=PUnixFindData(Rslt.FindHandle); { SearchSpec='' means that there were no wild cards, so only one file to find. } If (UnixFindData=Nil) or (UnixFindData^.SearchSpec='') then exit; if (UnixFindData^.SearchType=0) and (UnixFindData^.Dirptr=nil) then begin If UnixFindData^.NamePos = 0 Then DirName:='./' Else DirName:=Copy(UnixFindData^.SearchSpec,1,UnixFindData^.NamePos); UnixFindData^.DirPtr := fpopendir(PAnsiChar(DirName)); end; SName:=Copy(UnixFindData^.SearchSpec,UnixFindData^.NamePos+1,Length(UnixFindData^.SearchSpec)); Found:=False; Finished:=(UnixFindData^.dirptr=nil); While Not Finished Do Begin p:=fpreaddir(pdir(UnixFindData^.dirptr)^); if p=nil then FName:='' else FName:=p^.d_name; If FName='' Then Finished:=True Else Begin SetCodePage(FName,DefaultFileSystemCodePage,false); If FNMatch(SName,FName) Then Begin Found:=FindGetFileInfo(Copy(UnixFindData^.SearchSpec,1,UnixFindData^.NamePos)+FName,Rslt,Name); if Found then begin Result:=0; exit; end; End; End; End; End; Function InternalFindFirst (Const Path : RawByteString; Attr : Longint; out Rslt : TAbstractSearchRec; var Name: RawByteString) : Longint; { opens dir and calls FindNext if needed. } var UnixFindData : PUnixFindData; Begin Result:=-1; { this is safe even though Rslt actually contains a refcounted field, because it is declared as "out" and hence has already been initialised } fillchar(Rslt,sizeof(Rslt),0); if Path='' then exit; { Allocate UnixFindData (we always need it, for the search attributes) } New(UnixFindData); FillChar(UnixFindData^,sizeof(UnixFindData^),0); Rslt.FindHandle:=UnixFindData; {We always also search for readonly and archive, regardless of Attr:} UnixFindData^.SearchAttr := Attr or faarchive or fareadonly; {Wildcards?} if (Pos('?',Path)=0) and (Pos('*',Path)=0) then begin if FindGetFileInfo(ToSingleByteFileSystemEncodedFileName(Path),Rslt,Name) then Result:=0; end else begin {Create Info} UnixFindData^.SearchSpec := ToSingleByteFileSystemEncodedFileName(Path); UnixFindData^.NamePos := Length(UnixFindData^.SearchSpec); while (UnixFindData^.NamePos>0) and (UnixFindData^.SearchSpec[UnixFindData^.NamePos]<>'/') do dec(UnixFindData^.NamePos); Result:=InternalFindNext(Rslt,Name); end; If (Result<>0) then InternalFindClose(Rslt.FindHandle); End; Function FileGetDate (Handle : Longint) : Int64; Var Info : Stat; {$ifdef USE_STATX} Infox : TStatx; {$endif USE_STATX} Char0 : AnsiChar; begin Result:=-1; {$ifdef USE_STATX} Char0:=#0; if {$ifdef FPC_USE_LIBC} (@statx<>nil) and {$endif} (statx(Handle,@Char0,AT_EMPTY_PATH,STATX_MTIME,Infox)=0) then Result:=Infox.stx_Mtime.tv_sec else if fpgeterrno=ESysENOSYS then {$endif USE_STATX} begin If fpFStat(Handle,Info)=0 then Result:=Info.st_Mtime; end; end; Function FileSetDate (Handle : Longint;Age : Int64) : Longint; {$ifdef USE_FUTIMES} var times : TTimespecArr; {$endif USE_FUTIMES} begin Result:=0; {$ifdef USE_FUTIMES} times[0].tv_sec:=Age; times[0].tv_nsec:=0; times[1].tv_sec:=Age; times[1].tv_nsec:=0; if fpfutimens(Handle,times) = -1 then Result:=fpgeterrno; {$else USE_FUTIMES} FileSetDate:=-1; {$endif USE_FUTIMES} end; Function FileGetAttr (Const FileName : RawByteString) : Longint; Var SystemFileName: RawByteString; Info : Stat; res : Integer; begin SystemFileName:=ToSingleByteFileSystemEncodedFileName(FileName); res:=FpLStat(pointer(SystemFileName),Info); if res<0 then res:=FpStat(pointer(SystemFileName),Info); if res<0 then Result:=-1 Else Result:=LinuxToWinAttr(SystemFileName,Info); end; Function FileSetAttr (Const Filename : RawByteString; Attr: longint) : Longint; begin Result:=-1; end; Function DeleteFile (Const FileName : RawByteString) : Boolean; var SystemFileName: RawByteString; begin SystemFileName:=ToSingleByteFileSystemEncodedFileName(FileName); Result:=fpUnLink (PAnsiChar(SystemFileName))>=0; end; Function RenameFile (Const OldName, NewName : RawByteString) : Boolean; var SystemOldName, SystemNewName: RawByteString; begin SystemOldName:=ToSingleByteFileSystemEncodedFileName(OldName); SystemNewName:=ToSingleByteFileSystemEncodedFileName(NewName); RenameFile:=BU.FpRename(pointer(SystemOldName),pointer(SystemNewName))>=0; end; Function FileIsReadOnly(const FileName: RawByteString): Boolean; var SystemFileName: RawByteString; begin SystemFileName:=ToSingleByteFileSystemEncodedFileName(FileName); Result:=fpAccess(PAnsiChar(SystemFileName),W_OK)<>0; end; Function FileSetDate (Const FileName : RawByteString; Age : Int64) : Longint; var SystemFileName: RawByteString; {$ifdef USE_UTIMENSAT} times : TTimespecArr; {$endif USE_UTIMENSAT} t: TUTimBuf; begin SystemFileName:=ToSingleByteFileSystemEncodedFileName(FileName); Result:=0; {$ifdef USE_UTIMENSAT} times[0].tv_sec:=Age; times[0].tv_nsec:=0; times[1].tv_sec:=Age; times[1].tv_nsec:=0; if utimensat(AT_FDCWD,PAnsiChar(SystemFileName),times,0) = -1 then Result:=fpgeterrno; if fpgeterrno=ESysENOSYS then {$endif USE_UTIMENSAT} begin Result:=0; t.actime:= Age; t.modtime:=Age; if fputime(PAnsiChar(SystemFileName), @t) = -1 then Result:=fpgeterrno; end end; {$IF defined(DARWIN)} Function IsFileNameCaseSensitive(Const aFileName : RawByteString) : Boolean; var res : clong; begin res:=FpPathconf(PAnsiChar(aFileName),11 {_PC_CASE_SENSITIVE }); { fall back to default if path is not found } if res<0 then Result:=FileNameCaseSensitive else Result:=res<>0; end; Function IsFileNameCaseSensitive(Const aFileName : UnicodeString) : Boolean; begin Result:=IsFileNameCaseSensitive(RawByteString(aFileName)); end; Function IsFileNameCasePreserving(Const aFileName : RawByteString) : Boolean; var res : clong; begin res:=FpPathconf(PAnsiChar(aFileName),12 { _PC_CASE_PRESERVING }); if res<0 then { fall back to default if path is not found } Result:=FileNameCasePreserving else Result:=res<>0; end; Function IsFileNameCasePreserving(Const aFileName : UnicodeString) : Boolean; begin Result:=IsFileNameCasePreserving(RawByteString(aFileName)); end; {$ENDIF defined(DARWIN)} {**************************************************************************** Disk Functions ****************************************************************************} { The Diskfree and Disksize functions need a file on the specified drive, since this is required for the fpstatfs system call. These filenames are set in drivestr[0..26], and have been preset to : 0 - '.' (default drive - hence current dir is ok.) 1 - '/fd0/.' (floppy drive 1 - should be adapted to local system ) 2 - '/fd1/.' (floppy drive 2 - should be adapted to local system ) 3 - '/' (C: equivalent of dos is the root partition) 4..26 (can be set by you're own applications) ! Use AddDisk() to Add new drives ! They both return -1 when a failure occurs. } Const FixDriveStr : array[0..3] of PAnsiChar=( '.', '/fd0/.', '/fd1/.', '/.' ); var Drives : byte = 4; DriveStr : array[4..26] of PAnsiChar; Function GetDriveStr(Drive : Byte) : PAnsiChar; begin case Drive of Low(FixDriveStr)..High(FixDriveStr): Result := FixDriveStr[Drive]; Low(DriveStr)..High(DriveStr): Result := DriveStr[Drive]; else Result := nil; end; end; Function DiskFree(Drive: Byte): int64; var p : PAnsiChar; fs : TStatfs; Begin p:=GetDriveStr(Drive); if (p<>nil) and (fpStatFS(p, @fs)<>-1) then DiskFree := int64(fs.bavail)*int64(fs.bsize) else DiskFree := -1; End; Function DiskSize(Drive: Byte): int64; var p : PAnsiChar; fs : TStatfs; Begin p:=GetDriveStr(Drive); if (p<>nil) and (fpStatFS(p, @fs)<>-1) then DiskSize := int64(fs.blocks)*int64(fs.bsize) else DiskSize := -1; End; Function AddDisk(const path: string): Byte; begin if DriveStr[Drives]<>nil then FreeMem(DriveStr[Drives]); GetMem(DriveStr[Drives],length(Path)+1); StrPCopy(DriveStr[Drives],path); Result:=Drives; inc(Drives); if Drives>High(DriveStr) then Drives:=Low(DriveStr); end; Procedure FreeDriveStr; var i: longint; begin for i:=low(drivestr) to high(drivestr) do if assigned(drivestr[i]) then begin freemem(drivestr[i]); drivestr[i]:=nil; end; end; {**************************************************************************** Misc Functions ****************************************************************************} {**************************************************************************** Locale Functions ****************************************************************************} Function GetEpochTime: time_t; { Get the number of seconds since 00:00, January 1 1970, GMT the time NOT corrected any way } begin GetEpochTime:=fptime; end; Procedure DoGetUniversalDateTime(var year, month, day, hour, min, sec, msec, usec : word); var tz:timeval; begin fpgettimeofday(@tz,nil); EpochToUniversal(tz.tv_sec,year,month,day,hour,min,sec); msec:=tz.tv_usec div 1000; usec:=tz.tv_usec mod 1000; end; // Now, adjusted to local time. Procedure DoGetLocalDateTime(var year, month, day, hour, min, sec, msec, usec : word); var tz:timeval; begin fpgettimeofday(@tz,nil); EpochToLocal(tz.tv_sec,year,month,day,hour,min,sec); msec:=tz.tv_usec div 1000; usec:=tz.tv_usec mod 1000; end; procedure GetTime(var hour,min,sec,msec,usec:word); Var year,day,month:Word; begin DoGetLocalDateTime(year,month,day,hour,min,sec,msec,usec); end; procedure GetTime(var hour,min,sec,sec100:word); { Gets the current time, adjusted to local time } var year,day,month,usec : word; begin DoGetLocalDateTime(year,month,day,hour,min,sec,sec100,usec); sec100:=sec100 div 10; end; Procedure GetTime(Var Hour,Min,Sec:Word); { Gets the current time, adjusted to local time } var year,day,month,msec,usec : Word; Begin DoGetLocalDateTime(year,month,day,hour,min,sec,msec,usec); End; Procedure GetDate(Var Year,Month,Day:Word); { Gets the current date, adjusted to local time } var hour,minute,second,msec,usec : word; Begin DoGetLocalDateTime(year,month,day,hour,minute,second,msec,usec); End; Procedure GetDateTime(Var Year,Month,Day,hour,minute,second:Word); { Gets the current date, adjusted to local time } Var usec,msec : word; Begin DoGetLocalDateTime(year,month,day,hour,minute,second,msec,usec); End; {$ifndef FPUNONE} Procedure GetLocalTime(var SystemTime: TSystemTime); var usecs : Word; begin DoGetLocalDateTime(SystemTime.Year, SystemTime.Month, SystemTime.Day,SystemTime.Hour, SystemTime.Minute, SystemTime.Second, SystemTime.MilliSecond, usecs); SystemTime.DayOfWeek:=DayOfWeek(EncodeDate(SystemTime.Year,SystemTime.Month,SystemTime.Day))-1; end ; {$endif} Procedure InitAnsi; Var i : longint; begin { Fill table entries 0 to 127 } for i := 0 to 96 do UpperCaseTable[i] := chr(i); for i := 97 to 122 do UpperCaseTable[i] := chr(i - 32); for i := 123 to 191 do UpperCaseTable[i] := chr(i); Move (CPISO88591UCT,UpperCaseTable[192],SizeOf(CPISO88591UCT)); for i := 0 to 64 do LowerCaseTable[i] := chr(i); for i := 65 to 90 do LowerCaseTable[i] := chr(i + 32); for i := 91 to 191 do LowerCaseTable[i] := chr(i); Move (CPISO88591LCT,LowerCaseTable[192],SizeOf(CPISO88591UCT)); end; Procedure InitInternational; begin InitInternationalGeneric; InitAnsi; end; function SysErrorMessage(ErrorCode: Integer): String; begin Result:=StrError(ErrorCode); end; {**************************************************************************** OS utility functions ****************************************************************************} Function GetEnvironmentVariable(Const EnvVar : AnsiString) : AnsiString; begin Result:=BU.FPGetenv(PAnsiChar(pointer(EnvVar))); end; Function GetEnvironmentVariableCount : Integer; begin Result:=FPCCountEnvVar(EnvP); end; Function GetEnvironmentString(Index : Integer) : RTLString; begin Result:=FPCGetEnvStrFromP(Envp,Index); end; function ExecuteProcess(Const Path: RawByteString; Const ComLine: RawByteString;Flags:TExecuteFlags=[]):integer; var pid : longint; e : EOSError; CommandLine: RawByteString; LPath : RawByteString; cmdline2 : PPAnsiChar; Begin { always surround the name of the application by quotes so that long filenames will always be accepted. But don't do it if there are already double quotes! } // Only place we still parse cmdline2:=nil; LPath:=Path; UniqueString(LPath); SetCodePage(LPath,DefaultFileSystemCodePage,true); if Comline<>'' Then begin CommandLine:=ComLine; { Make an unique copy because stringtoppchar modifies the string, and force conversion to intended fscp } UniqueString(CommandLine); SetCodePage(CommandLine,DefaultFileSystemCodePage,true); cmdline2:=StringtoPPChar(CommandLine,1); cmdline2^:=PAnsiChar(pointer(LPath)); end else begin getmem(cmdline2,2*sizeof(PAnsiChar)); cmdline2^:=PAnsiChar(LPath); cmdline2[1]:=nil; end; {$ifdef USE_VFORK} pid:=fpvFork; {$else USE_VFORK} pid:=fpFork; {$endif USE_VFORK} if pid=0 then begin {The child does the actual exec, and then exits} fpexecve(PAnsiChar(pointer(LPath)),Cmdline2,envp); { If the execve fails, we return an exitvalue of 127, to let it be known} fpExit(127); end else if pid=-1 then {Fork failed} begin e:=EOSError.CreateFmt(SExecuteProcessFailed,[LPath,-1]); e.ErrorCode:=-1; raise e; end; { We're in the parent, let's wait. } result:=WaitProcess(pid); // WaitPid and result-convert if Comline<>'' Then freemem(cmdline2); if (result<0) or (result=127) then begin E:=EOSError.CreateFmt(SExecuteProcessFailed,[LPath,result]); E.ErrorCode:=result; Raise E; end; End; function ExecuteProcess(Const Path: RawByteString; Const ComLine: Array Of RawByteString;Flags:TExecuteFlags=[]):integer; var pid : longint; e : EOSError; Begin pid:=fpFork; if pid=0 then begin {The child does the actual exec, and then exits} fpexecl(Path,Comline); { If the execve fails, we return an exitvalue of 127, to let it be known} fpExit(127); end else if pid=-1 then {Fork failed} begin e:=EOSError.CreateFmt(SExecuteProcessFailed,[Path,-1]); e.ErrorCode:=-1; raise e; end; { We're in the parent, let's wait. } result:=WaitProcess(pid); // WaitPid and result-convert if (result<0) or (result=127) then begin E:=EOSError.CreateFmt(SExecuteProcessFailed,[Path,result]); E.ErrorCode:=result; raise E; end; End; procedure Sleep(milliseconds: Cardinal); Var timeout,timeoutresult : TTimespec; res: cint; begin timeout.tv_sec:=milliseconds div 1000; timeout.tv_nsec:=1000*1000*(milliseconds mod 1000); repeat res:=fpnanosleep(@timeout,@timeoutresult); timeout:=timeoutresult; until (res<>-1) or (fpgeterrno<>ESysEINTR); end; Function GetLastOSError : Integer; begin Result:=fpgetErrNo; end; { --------------------------------------------------------------------- Application config files ---------------------------------------------------------------------} {$ifdef android} var _HomeDir: string; _HasPackageDataDir: boolean; Function GetHomeDir : String; var h: longint; i: longint; begin Result:=_HomeDir; if Result <> '' then exit; if IsLibrary then begin // For shared library get the package name of a host Java application h:=FileOpen('/proc/self/cmdline', fmOpenRead or fmShareDenyNone); if h >= 0 then begin SetLength(Result, MAX_PATH); SetLength(Result, FileRead(h, Result[1], Length(Result))); SetLength(Result, strlen(PAnsiChar(Result))); FileClose(h); Result:='/data/data/' + Result; _HasPackageDataDir:=DirectoryExists(Result); if _HasPackageDataDir then begin Result:=Result + '/files/'; ForceDirectories(Result); end else Result:=''; // No package end; end; if Result = '' then Result:='/data/local/tmp/'; _HomeDir:=Result; end; Function XdgConfigHome : String; begin Result:=GetHomeDir; end; {$else} Function GetHomeDir : String; begin Result:=GetEnvironmentVariable('HOME'); If (Result<>'') then Result:=IncludeTrailingPathDelimiter(Result); end; { Follows base-dir spec, see [http://freedesktop.org/Standards/basedir-spec]. Always ends with PathDelim. } Function XdgConfigHome : String; begin Result:=GetEnvironmentVariable('XDG_CONFIG_HOME'); if (Result='') then Result:=GetHomeDir + '.config/' else Result:=IncludeTrailingPathDelimiter(Result); end; {$endif android} Function GetAppConfigDir(Global : Boolean) : String; begin If Global then Result:=IncludeTrailingPathDelimiter(SysConfigDir) else Result:=IncludeTrailingPathDelimiter(XdgConfigHome); {$ifdef android} if _HasPackageDataDir then exit; {$endif android} if VendorName<>'' then Result:=IncludeTrailingPathDelimiter(Result+VendorName); Result:=IncludeTrailingPathDelimiter(Result+ApplicationName); end; Function GetAppConfigFile(Global : Boolean; SubDir : Boolean) : String; begin If Global then Result:=IncludeTrailingPathDelimiter(SysConfigDir) else Result:=IncludeTrailingPathDelimiter(XdgConfigHome); {$ifdef android} if _HasPackageDataDir then begin Result:=Result+'config'+ConfigExtension; exit; end; {$endif android} if SubDir then begin if VendorName<>'' then Result:=IncludeTrailingPathDelimiter(Result+VendorName); Result:=IncludeTrailingPathDelimiter(Result+ApplicationName); end; Result:=Result+ApplicationName+ConfigExtension; end; {**************************************************************************** GetTempDir ****************************************************************************} Function GetTempDir(Global : Boolean) : String; begin If Assigned(OnGetTempDir) then Result:=OnGetTempDir(Global) else begin {$ifdef android} Result:=GetHomeDir + 'tmp'; ForceDirectories(Result); {$else} Result:=GetEnvironmentVariable('TEMP'); If (Result='') Then Result:=GetEnvironmentVariable('TMP'); If (Result='') Then Result:=GetEnvironmentVariable('TMPDIR'); if (Result='') then Result:='/tmp/'; // fallback. {$endif android} end; if (Result<>'') then Result:=IncludeTrailingPathDelimiter(Result); end; {**************************************************************************** GetUserDir ****************************************************************************} Var TheUserDir : String; Function GetUserDir : String; begin If (TheUserDir='') then begin {$ifdef android} TheUserDir:=GetHomeDir; {$else} TheUserDir:=GetEnvironmentVariable('HOME'); {$endif android} if (TheUserDir<>'') then TheUserDir:=IncludeTrailingPathDelimiter(TheUserDir) else TheUserDir:=GetTempDir(False); end; Result:=TheUserDir; end; Procedure SysBeep; begin Write(#7); Flush(Output); end; function GetUniversalTime(var SystemTime: TSystemTime): Boolean; var usecs : Word; begin DoGetUniversalDateTime(SystemTime.Year, SystemTime.Month, SystemTime.Day,SystemTime.Hour, SystemTime.Minute, SystemTime.Second, SystemTime.MilliSecond, usecs); Result:=True; end; function GetLocalTimeOffset: Integer; begin Result := -Tzseconds div 60; end; function GetLocalTimeOffset(const DateTime: TDateTime; const InputIsUTC: Boolean; out Offset: Integer; out IsDST : Boolean): Boolean; var Year, Month, Day, Hour, Minute, Second, MilliSecond: word; UnixTime: Int64; lTZInfo: TTZInfo; begin DecodeDate(DateTime, Year, Month, Day); DecodeTime(DateTime, Hour, Minute, Second, MilliSecond); UnixTime:=UniversalToEpoch(Year, Month, Day, Hour, Minute, Second); {$if declared(GetLocalTimezone)} GetLocalTimeOffset:=GetLocalTimezone(UnixTime,InputIsUTC,lTZInfo); isDST:=lTZInfo.daylight; if GetLocalTimeOffset then Offset:=-lTZInfo.seconds div 60; {$else} GetLocalTimeOffset:=False; {$endif} end; {$ifdef android} procedure InitAndroid; var dlinfo: dl_info; s: string; begin FillChar(dlinfo, sizeof(dlinfo), 0); dladdr(@InitAndroid, @dlinfo); s:=dlinfo.dli_fname; if s <> '' then SetDefaultSysLogTag(ExtractFileName(s)); end; {$endif android} {**************************************************************************** Initialization code ****************************************************************************} Initialization InitExceptions; { Initialize exceptions. OS independent } InitInternational; { Initialize internationalization settings } SysConfigDir:='/etc'; { Initialize system config dir } OnBeep:=@SysBeep; {$ifdef android} InitAndroid; {$endif android} Finalization FreeDriveStr; FreeTerminateProcs; DoneExceptions; end.