home *** CD-ROM | disk | FTP | other *** search
- (* ----------------------------------------------------------------------- *)
- (* DIRCP.TUR *)
- (* (c) 1987 Michael Ceol & PASCAL INT. *)
- (* System- und Compiler-spezifischer Teil der DIRECTORY-Bibliothek fuer *)
- (* CP/M 2.x & 3.x in Turbo Pascal *)
- (* programmiert und getestet auf einem Schneider Joyce CP/M 3.0-System *)
- (* sowie einem Schneider CPC 6128 CP/M 2.2-System *)
- (* Der hier durch die CP/M-Versionsunterscheidung zur Laufzeit erzeugte *)
- (* Code kann verringert werden, in dem *)
- (* a) strickt an die gewuenschte CP/M-Version angepasst wird *)
- (* b) die CP/M 3.0-Features durch die entsprechenden Pascal-Loesungen *)
- (* ersetzt werden *)
- (* durch beide Massnahmen kann die Versionsunterscheidung mit den ent- *)
- (* sprechend auszufuehrenden Code-Alternativen entfallen. Hier wurde da- *)
- (* rauf verzichtet, um beide Varianten aufzuzeigen. *)
- (* ----------------------------------------------------------------------- *)
-
- (* CP/M 2.x verraet nicht die aktuelle DTA. Da diese aber beim Programm- *)
- (* start immer auf $80 gesetzt ist, koennen wir sie uns immer selbst mer- *)
- (* ken, wenn brav die Prozedur "FSetDTA" benutzt wird. "FGetDTA" liefert *)
- (* dann auch fuer CP/M 2.x immer die wirklich benutzte DTA! *)
-
- CONST CPM_2x_DTA : INTEGER = $80;
-
- (* ----------------------------------------------------------------------- *)
- (* CP/M Versionsnummer ermitteln: $20-$2F: CP/M 2.x, $30-$3F: CP/M 3.x *)
- FUNCTION DVersion: INTEGER;
- BEGIN DVersion := Lo(BDosHL($0C)) END;
- (* ----------------------------------------------------------------------- *)
- (* ein Byte aus dem System Control Block lesen (nur CP/M 3.0 !!): *)
- FUNCTION GetSCBByte (SCBOfs: BYTE): BYTE;
- VAR scbpb: RECORD ofs, com : BYTE; value : INTEGER; END;
- BEGIN
- scbpb.ofs := SCBOfs; scbpb.com := 0; GetSCBByte := BDos($31,Addr(scbpb));
- END;
-
- (* ein Word aus dem System Control Block lesen (nur CP/M 3.0 !!): *)
- FUNCTION GetSCBWord (SCBOfs: BYTE): INTEGER;
- VAR scbpb: RECORD ofs, com : BYTE; value : INTEGER; END;
- BEGIN
- scbpb.ofs := SCBOfs; scbpb.com := 0; GetSCBWord := BDosHL($31,Addr(scbpb));
- END;
-
- (* ein Byte in den System Control Block schreiben (nur CP/M 3.0 !!): *)
- PROCEDURE SetSCBByte (SCBOfs, newvalue: BYTE);
- VAR scbpb: RECORD ofs, com : BYTE; value : INTEGER; END;
- BEGIN
- scbpb.ofs := SCBOfs; scbpb.com := $FF;
- scbpb.value := newvalue; BDos($31,Addr(scbpb));
- END;
-
- (* ein Word in den System Control Block schreiben (nur CP/M 3.0 !!): *)
- PROCEDURE SetSCBWord (SCBOfs: BYTE; newvalue: INTEGER);
- VAR scbpb: RECORD ofs, com : BYTE; value : INTEGER; END;
- BEGIN
- scbpb.ofs := SCBOfs; scbpb.com := $FE;
- scbpb.value := newvalue; BDos($31,Addr(scbpb));
- END;
- (* ----------------------------------------------------------------------- *)
- (* aktuellen BDOS-Fehlermodus ermitteln (nur CP/M 3.0 !!): *)
- FUNCTION GetDosErrMode: INTEGER;
- CONST ErrMode = $4B;
- BEGIN GetDosErrMode := GetSCBByte(ErrMode); END;
-
- (* neuen BDOS-Fehlermodus setzen (nur CP/M 3.0 !!): *)
- PROCEDURE SetDosErrMode (ErrMode: INTEGER);
- BEGIN BDos($2D, ErrMode) END;
- (* ----------------------------------------------------------------------- *)
- PROCEDURE FSetDTA (DTA: DTA_Ptr); (* neue DTA-Adresse setzen *)
- BEGIN
- BDos($1A, Ord(DTA)); (* bei CP/M 2.x die DTA selbst merken: *)
- IF NOT (DVersion IN [$30..$3F]) THEN CPM_2x_DTA := Ord(DTA);
- END;
-
- FUNCTION FGetDTA: DTA_Ptr; (* aktuelle DTA-Adresse ermitteln *)
- CONST CurDTA = $3C;
- BEGIN
- IF DVersion IN [$30..$3F] THEN FGetDTA := Ptr(GetSCBWord(CurDTA))
- ELSE FGetDTA := Ptr(CPM_2x_DTA); (* CP/M 2.x ! *)
- END;
- (* ----------------------------------------------------------------------- *)
- FUNCTION DGetDrive: INTEGER; (* aktuelles Laufwerk ermitteln *)
- BEGIN DGetDrive := BDos($19) END;
-
- FUNCTION DSetDrive (drive: INTEGER): INTEGER; (* neues Laufwerk setzen *)
- VAR olderrmode, errcode: INTEGER;
- BEGIN
- DSetDrive := DOSedriv; (* Funktion erstmal fehlerhaft! *)
- IF drive IN [0..15] THEN BEGIN
- (* keinen Fehlerabbruch durch CP/M 3.0 zulassen: *)
- IF DVersion IN [$30..$3F] THEN BEGIN
- olderrmode := GetDosErrMode; SetDosErrMode($FF);
- END;
- errcode := BDos($0E, drive);
- (* wieder alten Fehlermodus von CP/M 3.0 herstellen: *)
- IF DVersion IN [$30..$3F] THEN SetDosErrMode(olderrmode);
- IF errcode = 0 THEN DSetDrive := DOSfnok; (* hat's geklappt ? *)
- END;
- END;
- (* ----------------------------------------------------------------------- *)
- FUNCTION DGetUser: INTEGER; (* aktuelle Benutzernummer ermitteln *)
- BEGIN DGetUser := BDos($20,$FF); END;
-
- PROCEDURE DSetUser (user: INTEGER); (* neue Benutzernummer setzen *)
- BEGIN BDos($20,user); END;
- (* ----------------------------------------------------------------------- *)
- (* aktuellen Pfad (MS-DOS-Simulation) = Benutzernummer ermitteln: *)
- FUNCTION DGetPath (VAR path: Dir_Chr0; dummydrive: INTEGER): INTEGER;
- VAR i: INTEGER; temp: STRING[3];
- BEGIN
- i := DGetUser; Str(i,temp);
- FOR i := 1 TO Length(temp) DO path[i] := temp[i];
- path[Length(temp)+1] := ':'; path[Length(temp)+2] := Chr(0);
- DGetPath := DOSfnok;
- END;
-
- (* Pfadspezifikation aus fspec extrahieren und in fpath zurueckgeben. Da- *)
- (* bei findet keine Ueberpruefung auf Korrektheit des Pfades statt!. next- *)
- (* ch zeigt auf das erste, dem Pfad folgende Zeichen des Dateinamens. *)
- PROCEDURE FGetPath (VAR fspec, fpath: Dir_Chr0; VAR nextch: INTEGER);
- VAR i:INTEGER;
- BEGIN
- nextch := 0;
- REPEAT nextch := Succ(nextch); fpath[nextch] := UpCase(fspec[nextch]);
- UNTIL (fspec[nextch] = ':') OR (fspec[nextch] = Chr(0));
- IF fspec[nextch] = ':' THEN nextch := Succ(nextch) ELSE nextch := 1;
- fpath[nextch] := Chr(0);
- END;
-
- (* neuen Pfad (MS-DOS-Simulation) = Benutzernummer setzen: *)
- FUNCTION DSetPath (VAR path: Dir_Chr0): INTEGER;
- VAR i, j, n, nextch, Usr: INTEGER; temp: Dir_Chr0;
- BEGIN
- DSetPath := DOSfnok;
- FGetPath(path, temp, nextch); (* Pfad ueberhaupt angegeben ? *)
- IF nextch > 1 THEN BEGIN (* ja... *)
- i := DOSfnok; n := 1; nextch := nextch-2;
- j := Ord(temp[1])-Ord('A'); (* Laufwerk angegeben ? *)
- IF j IN [0..15] THEN BEGIN i := DSetDrive(j); n := 2; END; (* ja! *)
- IF i = DOSfnok THEN BEGIN
- IF nextch >= n THEN BEGIN (* User-Nummer (Directory) angegeben ? *)
- j := 1; Usr := 0;
- FOR i := n TO nextch DO
- IF temp[i] IN ['0'..'9'] THEN BEGIN
- Usr := Usr * j + Ord(path[i])-Ord('0'); j := j*10;
- END
- ELSE DSetPath := DOSpthnf;
- DSetUser(Usr);
- END;
- END
- ELSE DSetPath := DOSpthnf
- END;
- END;
- (* ----------------------------------------------------------------------- *)
- (* Dateinamen wie z.B. "a:*.pas" untersuchen und in der Form "1????????PAS"*)
- (* im File Control Block "DirFCB" eintragen sowie weitere Vorbereitungen *)
- (* im "DirFCB" zur Suche treffen. Diese Funktion existiert erst ab CP/M *)
- (* 3.0. Fuer CP/M 2.x die in "FPARSCP2.PAS" angegebene alternative verwen- *)
- (* den! Die Funktion wurde an die Konventionen der MS-DOS-Funktion 29 an- *)
- (* gepasst: result=0 -> fname war ok, result=1 -> fname enthaelt "*" oder *)
- (* "?", result = 255 -> fname fehlerhaft (z.B. illegales Laufwerk). next- *)
- (* ch zeigt auf das erste, nicht mehr zum Dateiname gehoerende Zeichen. *)
- PROCEDURE FParsName (VAR fname: Dir_Chr0; VAR nextch, result: INTEGER);
- VAR pfcb: RECORD fname_adr, FCB_adr : INTEGER; END; temp: INTEGER;
- BEGIN
- pfcb.fname_adr := Addr(fname); pfcb.FCB_adr := Addr(DirFCB);
- temp := BDosHL($98,Addr(pfcb)); result := 0; nextch := 0;
- IF temp = $FFFF THEN result := 255
- ELSE IF temp <> 0 THEN nextch := Succ(temp-Addr(fname));
- FOR temp := 1 TO 11 DO IF DirFCB.name[temp] = '?' THEN result := 1;
- END;
- (* ----------------------------------------------------------------------- *)
- (* Die CP/M-Dateiattribute an die von MS-DOS/TOS anpassen: *)
- FUNCTION GetDEAttr(DTAInx: INTEGER): INTEGER;
- VAR attr: INTEGER;
- BEGIN
- attr := 0;
- IF DirDTA^[DTAInx].fname[9] > Chr(127) THEN attr := attr + DirRO;
- IF DirDTA^[DTAInx].fname[10] > Chr(127) THEN attr := attr + DirSys;
- IF DirDTA^[DTAInx].fname[11] < Chr(128) THEN attr := attr + DirBak;
- GetDEAttr := attr;
- END;
- (* ----------------------------------------------------------------------- *)
- (* ersten Directory-Eintrag nach MS-DOS-Manier suchen: *)
- FUNCTION FSFirst (VAR search: Dir_Chr0; attr: INTEGER): INTEGER;
- VAR i, j, nextch, voldrive, olddrive, oldusr: INTEGER;
- found: BOOLEAN; path: Dir_Chr0;
- BEGIN
- DirFCB.DirUsr := 0; DirFCB.DirAtr := attr;
- FGetPath(search, path, nextch); (* Pfad angegeben ? *)
- IF nextch = 1 THEN i := DGetPath(path,0) (* nein, also default *)
- ELSE BEGIN (* ja, aus 'search' entfernen! *)
- i := 0; nextch := Pred(nextch);
- IF path[1] IN ['A'..'P'] THEN BEGIN (* Laufwerk wird im FCB gebraucht *)
- i := 2; search[i] := ':';
- END;
- REPEAT
- nextch := Succ(nextch); i := Succ(i); search[i] := search[nextch];
- UNTIL search[nextch] = Chr(0);
- END;
- found := FALSE; FSFirst := DOSnmfil;
- oldusr := DGetUser; (* aktuelle User-Nummer *)
- olddrive := DGetDrive; (* und Laufwerk merken *)
- IF DSetPath(path) = DOSfnok THEN BEGIN (* Laufw. u. User fuer Dir setzen *)
- FParsName(search,i,j);
- IF j <> 255 THEN BEGIN (* gueltiger Dateiname ? *)
- DirFCB.DirUsr := DGetUser; (* User-Nummer und Laufwerk fuer FSNext *)
- DirFCB.DirDrv := DGetDrive; (* im erweiterten FCB uebergeben. *)
- IF attr = DirVol THEN BEGIN (* Directory-Label(Volume) gewuenscht ? *)
- DirFCB.drive := Ord('?'); (* ja, alle Eintraege nach Directory- *)
- (* Label durchsuchen.... *)
- i := BDos($11,Addr(DirFCB)); (* ersten Eintrag suchen *)
- WHILE (i <> 255) AND NOT found DO
- IF DirDTA^[i].dircod = 32 THEN BEGIN (* gefunden? Ja! *)
- DirFCB.DTAInx := i; found := TRUE; FSFirst := DOSfnok;
- END
- ELSE i := BDos($12); (* nein, naechsten Eintrag. *)
- END
- ELSE BEGIN (* normale Suche nach Dateien: *)
- i := BDos($11,Addr(DirFCB)); (* ersten Eintrag suchen *)
- REPEAT
- IF i <> 255 THEN BEGIN
- j := GetDEAttr(i) AND 223; (* besondere Attribute ? *)
- IF j > 0 THEN IF (j AND DirFCB.DirAtr) > 0 THEN j := 0;
- IF j = 0 THEN BEGIN (* Eintrag stimmt mit Suchspez. ueberein *)
- DirFCB.DTAInx := i; FSFirst := DOSfnok; found := TRUE;
- END
- ELSE i := BDos($12) (* sonst mit dem Naechsten weiter machen *)
- END;
- UNTIL (i = 255) OR found; (* bis gefunden oder kein Eintrag mehr. *)
- END;
- END;
- DSetUser(oldusr); i := DSetDrive(olddrive);
- END;
- END;
-
- (* naechsten Eintrag nach MS-DOS-Manier suchen: *)
- FUNCTION FSNext: INTEGER;
- VAR i,j,oldusr,olddrive: INTEGER; found: BOOLEAN;
- BEGIN
- IF DirFCB.DirAtr <> DirVol THEN BEGIN (* DirVol ist eine exklusive Suche *)
- oldusr := DGetUser; DSetUser(DirFCB.DirUsr);
- olddrive := DGetDrive; i := DSetDrive(DirFCB.DirDrv);
- FSNext := DOSfilnf; found := FALSE;
- REPEAT
- i := BDos($12);
- IF i <> 255 THEN BEGIN (* s. FSFirst *)
- j := GetDEAttr(i) AND 223;
- IF j > 0 THEN IF (j AND DirFCB.DirAtr) > 0 THEN j := 0;
- IF j = 0 THEN BEGIN
- DirFCB.DTAInx := i; FSNext := DOSfnok; found := TRUE;
- END;
- END;
- UNTIL (i = 255) OR found;
- DSetUser(oldusr); i := DSetDrive(olddrive);
- END;
- END;
- (* ----------------------------------------------------------------------- *)
- (* ----------------------------------------------------------------------- *)
- (* zwei Bit-Funktionen, die Compiler-abhaengig sind: *)
- (* Die Bits von "value" um "n" Stellen nach rechts shiften (verschieben): *)
- FUNCTION ShiftR (value, n: INTEGER): INTEGER;
- BEGIN ShiftR := value SHR n END;
- (* Die Bits von "val1" und "val2" undieren: *)
- FUNCTION AndInt (val1, val2: INTEGER): INTEGER;
- BEGIN AndInt := val1 AND val2 END;
- (* ----------------------------------------------------------------------- *)
- (* nochmal zwei eventuell anzupassende Routinen: *)
- (* Integer-Wert zu einer 'n'-stelligen Zeichenkette mit fuehrenden Nullen: *)
- PROCEDURE IntStr (value, n: INTEGER; VAR s: Dir_Str);
- VAR i : INTEGER; Ch: CHAR;
- BEGIN
- s := '';
- FOR i := 1 TO n DO BEGIN
- s := Concat(Chr((value MOD 10)+Ord('0')),s); value := value DIV 10;
- END;
- END;
- (* wg. negativen Integer-Werten bei grossen Dateigroessen selbigen Wert zu *)
- (* einem positiven Real-Wert konvertieren: *)
- FUNCTION IntCard (i: INTEGER): REAL;
- BEGIN IF i < 0 THEN IntCard := 65536.0 + i ELSE IntCard := i; END;
- (* ----------------------------------------------------------------------- *)
- (* ----------------------------------------------------------------------- *)
- (* Konvertierung eines CP/M 3.x Datums. Das CP/M-Datum ist die Anzahl von *)
- (* seit dem 1.1.1978 vergangenen Tagen (etwas seltsam, oder nicht ?): *)
- PROCEDURE DOSDateStr (DOSDate: INTEGER; VAR Date: Date_Str);
- CONST monate: ARRAY[0..12] OF INTEGER =
- (0,31,59,90,120,151,181,212,243,273,304,334,365);
- VAR j, schalt, lfdtag, tag, monat, jahr: INTEGER; temp: Dir_Str;
- BEGIN
- j := (DOSDate+364) MOD 1461; schalt := 0; monat := 0;
- IF j > 1095 THEN schalt := 1;
- jahr := ((DOSDate+364) DIV 1461) * 4 + (j-schalt) DIV 365 + 1977;
- IF j = 1460 THEN BEGIN tag := 31; monat := 12; END
- ELSE BEGIN
- lfdtag := j MOD 365;
- IF (schalt = 1) AND (lfdtag = 59) THEN
- BEGIN tag := 29; monat := 2; END
- ELSE BEGIN
- REPEAT
- IF (schalt = 1) AND (monat > 1) THEN j := 1 ELSE j := 0;
- monat := Succ(monat);
- UNTIL (lfdtag < monate[monat+j]) OR (monat = 12);
- tag := lfdtag - monate[Pred(monat)] - j + 1;
- END;
- END;
- IntStr(jahr,4,temp); Date := temp;
- IntStr(monat,2,temp); Date := Concat(Date,temp);
- IntStr(tag,2,temp); Date := Concat(Date,temp);
- END;
-
- (* Konvertierung der CP/M 3.0-Zeit. Stunde und Minute sind in DOSTime BCD- *)
- (* codiert: *)
- PROCEDURE DOSTimeStr (DOSTime: INTEGER; VAR Time: Time_Str);
- (* Byte mit zwei BCD-Ziffern zu einen 2-Zeichen-String konvertieren: *)
- FUNCTION BCDtoStr (i: INTEGER): Time_Str;
- VAR j: INTEGER;
- BEGIN
- j := i SHR 4 + Ord('0'); i := i AND 15 + Ord('0');
- BCDtoStr := Concat(Chr(j),Chr(i));
- END;
-
- BEGIN (* DOSTimeStr *)
- Time := BCDtoStr(DOSTime MOD 256); (* Stunden *)
- Time := Concat(Time,BCDtoStr(DOSTime DIV 256)); (* Minuten *)
- Time := Concat(Time,'00'); (* keine Sekunden *)
- END;
- (* ----------------------------------------------------------------------- *)
- (* BS-Funktion zur Ermittlung der Dateigroesse mit initial. FCB aufrufen: *)
- (* CP/M gibt die Anzahl von 128-Byte-Records in 'ranrec' des FCBs zurueck. *)
- FUNCTION CompFSize: REAL;
- BEGIN
- WITH DirFCB DO BEGIN
- BDos($23, Addr(DirFCB));
- CompFSize := (ranrec[2]*65536.0+ranrec[1]*256.0+ranrec[0])*128.0;
- END;
- END;
-
- (* Da CP/M Dateigroessen nicht mit den Dir-Suchfunktionen liefert, wird *)
- (* diese fuer ein gelesenes Directory hier mittels der entspr. BS-Funktion *)
- (* ermittelt. Dies kann nicht waehrend des Lesens des Directorys geschehen,*)
- (* da zwischen FSFirst und FSNext keine anderen Directory-bezogenen Funk- *)
- (* tionen ausgefuehrt werden duerfen! Diese Vorgehensweise benoetigt aller-*)
- (* dings entspr. Zeit. Wenn auf die Groesse verzichtet werden kann, ist *)
- (* der Aufruf dieser Prozedur in "Dir" (s. DIRLIB.PAS) zu entfernen! *)
- PROCEDURE FDirSize;
- VAR i, j, oldusr, olddrive: INTEGER;
- BEGIN
- oldusr := DGetUser; DSetUser(DirFCB.DirUsr);
- olddrive := DGetDrive; i := DSetDrive(DirFCB.DirDrv);
- FOR i := 1 TO Directory.num DO
- WITH Directory.items[i] DO BEGIN
- FOR j := 1 TO 8 DO DirFCB.name[j] := name[j];
- FOR j := 1 TO 3 DO DirFCB.name[j+8] := ext[j];
- DirFCB.drive := 0; size := CompFSize;
- END;
- DSetUser(oldusr); i := DSetDrive(olddrive);
- END;
- (* ----------------------------------------------------------------------- *)
- (* DIRCP.TUR *)