{
    $Id: dos.pp,v 1.6 2003/03/25 18:17:54 armin Exp $
    This file is part of the Free Pascal run time library.
    Copyright (c) 1999-2000 by the Free Pascal development team.

    Dos unit for BP7 compatible RTL (novell netware)

    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.

 **********************************************************************}

{ 2000/09/03 armin: first version
  2001/04/08 armin: implemented more functions
                      OK: Implemented and tested
                      NI: not implemented
  2001/04/15 armin: FindFirst bug corrected, FExpand and FSearch tested, GetCBreak, SetCBreak
                    implemented
}

unit dos;
interface

Const
  {Bitmasks for CPU Flags}
  fcarry     = $0001;
  fparity    = $0004;
  fauxiliary = $0010;
  fzero      = $0040;
  fsign      = $0080;
  foverflow  = $0800;

  {Bitmasks for file attribute}
  readonly  = $01;
  hidden    = $02;
  sysfile   = $04;
  volumeid  = $08;
  nwexeconly= $08;
  directory = $10;
  archive   = $20;
  sharable  = $80;
  anyfile   = $3F;

  {File Status}
  fmclosed = $D7B0;
  fminput  = $D7B1;
  fmoutput = $D7B2;
  fminout  = $D7B3;


Type
{ Needed for LFN Support }
  ComStr  = String[255];
  PathStr = String[255];
  DirStr  = String[255];
  NameStr = String[255];
  ExtStr  = String[255];

{
  filerec.inc contains the definition of the filerec.
  textrec.inc contains the definition of the textrec.
  It is in a separate file to make it available in other units without
  having to use the DOS unit for it.
}
{$i filerec.inc}
{$i textrec.inc}

  DateTime = packed record
    Year,
    Month,
    Day,
    Hour,
    Min,
    Sec   : word;
  End;

  searchrec = packed record
     DirP  : POINTER;              { used for opendir }
     EntryP: POINTER;              { and readdir }
     Magic : WORD;
     fill  : array[1..11] of byte;
     attr  : byte;
     time  : longint;
     { reserved : word; not in DJGPP V2 }
     size  : longint;
     name  : string[255]; { NW uses only [12] but more can't hurt }
   end;

  registers = packed record
    case i : integer of
     0 : (ax,f1,bx,f2,cx,f3,dx,f4,bp,f5,si,f51,di,f6,ds,f7,es,f8,flags,fs,gs : word);
     1 : (al,ah,f9,f10,bl,bh,f11,f12,cl,ch,f13,f14,dl,dh : byte);
     2 : (eax,  ebx,  ecx,  edx,  ebp,  esi,  edi : longint);
    end;


Var
  DosError : integer;



{Info/Date/Time}
Function  DosVersion: Word;                                  {ok}
Procedure GetDate(var year, month, mday, wday: word);        {ok}
Procedure GetTime(var hour, minute, second, sec100: word);   {ok}
procedure SetDate(year,month,day: word);                     {ok}
Procedure SetTime(hour,minute,second,sec100: word);          {ok}
Procedure UnpackTime(p: longint; var t: datetime);           {ok}
Procedure PackTime(var t: datetime; var p: longint);         {ok}

{Exec}
Procedure Exec(const path: pathstr; const comline: comstr);  {ni}
Function  DosExitCode: word;                                 {ni}

{Disk}
{$ifdef Int64}
 Function  DiskFree(drive: byte) : int64;                    {ok}
 Function  DiskSize(drive: byte) : int64;                    {ok}
{$else}
 Function  DiskFree(drive: byte) : longint;                  {ok}
 Function  DiskSize(drive: byte) : longint;                  {ok}
{$endif}

{FincClose has to be called to avoid memory leaks}
Procedure FindFirst(const path: pathstr; attr: word;         {ok}
                    var f: searchRec);
Procedure FindNext(var f: searchRec);                        {ok}
Procedure FindClose(Var f: SearchRec);                       {ok}

{File}
Procedure GetFAttr(var f; var attr: word);                   {ok}
Procedure GetFTime(var f; var time: longint);                {ok}
Function  FSearch(path: pathstr; dirlist: string): pathstr;  {ok}
Function  FExpand(const path: pathstr): pathstr;             {ok}
Procedure FSplit(path: pathstr; var dir: dirstr; var name:   {untested}
                 namestr; var ext: extstr);

{Environment}
Function  EnvCount: longint;                                 {ni}
Function  EnvStr(index: integer): string;                    {ni}
Function  GetEnv(envvar: string): string;                    {ok}

{Misc}
Procedure SetFAttr(var f; attr: word);                       {ni}
Procedure SetFTime(var f; time: longint);                    {ni}
Procedure GetCBreak(var breakvalue: boolean);                {ni}
Procedure SetCBreak(breakvalue: boolean);                    {ni}
Procedure GetVerify(var verify: boolean);                    {ni}
Procedure SetVerify(verify: boolean);                        {ni}

{Do Nothing Functions}
Procedure SwapVectors;                                       {ni}
Procedure GetIntVec(intno: byte; var vector: pointer);       {ni}
Procedure SetIntVec(intno: byte; vector: pointer);           {ni}
Procedure Keep(exitcode: word);                              {ni}

Procedure Intr(intno: byte; var regs: registers);            {ni}
Procedure MSDos(var regs: registers);                        {ni}


implementation

uses
  strings;

{$ASMMODE ATT}
{$I nwsys.inc }

{*****************************************************************************
                        --- Info / Date / Time ---
******************************************************************************}
{$PACKRECORDS 4}


function dosversion : word;
VAR F : FILE_SERV_INFO;
begin
  IF GetServerInformation(SIZEOF(F),@F) = 0 THEN
    dosversion := WORD (F.netwareVersion) SHL 8 + F.netwareSubVersion;
end;


procedure getdate(var year,month,mday,wday : word);
VAR N : NWdateAndTime;
begin
  GetFileServerDateAndTime (N);
  wday:=N.DayOfWeek;
  year:=1900 + N.Year;
  month:=N.Month;
  mday:=N.Day;
end;


procedure setdate(year,month,day : word);
VAR N : NWdateAndTime;
begin
  GetFileServerDateAndTime (N);
  SetFileServerDateAndTime(year,month,day,N.Hour,N.Minute,N.Second);
end;


procedure gettime(var hour,minute,second,sec100 : word);
VAR N : NWdateAndTime;
begin
  GetFileServerDateAndTime (N);
  hour := N.Hour;
  Minute:= N.Minute;
  Second := N.Second;
  sec100 := 0;
end;


procedure settime(hour,minute,second,sec100 : word);
VAR N : NWdateAndTime;
begin
  GetFileServerDateAndTime (N);
  SetFileServerDateAndTime(N.year,N.month,N.day,hour,minute,second);
end;


Procedure packtime(var t : datetime;var p : longint);
Begin
  p:=(t.sec shr 1)+(t.min shl 5)+(t.hour shl 11)+(t.day shl 16)+(t.month shl 21)+((t.year-1980) shl 25);
End;


Procedure unpacktime(p : longint;var t : datetime);
Begin
  with t do
   begin
     sec:=(p and 31) shl 1;
     min:=(p shr 5) and 63;
     hour:=(p shr 11) and 31;
     day:=(p shr 16) and 31;
     month:=(p shr 21) and 15;
     year:=(p shr 25)+1980;
   end;
End;


{******************************************************************************
                               --- Exec ---
******************************************************************************}

var
  lastdosexitcode : word;

procedure exec(const path : pathstr;const comline : comstr);
begin
  ConsolePrintf ('warning: fpc dos.exec not implemented'#13#10,0);
end;


function dosexitcode : word;
begin
  dosexitcode:=lastdosexitcode;
end;


procedure getcbreak(var breakvalue : boolean);
begin
  breakvalue := _SetCtrlCharCheckMode (false);  { get current setting }
  if breakvalue then
    _SetCtrlCharCheckMode (breakvalue);         { and restore old setting }
end;


procedure setcbreak(breakvalue : boolean);
begin
  _SetCtrlCharCheckMode (breakvalue);
end;


procedure getverify(var verify : boolean);
begin
  verify := true;
end;


procedure setverify(verify : boolean);
begin
end;


{******************************************************************************
                               --- Disk ---
******************************************************************************}

function getvolnum (drive : byte) : longint;
var dir : STRING[255];
    P,PS,
    V   : LONGINT;
begin
  if drive = 0 then
  begin  // get volume name from current directory (i.e. SERVER-NAME/VOL2:TEST)
    getdir (0,dir);
    p := pos (':', dir);
    if p = 0 then
    begin
      getvolnum := -1;
      exit;
    end;
    byte (dir[0]) := p-1;
    dir[p] := #0;
    PS := pos ('/', dir);
    INC (PS);
    if _GetVolumeNumber (@dir[PS], V) <> 0 then
      getvolnum := -1
    else
      getvolnum := V;
  end else
    getvolnum := drive-1;
end;

{$ifdef Int64}

function diskfree(drive : byte) : int64;
VAR Buf                 : ARRAY [0..255] OF CHAR;
    TotalBlocks         : WORD;
    SectorsPerBlock     : WORD;
    availableBlocks     : WORD;
    totalDirectorySlots : WORD;
    availableDirSlots   : WORD;
    volumeisRemovable   : WORD;
    volumeNumber        : LONGINT;
begin
  volumeNumber := getvolnum (drive);
  if volumeNumber >= 0 then
  begin
    {i think thats not the right function but for others i need a connection handle}
    if _GetVolumeInfoWithNumber (volumeNumber,@Buf,
                                 TotalBlocks,
                                 SectorsPerBlock,
                                 availableBlocks,
                                 totalDirectorySlots,
                                 availableDirSlots,
                                 volumeisRemovable) = 0 THEN
    begin
      diskfree := int64 (availableBlocks) * int64 (SectorsPerBlock) * 512;
    end else
      diskfree := 0;
  end else
    diskfree := 0;
end;


function disksize(drive : byte) : int64;
VAR Buf                 : ARRAY [0..255] OF CHAR;
    TotalBlocks         : WORD;
    SectorsPerBlock     : WORD;
    availableBlocks     : WORD;
    totalDirectorySlots : WORD;
    availableDirSlots   : WORD;
    volumeisRemovable   : WORD;
    volumeNumber        : LONGINT;
begin
  volumeNumber := getvolnum (drive);
  if volumeNumber >= 0 then
  begin
    {i think thats not the right function but for others i need a connection handle}
    if _GetVolumeInfoWithNumber (volumeNumber,@Buf,
                                 TotalBlocks,
                                 SectorsPerBlock,
                                 availableBlocks,
                                 totalDirectorySlots,
                                 availableDirSlots,
                                 volumeisRemovable) = 0 THEN
    begin
      disksize := int64 (TotalBlocks) * int64 (SectorsPerBlock) * 512;
    end else
      disksize := 0;
  end else
    disksize := 0;
end;
{$else}

function diskfree(drive : byte) : longint;
VAR Buf                 : ARRAY [0..255] OF CHAR;
    TotalBlocks         : WORD;
    SectorsPerBlock     : WORD;
    availableBlocks     : WORD;
    totalDirectorySlots : WORD;
    availableDirSlots   : WORD;
    volumeisRemovable   : WORD;
    volumeNumber        : LONGINT;
begin
  volumeNumber := getvolnum (drive);
  if (volumeNumber >= 0) and (volumeNumber <= 255) then
  begin
    {i think thats not the right function but for others i need a connection handle}
    if _GetVolumeInfoWithNumber (byte(volumeNumber),@Buf,
                                 TotalBlocks,
                                 SectorsPerBlock,
                                 availableBlocks,
                                 totalDirectorySlots,
                                 availableDirSlots,
                                 volumeisRemovable) = 0 THEN
    begin
      diskfree := availableBlocks * SectorsPerBlock * 512;
    end else
      diskfree := 0;
  end else
    diskfree := 0;
end;


function disksize(drive : byte) : longint;
VAR Buf                 : ARRAY [0..255] OF CHAR;
    TotalBlocks         : WORD;
    SectorsPerBlock     : WORD;
    availableBlocks     : WORD;
    totalDirectorySlots : WORD;
    availableDirSlots   : WORD;
    volumeisRemovable   : WORD;
    volumeNumber        : LONGINT;
begin
  volumeNumber := getvolnum (drive);
  if (volumeNumber >= 0) and (volumeNumber <= 255) then
  begin
    {i think thats not the right function but for others i need a connection handle}
    if _GetVolumeInfoWithNumber (byte(volumeNumber),@Buf,
                                 TotalBlocks,
                                 SectorsPerBlock,
                                 availableBlocks,
                                 totalDirectorySlots,
                                 availableDirSlots,
                                 volumeisRemovable) = 0 THEN
    begin
      disksize := TotalBlocks * SectorsPerBlock * 512;
    end else
      disksize := 0;
  end else
    disksize := 0;
end;

{$endif}


{******************************************************************************
                     --- Findfirst FindNext ---
******************************************************************************}


PROCEDURE find_setfields (VAR f : searchRec);
BEGIN
  WITH F DO
  BEGIN
    IF Magic = $AD01 THEN
    BEGIN
      attr := WORD (PNWDirEnt(EntryP)^.d_attr);  // lowest 16 bit -> same as dos
      time := PNWDirEnt(EntryP)^.d_time + (LONGINT (PNWDirEnt(EntryP)^.d_date) SHL 16);
      size := PNWDirEnt(EntryP)^.d_size;
      name := strpas (PNWDirEnt(EntryP)^.d_nameDOS);
      doserror := 0;
    END ELSE
    BEGIN
      FillChar (f,SIZEOF(f),0);
      doserror := 18;
    END;
  END;
END;


procedure findfirst(const path : pathstr;attr : word;var f : searchRec);
var
  path0 : array[0..256] of char;
begin
  IF path = '' then
  begin
    doserror := 18;
    exit;
  end;
  strpcopy(path0,path);
  PNWDirEnt(f.DirP) := _opendir (path0);
  IF f.DirP = NIL THEN
    doserror := 18
  ELSE
  BEGIN
    IF attr <> anyfile THEN
      _SetReaddirAttribute (PNWDirEnt(f.DirP), attr);
    F.Magic := $AD01;
    PNWDirEnt(f.EntryP) := _readdir (PNWDirEnt(f.DirP));
    IF F.EntryP = NIL THEN
    BEGIN
      _closedir (PNWDirEnt(f.DirP));
      f.Magic := 0;
      doserror := 18;
    END ELSE
      find_setfields (f);
  END;
end;


procedure findnext(var f : searchRec);
begin
  IF F.Magic <> $AD01 THEN
  BEGIN
    doserror := 18;
    EXIT;
  END;
  doserror:=0;
  PNWDirEnt(f.EntryP) := _readdir (PNWDirEnt(f.DirP));
  IF F.EntryP = NIL THEN
    doserror := 18
  ELSE
    find_setfields (f);
end;


Procedure FindClose(Var f: SearchRec);
begin
  IF F.Magic <> $AD01 THEN
  BEGIN
    doserror := 18;
    EXIT;
  END;
  doserror:=0;
  _closedir (PNWDirEnt(f.DirP));
  f.Magic := 0;
  f.DirP := NIL;
  f.EntryP := NIL;
end;


procedure swapvectors;
begin
end;


{******************************************************************************
                               --- File ---
******************************************************************************}

procedure fsplit(path : pathstr;var dir : dirstr;var name : namestr;var ext : extstr);
var
   dotpos,p1,i : longint;
begin
  { allow slash as backslash }
  for i:=1 to length(path) do
   if path[i]='/' then path[i]:='\';
  { get drive name }
  p1:=pos(':',path);
  if p1>0 then
    begin
       dir:=path[1]+':';
       delete(path,1,p1);
    end
  else
    dir:='';
  { split the path and the name, there are no more path informtions }
  { if path contains no backslashes                                 }
  while true do
    begin
       p1:=pos('\',path);
       if p1=0 then
         break;
       dir:=dir+copy(path,1,p1);
       delete(path,1,p1);
    end;
  { try to find out a extension }
  if LFNSupport then
    begin
       Ext:='';
       i:=Length(Path);
       DotPos:=256;
       While (i>0) Do
         Begin
            If (Path[i]='.') Then
              begin
                 DotPos:=i;
                 break;
              end;
            Dec(i);
         end;
       Ext:=Copy(Path,DotPos,255);
       Name:=Copy(Path,1,DotPos - 1);
    end
  else
    begin
       p1:=pos('.',path);
       if p1>0 then
         begin
            ext:=copy(path,p1,4);
            delete(path,p1,length(path)-p1+1);
         end
       else
         ext:='';
       name:=path;
    end;
end;


function fexpand(const path : pathstr) : pathstr;
var
  s,pa : pathstr;
  i,j  : longint;
begin
  getdir(0,s);
  i:=ioresult;
  if LFNSupport then
   begin
     pa:=path;
   end
  else
   if FileNameCaseSensitive then
    pa:=path
   else
    pa:=upcase(path);

  { allow slash as backslash }
  for i:=1 to length(pa) do
   if pa[i]='/' then
    pa[i]:='\';

  if (length(pa)>1) and (pa[2]=':') and (pa[1] in ['A'..'Z','a'..'z']) then
    begin
       { Always uppercase driveletter }
       if (pa[1] in ['a'..'z']) then
        pa[1]:=Chr(Ord(Pa[1])-32);
       { we must get the right directory }
       getdir(ord(pa[1])-ord('A')+1,s);
       i:=ioresult;
       if (ord(pa[0])>2) and (pa[3]<>'\') then
         if pa[1]=s[1] then
           begin
             { remove ending slash if it already exists }
             if s[length(s)]='\' then
              dec(s[0]);
             pa:=s+'\'+copy (pa,3,length(pa));
           end
         else
           pa:=pa[1]+':\'+copy (pa,3,length(pa))
    end
  else
    if pa[1]='\' then
      begin
        { Do not touch Network drive names if LFNSupport is true }
        if not ((Length(pa)>1) and (pa[2]='\') and LFNSupport) then
          pa:=s[1]+':'+pa;
      end
    else if s[0]=#3 then
      pa:=s+pa
    else
      pa:=s+'\'+pa;

{ Turbo Pascal gives current dir on drive if only drive given as parameter! }
if length(pa) = 2 then
 begin
   getdir(byte(pa[1])-64,s);
   pa := s;
 end;

{First remove all references to '\.\'}
  while pos ('\.\',pa)<>0 do
   delete (pa,pos('\.\',pa),2);
{Now remove also all references to '\..\' + of course previous dirs..}
  repeat
    i:=pos('\..\',pa);
    if i<>0 then
     begin
       j:=i-1;
       while (j>1) and (pa[j]<>'\') do
        dec (j);
       if pa[j+1] = ':' then j := 3;
       delete (pa,j,i-j+3);
     end;
  until i=0;

  { Turbo Pascal gets rid of a \.. at the end of the path }
  { Now remove also any reference to '\..'  at end of line
    + of course previous dir.. }
  i:=pos('\..',pa);
  if i<>0 then
   begin
     if i = length(pa) - 2 then
      begin
        j:=i-1;
        while (j>1) and (pa[j]<>'\') do
         dec (j);
        delete (pa,j,i-j+3);
      end;
      pa := pa + '\';
    end;
  { Remove End . and \}
  if (length(pa)>0) and (pa[length(pa)]='.') then
   dec(byte(pa[0]));
  { if only the drive + a '\' is left then the '\' should be left to prevtn the program
    accessing the current directory on the drive rather than the root!}
  { if the last char of path = '\' then leave it in as this is what TP does! }
  if ((length(pa)>3) and (pa[length(pa)]='\')) and (path[length(path)] <> '\') then
   dec(byte(pa[0]));
  { if only a drive is given in path then there should be a '\' at the
    end of the string given back }
  if length(pa) = 2 then pa := pa + '\';
  fexpand:=pa;
end;


Function FSearch(path: pathstr; dirlist: string): pathstr;
var
  i,p1   : longint;
  s      : searchrec;
  newdir : pathstr;
begin
{ check if the file specified exists }
  findfirst(path,anyfile,s);
  if doserror=0 then
   begin
     findclose(s);
     fsearch:=path;
     exit;
   end;
{ No wildcards allowed in these things }
  if (pos('?',path)<>0) or (pos('*',path)<>0) then
    fsearch:=''
  else
    begin
       { allow slash as backslash }
       for i:=1 to length(dirlist) do
         if dirlist[i]='/' then dirlist[i]:='\';
       repeat
         p1:=pos(';',dirlist);
         if p1<>0 then
          begin
            newdir:=copy(dirlist,1,p1-1);
            delete(dirlist,1,p1);
          end
         else
          begin
            newdir:=dirlist;
            dirlist:='';
          end;
         if (newdir<>'') and (not (newdir[length(newdir)] in ['\',':'])) then
          newdir:=newdir+'\';
         findfirst(newdir+path,anyfile,s);
         if doserror=0 then
          newdir:=newdir+path
         else
          newdir:='';
       until (dirlist='') or (newdir<>'');
       fsearch:=newdir;
    end;
  findclose(s);
end;


{******************************************************************************
                       --- Get/Set File Time,Attr ---
******************************************************************************}

procedure getftime(var f;var time : longint);
VAR StatBuf : NWStatBufT;
    T       : DateTime;
    DosDate,
    DosTime : WORD;
begin
  IF _fstat (FileRec (f).Handle, StatBuf) = 0 THEN
  BEGIN
    _ConvertTimeToDos (StatBuf.st_mtime, DosDate, DosTime);
    time := DosTime + (LONGINT (DosDate) SHL 16);
  END ELSE
    time := 0;
end;


procedure setftime(var f;time : longint);
begin
  {is there a netware function to do that ?????}
  ConsolePrintf ('warning: fpc dos.setftime not implemented'#13#10,0);
end;


procedure getfattr(var f;var attr : word);
VAR StatBuf : NWStatBufT;
begin
  IF _fstat (FileRec (f).Handle, StatBuf) = 0 THEN
  BEGIN
    attr := word (StatBuf.st_attr);
  END ELSE
    attr := 0;
end;


procedure setfattr(var f;attr : word);
begin
  {is there a netware function to do that ?????}
  ConsolePrintf ('warning: fpc dos.setfattr not implemented'#13#10,0);
end;


{******************************************************************************
                             --- Environment ---
******************************************************************************}

function envcount : longint;
begin
  envcount := 0;  {is there a netware function to do that ?????}
  ConsolePrintf ('warning: fpc dos.envcount not implemented'#13#10,0);
end;


function envstr(index : integer) : string;
begin
  envstr := '';   {is there a netware function to do that ?????}
  ConsolePrintf ('warning: fpc dos.envstr not implemented'#13#10,0);
end;

{ the function exists in clib but i dont know how to set environment vars.
  may be it's only a dummy in clib }
Function  GetEnv(envvar: string): string;
var
  envvar0 : array[0..256] of char;
  p       : pchar;
begin
  strpcopy(envvar0,envvar);
  p := _getenv (envvar0);
  if p = NIL then
    GetEnv := ''
  else
    GetEnv := strpas (p);
end;


{******************************************************************************
                             --- Not Supported ---
******************************************************************************}

Procedure keep(exitcode : word);
Begin
 { no netware equivalent }
End;

Procedure getintvec(intno : byte;var vector : pointer);
Begin
 { no netware equivalent }
End;

Procedure setintvec(intno : byte;vector : pointer);
Begin
 { no netware equivalent }
End;

procedure intr(intno : byte;var regs : registers);
begin
 { no netware equivalent }
end;

procedure msdos(var regs : registers);
begin
 { no netware equivalent }
end;


end.
{
  $Log: dos.pp,v $
  Revision 1.6  2003/03/25 18:17:54  armin
  * support for fcl, support for linking without debug info
  * renamed winsock2 to winsock for win32 compatinility
  * new sockets unit for netware
  * changes for compiler warnings

  Revision 1.5  2002/09/07 16:01:20  peter
    * old logs removed and tabs fixed

}
