{
   $Id: filesys.pas,v 1.1.2.2 2000/12/14 19:27:17 hajny Exp $

   Unit to access the file system
   All file operations except those on open files (see FileCtrl for that)

   Copyright by Marco Schmidt <marco@pool.informatik.rwth-aachen.de>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.


   This library 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.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this library; if not, write to the Free
   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

 ****************************************************************************}
unit FileSys;

interface

{$I platform.inc} { Conditional directives :
                    compiler, operating system }

uses
  ApiComm          { Error handling }

{$IFDEF PPC_FPC}
  , Strings
{$ENDIF}

{$IFDEF OS_DOS}
  , DOS           { GetFAttr, GetFTime, FindFirst, FindNext, ... }
{$else not OS_DOS}
 {$ifdef PPC_FPC}
  {$ifdef OS_WINDOWS}
  {$define OS_DOS}
  , DOS
  {$endif OS_WIN32}
  {$IFDEF OS_OS2}
  {$DEFINE OS_DOS}
  , DOS
  {$ENDIF OS_OS2}
 {$endif PPC_FPC}
{$ENDIF}

{$IFDEF OS_LINUX}
  , linux
{$ENDIF}
  ;


const

  { Maximum length of a file name (must be <= 255, for we use
    standard Pascal strings) }
  MaxNameLength = {$IFDEF PPC_BP}
                  79;
                  {$ELSE}
                  255;
                  {$ENDIF}

  { Character to separate directories in a path }
  PathSeparator = {$IFDEF OS_Linux}
                  '/';
                  {$ELSE}
                  '\';
                  {$ENDIF}

  { Defines if a character is inserted into a number string every three
    digits;
    true :  returns "3,555,234"
    false : returns "3555234" }
  SeparateThousands : Boolean = true;

  { Character to be used to separate three digits in FileIntToString }
  ThousandsSeparator : Char = ',';

  { "CheckName" function return values }
  cnUnknown     = 0;
  cnFile        = 1;
  cnDirectory   = 2;

  { File attribute bit masks }

  faReadOnly    = $0001;
  faSystem      = $0002;
  faHidden      = $0004;
  faVolumeID    = $0008;
  faDirectory   = $0010;
  faArchive     = $0020;
  faAnyFile     = faReadOnly or
                  faSystem or
                  faHidden or
                  faVolumeID or
                  faDirectory or
                  faArchive;      { = $003f }

  { Wildcard characters for use with "ContainsWildcards" }

  NumWildcardChars = 2;
  WildcardChars : Array[0..NumWildcardChars-1] of Char =
      ('*', '?');

type
  { file attribute type }
  TFileAttr = {$IFDEF PPC_BP}
              Word;               { DOS: RSHVAD }
              {$ELSE}
              Longint;            { Any other OS }
              {$ENDIF}

  { Stores date and time in a system-independent way }
  TDateTime = packed record
    DOW    : Byte; { 0=Sunday, 1=Monday, ... }
    Day    : Byte; { 1..31 }
    Month  : Byte; { 1..12 }
    Year   : Word; { 1601..3999 }
    IsLeap : Boolean; { is "Year" a leap year ? }
    Hour   : Byte; { 0..23 }
    Minute : Byte; { 0..59 }
    Second : Byte; { 0..59 }
    Valid  : Boolean; { set by "CheckDateTime" }
  end;

  { Stores file size & offset values;
    may have to be changed for other environments }
  TFileInt  = Longint; { 32 bit signed, as we have no unsigned 32 bit type }

  { directory / file name }
  TFileName = String[MaxNameLength];

  { record to describe a file or directory entry;
    used in combination with a file search }
  TFileDescriptor = packed record
    { fields available for all platforms }
    Attr             : TFileAttr;
    IsDirectory      : Boolean;
    LastModification : TDateTime;
    Name             : TFileName;
    Size             : TFileInt;
    { platform-specific fields }
    {$IFDEF OS_LINUX}
    Created          : TDateTime;
    LastAccessed     : TDateTime;
    {$ENDIF OS_LINUX}
  end;

  { Search record declaration for FPC for DOS (we're not using the DOS unit
    that provides SearchRec) }

  {$IFDEF PPC_FPC}
    {$IFDEF OS_DOS}

    type
      TDOSSearchRec = packed record
        Fill:     Array[1..21] of Byte;
        Attr:     Byte;
        Time:     Longint;
        Reserved: Word; { requires the DOS extender (DJ GNU-C) }
        Size:     Longint;
        Name:     String[15]; { the same size as declared by (DJ GNU C) }
      end;

    {$ENDIF OS_DOS}
  {$ENDIF PPC_FPC}

  { File search record to be used with
    StartSearch, ContinueSearch and TerminateSearch }

  TFileSearch = packed record
    { Input fields for all platforms }
    Specs   : TFileName;
    { OS-specific input fields }
    {$IFDEF OS_DOS}
    Attr    : TFileAttr;
    {$ENDIF}

    { Output fields for all platforms }
    FD      : TFileDescriptor;
    Success : Boolean;

    { OS-specific output fields }

    {$IFDEF OS_Linux}
    GL : PGlob;
    {$ELSE OS_Linux}
    SR      : DOS.SearchRec;
    {$ENDIF OS_Linux}
  end;

procedure CheckDateTime(var DT: TDateTime);
function  CheckName(AName: TFileName): Byte;
function  ContainsWildcards(AName: TFileName): Boolean;
procedure ContinueSearch(var FS: TFileSearch);
procedure CreateDir(AName: TFileName);
function  DateToString(const DT: TDateTime): String;
procedure DeleteDir(AName: TFileName);
procedure DeleteFile(AName: TFileName);
function  EqualNames(Name1, Name2: TFileName): Boolean;
function  Exists(AName: TFileName): Boolean;
function  ExpandName(AName: TFileName): TFileName;
function  FileAttrToString(AFileAttr: TFileAttr): String;
function  FileIntToString(FI: TFileInt): String;
function  GetCurrentDir: TFileName;
procedure GetFAttr(AName: TFileName; var Attr: TFileAttr);
procedure GetFTime(AName: TFileName; var DT: TDateTime);
function  IsValidName(AName: TFileName) : Boolean;
procedure RenameDir(OldName, NewName: TFileName);
procedure RenameFile(OldName, NewName: TFileName);
procedure SetCurrentDir(AName: TFileName);
procedure SetFAttr(AName: TFileName; AFileAttr: TFileAttr);
procedure SetFTime(AName: TFileName; DT: TDateTime);
procedure SplitName(AName: TFileName; var Path, RawName, Extension: TFileName);
procedure StartSearch(var FS: TFileSearch);
procedure TerminateSearch(var FS: TFileSearch);
function  TimeToString(DT: TDateTime): String;

implementation

{ Structure of the implementation section
  ---------------------------------------
  - proc. & functions that do not appear in the interface section and
    are the same for all platforms
  - proc. & functions that do appear in the interface section and
    are the same for all platforms
  - proc. & functions that do not appear in the interface section and
    are DOS-specific
  - proc. & functions that do appear in the interface section and
    are not the same for all platforms
}

{ procedures and functions that do not appear in the interface section and
  are the same for all platforms }

function weekday(y,m,d : longint) : longint;

{ Calculates th day of the week. Florian provided this.
  returns -1 on error }

    var
       century_offset : integer;
       temp : longint;
       _is_leap_year : boolean;

    const
       month_table : array[1..12] of longint = (1,4,4,0,2,5,0,3,6,1,4,6);

  function is_leap_year(y : longint) : boolean;

    begin
       if (y mod 100)=0 then
         is_leap_year:=((y mod 400)=0)
       else
         is_leap_year:=(y mod 4)=0;
    end;

  { Beginning of weekday }
  begin
       if (m<1) or (m>12) then
         begin
            weekday:=-1;
            exit;
         end;
       case y of
          1700..1799 : century_offset:=4;
          1800..1899 : century_offset:=2;
          1900..1999 : century_offset:=0;
          2000..2099 : century_offset:=-1;
          else
            begin
               if (y>=2100) then
                 begin
                 end;
               weekday:=-1;
               exit;
            end;
       end;
       _is_leap_year:=is_leap_year(y);
       y:=y mod 100;
       temp:=(y div 12)+(y mod 12)+((y mod 12) div 4);
       temp:=temp mod 7;
       temp:=(temp+month_table[m]+d) mod 7;
       { do some corrections for special years }
       { other century ? }
       inc(temp,century_offset);
       { leap year correction }
       if _is_leap_year and (m<3) then
         dec(temp);
       { now is sonday 1, but should be for example 0 }
       dec(temp);
       { the result could be less than zero }
       while temp<0 do
         inc(temp,7);
       weekday:=temp mod 7;
    end;


{ Returns Longint value as String }
function LongToStr(L: Longint): String;
var
  S: String[20];
begin
  System.Str(L, S);
  LongToStr := S;
end;

{ Returns Longint value as String, adding a leading '0' character if value
  is >= 0 and <= 9 (LZ = leading zero) }
function LongToStrLZ(L: Longint): String;
var
  Z: String[1];
begin
  if (L >= 0) and (L <= 9)
    then Z := '0'
    else Z := '';
  LongToStrLZ := Z + LongToStr(L);
end;

{ Procedures and functions that do appear in the interface section and are
  the same for all platforms }

{ Checks if date and time in "dt" is valid; also determines the day of the
  week }
procedure CheckDateTime(var DT: TDateTime);
const
  MonthLength : array[1..12] of Byte =
    (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
begin
  DT.Valid := false;
  { check data that is within a fixed range }
  with DT do
    if (Hour < 0) or (Hour > 23) or
       (Minute < 0) or (Minute > 59) or
       (Second < 0) or (Second > 59) or
       (Month < 1) or (Month > 12) or
       (Day < 1) or
       (Year < 1600) or (Year > 3999)
      then  exit;
  { determine if year is leap year }
  DT.IsLeap := ((dt.Year mod 4) = 0) and
                 (not (((dt.Year mod 100)  = 0) and
                      ((dt.Year mod 400) <> 0)));
  { check if day is within limits }
  if ( DT.IsLeap      and (dt.Month = 2) and (dt.Day > 29)) or
     ((not dt.IsLeap) and (dt.Day > MonthLength[dt.Month]))
    then exit;
  { date seems to be alright, compute day of the week
    (formula taken from DDJ 06/95 [#231], p.11) }
  if weekday (dt.year,dt.month,dt.day)<0 then
     dt.dow:=0
  else
     dt.dow:=weekday(dt.year,dt.month,dt.day);
{  Removed - caused segfault in linux. Michael.

  dt.DOW := (((( 3 * (dt.Year) - ( 7 * ((dt.Year) +
            ((dt.Month)+9) div 12)) div 4 +
            (23 * (dt.Month)) div 9 + (dt.Day) + 2 +
            (((dt.Year) - Ord ((dt.Month) < 3)) div 100 + 1)
             * 3 div 4 - 16 ) + 1 ) mod 7));
}
  dt.Valid := true;
end;

{ Returns if AName contains at least one of the characters from global
  constant WildcardChars }
function ContainsWildcards(AName: TFileName): Boolean;
var
  I, J: Longint;
begin
  ContainsWildcards := false;
  if (Length(AName) = 0)
    then exit;
  { compare each character in AName with each character in WildCards }
  for I := 1 to Length (AName) do
    for J := 0 to NumWildcardChars-1 do
      if (AName[I] = WildcardChars[J])
        then begin
               ContainsWildcards := true;
               exit;
             end;
end;

{ Returns date part of TDateTime as String : "Tue 29 Jul 1997" }
function DateToString(const DT: TDateTime): String;
const
  DOWNames : array[0..6] of String[3] =
    ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');
  MonthNames : array[1..12] of String[3] =
    ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
     'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
begin
  if DT.Valid
    then DateToString := DOWNames [dt.DOW] + ' ' +
                         LongToStrLZ (dt.Day) + ' ' +
                         MonthNames [dt.Month] + ' ' +
                         LongToStr   (dt.Year)
    else DateToString := '';
end;

{ Returns if two names are considered equal for the file system }
function  EqualNames(Name1, Name2: TFileName): Boolean;
{$IFDEF OS_DOS}
var
  I: Byte;
begin
  { case-insensitive comparision of strings }
  EqualNames := false;
  if (Length(Name1) <> Length(Name2)) or (Length(Name1) = 0)
    then exit;
  for I := 1 to Length(Name1) do
    if (Upcase(Name1[I]) <> Upcase(Name2[I]))
      then exit;
  EqualNames := true;
end;
{$ELSE}
begin
  { case-sensitive comparision of strings }
  EqualNames := (Name1 = Name2);
end;
{$ENDIF}

{ Returns if name "AName" is in use (as file or directory) }
function Exists(AName: TFileName): Boolean;
begin
  Exists := (CheckName (AName) <> cnUnknown);
end;

{ Splits AName into its path, raw name and extension; example:
  "c:\pp\fv\archive.zip" will be split into path "c:\pp\fv\",
  raw name "archive" and extension "zip" }
procedure SplitName(AName: TFileName; var Path, RawName, Extension: TFileName);
var
  HasDot, HasSeparator: Boolean;
  I, NameLength, DotOffset, SeparatorOffset: Longint;
begin
  NameLength := Length(AName);
  Path := '';
  RawName := '';
  Extension := '';
  { search for last separator in name }
  SeparatorOffset := -1;
  HasSeparator := false;
  I := NameLength;
  while (I > 0) and (not HasSeparator) do begin
    if (AName[i] = PathSeparator)
      then begin
             HasSeparator := true;
             SeparatorOffset := I;
           end;
    Dec(I);
  end;
  if HasSeparator
    then begin
           Path := System.Copy(AName, 1, SeparatorOffset);
           SeparatorOffset := SeparatorOffset + 1;
         end
    else SeparatorOffset := 1;
  I := SeparatorOffset;
  { search for last dot in name (not in path /
    think of 'dir/files.old/filename') }
  HasDot := false;
  while (I <= NameLength) do begin
    if (AName[I] = '.')
      then begin
             HasDot := true;
             DotOffset := I;
           end;
    Inc(I);
  end;
  if HasDot
    then begin
           RawName := System.Copy (AName,
                                   SeparatorOffset,
                                   DotOffset-SeparatorOffset);
           Extension := System.Copy (AName,
                                     DotOffset + 1,
                                     NameLength - DotOffset);
         end
    else begin
           { no extension }
           RawName := System.Copy (AName,
                                   SeparatorOffset,
                                   NameLength - SeparatorOffset);
         end;
end;

{ Returns time part of "DT" as "23:04:38" }
function TimeToString(DT: TDateTime): String;
begin
  if DT.Valid
    then TimeToString := LongToStrLZ(DT.Hour) + ':' +
                         LongToStrLZ(DT.Minute) + ':' +
                         LongToStrLZ(DT.Second)
    else TimeToString := '';
end;

{$IFDEF OS_DOS} { procedures & functions for the DOS platform }

{ Functions and procedures not declared in the interface section }

{ Returns date part of dt in DOS format, as unsigned 16 bit integer }
procedure GetDOSDate(DT: TDateTime; var W: Word);
begin
  W := (DT.Day and $1f) or
       ((DT.Month and $f) shl 5) or
       (((DT.Year - 1980) and $7f) shl 9);
end;

{ Returns time part of DT in DOS format, as unsigned 16 bit integer }
procedure GetDOSTime(DT: TDateTime; var W: Word);
begin
  W := ((DT.Second shr 1) and $1f) or
       ((DT.Minute and $3f) shl 5) or
       ((DT.Hour and $1f) shl 11);
end;

{ Returns date and time as 32 bit integer value (DOS time format) }
procedure GetDOSDateTime(DT : TDateTime; var L: Longint);
var
  W: Word;
begin
  GetDOSTime(DT, W);
  L := W;
  GetDOSDate(DT, W);
  L := L + (W * 65536); { shifting by 16 doesn't work everywhere ... }
end;

{ Sets date part of DT to W }
procedure SetDOSDate(W: Word; var DT: TDateTime);
begin
  DT.Day := W and $1f;
  DT.Month := (W shr 5) and $f;
  DT.Year := 1980 + (W shr 9) and $7f;
end;

{ Sets time part of DT to W }
procedure SetDOSTime(W: Word; var DT: TDateTime);
begin
  DT.Second := (W and $1f) shl 1;
  DT.Minute := (W shr 5) and $3f;
  DT.Hour := (W shr 11) and $1f;
end;

{ Sets DT to data from L }
procedure SetDOSDateTime(L: Longint; var DT: TDateTime);
begin
  SetDOSTime(L mod 65536, DT);
  SetDOSDate(L div 65536, DT);
end;

{ Converts DOS.SearchRec to TFileDesciptor }
procedure SearchRecToFileDescriptor (    SR: DOS.SearchRec;
                                     var FD: TFileDescriptor);
begin
  FD.Name := SR.Name;
  FD.Attr := SR.Attr;
  FD.Size := SR.Size;
  FD.IsDirectory := ((SR.Attr and faDirectory) <> 0);
  SetDOSDateTime(SR.Time, FD.LastModification);
  CheckDateTime(FD.LastModification);
end;

{$ENDIF} { OS_DOS }

{$IFDEF OS_LINUX}
{ Functions and procedures not decalred in interface section,
  Linux operating system }

Procedure EpochToDateTime (Epoch : Longint; var DT : TDateTime);
{ Returns a Checked datetime, starting from a Unix epoch-style time }

var y,m,d,h,mi,s : word; { needed because of call by var }

begin
  Linux.EpochToLocal(Epoch,Y,M,D,h,mi,s);
  DT.Year   :=y;
  DT.Month  :=m;
  DT.Day    :=d;
  DT.Hour   :=h;
  DT.Minute :=mi;
  DT.Second :=s;
  CheckDateTime (DT);
end;

Procedure StatToFileDescriptor (Info : Stat; Var Fd : TFileDescriptor);
{Starting from a stat record, returns a TFileDescriptor record.
 Name is not filled in !}
begin
  Fd.Attr:=Info.Mode;
  Fd.IsDirectory:=S_ISDIR(Info.mode);
  EpochToDateTime(Info.Mtime,Fd.LastModification);
  EpochToDateTime(Info.Atime,Fd.LastAccessed);
  EpochToDateTime(Info.Ctime,Fd.Created);
  Fd.Size:=Info.size;
end;
{$ENDIF} {OS_LINUX}

{ Functions and procedures declared in the interface section }

{ Returns type of name as cnXXXX constant (unknown, file, directory) }
function CheckName(AName: TFileName): Byte;
var
  FS: TFileSearch;
begin
  FS.Specs := AName;
  {$IFDEF OS_DOS}
  FS.Attr := faAnyFile;
  {$ENDIF}
  StartSearch(fs);
  if FS.Success
    then begin
           if FS.FD.IsDirectory
             then CheckName := cnDirectory
             else CheckName := cnFile;
         end
    else CheckName := cnUnknown;
  TerminateSearch(FS);
end;

{ Continues a file search started by StartSearch }
procedure ContinueSearch(var FS: TFileSearch);
{$IFDEF OS_Linux}
Var g : PGLob;
    info : stat;

begin
  if Not FS.Success then exit;
  FS.Success:=False;
  if FS.GL=nil then exit; { Paranoia setting }
  g:=FS.GL;
  FS.GL:=FS.GL^.NEXT;
  strdispose(g^.name);
  dispose (g);
  If FS.GL=Nil then exit;
  linux.fstat(strpas(FS.GL^.Name),info);
  if linuxerror<>0 then
    begin
    StatToFileDescriptor (info,FS.FD);
    FS.FD.Name:=strpas(FS.GL^.Name);
    FS.Success:=True;
    end;
end;
{$ELSE OS_Linux}
begin
  if fs.Success
    then begin
           DOS.FindNext(FS.SR);
           FS.Success := (DOS.DOSError = 0);
           if FS.Success
             then SearchRecToFileDescriptor(fs.sr, fs.fd);
         end;
end;
{$ENDIF OS_Linux}

{ Create a new subdirectory AName }
procedure CreateDir(AName : TFileName);
begin
  {$I-}
  System.MkDir(AName);
  {$I+}
  ErrorCode := System.IOResult;
end;

{ Deletes the directory AName }
procedure DeleteDir(AName : TFileName);
begin
  {$I-}
  System.RmDir(AName);
  {$I+}
  ErrorCode := System.IOResult;
end;

{ Deletes the file AName }
procedure DeleteFile(AName: TFileName);
var
  F: file;
begin
  Assign(F, AName);
  {$I-}
  System.Erase(F);
  {$I+}
  ErrorCode := System.IOResult;
end;

{ Returns the full version of AName }
function ExpandName(AName : TFileName): TFileName;
begin
{$IFDEF OS_LINUX}
  ExpandName := Linux.FExpand(AName);
{$ELSE}
  ExpandName := DOS.FExpand(AName);
{$ENDIF}
end;

{ Returns a string version of AFileAttr; OS-dependent }
function FileAttrToString(AFileAttr: TFileAttr): String;
{$IFDEF OS_DOS}
{ Volume Label and Directory are not regarded }
const
  NumChars = 4;
  AttrChars: String[NumChars] = 'RSHA';
  AttrMasks: Array[0..NumChars-1] of Word = (1, 2, 4, 32);
var
  I: Word;
  S: String[NumChars];
begin
  s[0] := Chr(NumChars);
  for I := 1 to NumChars do begin
    if ((AFileAttr and AttrMasks[i-1]) = 0)
      then S[I] := '.'
      else S[I] := AttrChars[i];
  end;
  FileAttrToString := S;
end;
{$ELSE OS_DOS}
{$IFDEF OS_LINUX}
var temp : string[9];
    i : longint;

const
    full = 'rwxrwxrwx';

begin
  temp:='---------';
  for i:=0 to 8 do
    if (AFileAttr and (1 shl i))=(1 shl I) then temp[9-i]:=full[9-i];
  FileAttrToString := Temp;
end;
{$ELSE OS_LINUX}
begin
  FileAttrToString:='';
end;
{$ENDIF OS_LINUX}
{$ENDIF OS_DOS}

{ Returns a string version of the file integer value fi }
function FileIntToString(fi: TFileInt): String;
var
  S: String[14]; { maximum is "-2,147,483,648" }
  I: Integer;    { must be signed ! }
begin
  Str(fi, S);
  if SeparateThousands
    then begin
           I := System.Length(S) - 2;
           while (I > 1) and (not (I = 2) and (s[1] = '-')) do begin
             System.Insert (ThousandsSeparator, S, I);
             Dec(I, 3);
           end;
         end;
  FileIntToString := S;
end;

{ Returns the currently set directory }
function GetCurrentDir: TFileName;
{$IFDEF PPC_BP}
var
  I: Byte;
  R: DOS.Registers;
  S: TFileName;
begin
  { to get a full name, we have to get the drive letter ourselves }

  { get current drive letter first }
  R.AH := $19;
  DOS.MsDos(R);

  S[1] := Chr(Ord('A') + R.AL);
  S[2] := ':';
  S[3] := '\';

  { get current directory }
  R.AH := $47;
  R.DL := $00;
  R.DS := Seg(S[4]);
  R.SI := Ofs(S[4]);
  DOS.MsDos (r);
  if ((R.Flags and FCarry) <> 0)
    then begin
           { error }
         end;

  { determine length of current directory }
  I := 4;
  while (S[I] <> #0) and (I < MaxNameLength) do
    Inc(I);
  S[0] := Chr(I - 1);

  GetCurrentDir := S;
end;
{$ELSE}
var
  S: TFileName;
begin
  System.GetDir(0, S);
  GetCurrentDir := S;
end;
{$ENDIF}

{ Gets attribute of AName }
procedure GetFAttr(AName: TFileName; var Attr: TFileAttr);
{$IFDEF OS_DOS}
var
  F: file;
  W: word;
begin
  Assign(F, AName);
  {$I-}
  DOS.GetFAttr(F, W);
  Attr:=W;
  {$I+}
  ErrorCode := DOS.DOSError;
end;
{$ELSE}
{$IFDEF OS_LINUX}
var
  info : stat;
begin
  Linux.FStat (AName,Info);
  ErrorCode:=LinuxError;
  if ErrorCode<>0 then exit;
  Attr:=Info.Mode;
end;
{$ELSE}
begin
end;
{$ENDIF}
{$ENDIF}

{ Gets date and time of last modification of AName }
procedure GetFTime(AName: TFileName; var DT: TDateTime);
{$IFDEF OS_DOS}
var
  F: file;
  L: Longint;
begin
  DT.Valid := false;
  { open file }
  Assign(F, AName);
  {$I-}
  Reset(F);
  {$I+}
  ErrorCode := System.IOResult;
  if (ErrorCode <> errOK)
    then exit;
  { get date/time of last modification in DOS format }
  {$I-}
  DOS.GetFTime(F, L);
  {$I+}
  ErrorCode := DOS.DOSError;
  if (ErrorCode <> errOK)
    then exit;
  { close file }
  {$I-}
  Close(F);
  {$I+}
  ErrorCode := System.IOResult;
  { convert date/time L to TDateTime format }
  GetDOSDateTime(DT, L);
  CheckDateTime(DT);
end;
{$ELSE}
{$IFDEF OS_LINUX}
var info : Stat;

begin
  Linux.FStat (AName,Info);
  ErrorCode:=LinuxError;
  if ErrorCode<>0 then exit;
  EpochToDateTime (info.mtime,DT);
end;
{$ELSE}
begin
end;
{$ENDIF}
{$ENDIF}

{ Returns if AName is a valid file name (not if it actually exists) }
function IsValidName(AName: TFileName): Boolean;
{$IFDEF OS_DOS}
  { isn't ready yet }

  { Returns if a name (without a path) is valid }
  function ValidName(S: TFileName): Boolean;
  var
    I: Byte;
  begin
    ValidName := false;
    if (Length(S) > 12)
      then exit;
    I := Pos('.', S);

    ValidName := true;
  end;

const
  InvalidChars: String[2] = '*?';

var
  I, J: Longint;
  P, R, E: TFileName;
begin
  IsValidName := false;
  { check for invalid characters }
  for I := 1 to Length(AName) do
    for J := 1 to Length(InvalidChars) do
      if (AName[I] = InvalidChars[J])
        then exit;
  SplitName(AName, P, R, E);
  if (Length(R) > 0) or (Length(E) > 0)
    then begin
           if (not ValidName(R + E))
             then exit;
         end;

  IsValidName := true;
end;
{$ELSE}
{$IFDEF OS_LINUX}
begin
  IsVAlidName:=((pos('?',AName)=0) and (pos('*',AName)=0))
end;
{$ELSE}
begin
  IsValidName:=True;
end;
{$ENDIF}
{$ENDIF}

{ Renames directory from OldName to NewName }
procedure RenameDir(OldName, NewName : TFileName);
begin
  { for DOS, renaming files and directories should be the same ... }
  RenameFile(OldName, NewName);
end;

{ Renames file from OldName to NewName }
procedure RenameFile(OldName, NewName : TFileName);
var
  F: file;
begin
  Assign(F, OldName);
  {$I-}
  System.Rename(F, NewName);
  {$I+}
  ErrorCode := IOResult;
end;

{ Sets current directory to AName }
procedure SetCurrentDir(AName : TFileName);
begin
  {$I-}
  System.ChDir(AName);
  {$I+}
  ErrorCode := IOResult;
end;

{ Sets attribute of file AName to AFileAttr }
procedure SetFAttr(AName: TFileName; AFileAttr: TFileAttr);
{$IFDEF OS_DOS}
var
  F: file;
begin
  Assign(F, AName);
  {$I-}
  DOS.SetFAttr(F, AFileAttr);
  {$I+}
  ErrorCode := DOS.DOSError;
end;
{$ELSE}
{$IFDEF OS_LINUX}
begin
  Linux.Chmod (Aname,AFileAttr);
  ErrorCode:=LinuxError;
end;
{$ELSE}
begin
end;
{$ENDIF}
{$ENDIF}

{ Sets date and time of last modification of file AName to dt }
procedure SetFTime(AName: TFileName; DT: TDateTime);
{$IFDEF OS_DOS}
var
  F: file;
  L: Longint;
begin
  GetDOSDateTime(DT, L);
  Assign(f, AName);
  {$I-}
  DOS.SetFTime(F, L);
  {$I+}
  ErrorCode := DOS.DOSError;
end;
{$ELSE}
{$IFDEF OS_LINUX}
var
  utim : utimebuf;
begin
  utim.actime:=LocalToEpoch(DT.Year,DT.Month,DT.Day,DT.Hour,DT.Minute,DT.second);
  utim.modtime:=utim.actime;
  utime (AName,utim);
  ErrorCode:=linuxerror
end;
{$ELSE}
begin
end;
{$ENDIF}
{$ENDIF}

{ Starts a file search, using input data from fs }
procedure StartSearch(var FS: TFileSearch);
{$IFDEF OS_Linux}
var
  info : stat;
begin
  FS.Success:=False;
  FS.GL:=Linux.Glob(FS.Specs);
  if FS.GL=nil then exit;
  linux.fstat(strpas(FS.GL^.Name),info);
  if linuxerror=0 then
    begin
    StatToFileDescriptor (info,FS.FD);
    FS.FD.Name:=strpas(FS.GL^.Name);
    FS.Success:=True;
    end;
end;
{$ELSE OS_Linux}
{ this version works for every platform/os/bits combination that has a
  working DOS unit : BP/FPC/Virtual Pascal }
begin
  DOS.FindFirst(fs.Specs, fs.Attr, fs.sr);
  fs.Success := (DOS.DOSError = 0);
  if fs.Success
    then SearchRecToFileDescriptor(FS.SR, FS.FD);
end;
{$ENDIF OS_Linux}

{ Terminates a file search }
procedure TerminateSearch (var FS: TFileSearch);
begin
{$IFDEF OS_LINUX}
GlobFree (FS.GL);
{$ELSE}
  {$IFNDEF PPC_BP}
  DOS.FindClose(fs.sr);
  {$ENDIF}
{$ENDIF}
end;

{ Unit initialization }
begin
  { Empty, though we could retrieve the thousands separator and
    date/time formats here (in case the OS supports that) }
end.
{
  $Log: filesys.pas,v $
  Revision 1.1.2.2  2000/12/14 19:27:17  hajny
    * made compilable for OS/2 target

  Revision 1.1.2.1  2000/07/19 05:58:21  michael
  + Fixed bug reported by M. Baikov

  Revision 1.1  2000/07/13 06:29:38  michael
  + Initial import

  Revision 1.2  2000/02/29 11:43:16  pierre
    Common renamed APIComm to avoid problems with free vision

  Revision 1.1  2000/01/06 01:20:31  peter
    * moved out of packages/ back to topdir

  Revision 1.1  1999/12/23 19:36:47  peter
    * place unitfiles in target dirs

  Revision 1.1  1999/11/24 23:36:37  peter
    * moved to packages dir

  Revision 1.4  1999/05/17 13:55:18  pierre
   * FPC win32 also need dos unit

  Revision 1.3  1999/04/13 09:25:47  daniel
  * Reverted a terrible mistake

  Revision 1.1  1998/12/04 12:48:24  peter
    * moved some dirs

  Revision 1.5  1998/10/26 11:22:50  peter
    * updates


  ?            0.1      marco   Initial implementation
  ?                             Several fixes ...
  08/29/1997   0.4      marco   Some platform adjustments
  09/16/1997   0.4.1    marco   Added "EqualNames"
  09/17/1997   0.5      michael Implemented linux part.
  09/20/1997   0.5.1    marco   Added LastAccessed/Created to Linux part of
                                file descriptor
  04/15/1998   0.5.2    michael Updated linux part.
}