fpc/rtl/amiga/m68k/legacydos.inc
Michael VAN CANNEYT 710d6eb5c5 * Char -> AnsiChar
2023-07-14 17:26:09 +02:00

541 lines
15 KiB
PHP

{
This file is part of the Free Pascal run time library.
Copyright (c) 2020 Karoly Balogh, Free Pascal Development team
Amiga dos.library legacy (OS 1.x/2.x) support functions
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.
**********************************************************************}
{
This unit implements some missing functions of OS 1.x (and some OS 2.x)
dos.library, so the legacy OS support can be implemented with minimal
changes to the normal system unit and common Amiga-like code
Please note that this code doesn't aim to be API feature complete, just
functional enough for the RTL code.
}
procedure NextTag(var Tag: PTagItem); inline;
begin
if Tag^.ti_Tag = TAG_END then
Exit;
Inc(Tag);
repeat
case Tag^.ti_Tag of
TAG_IGNORE: Inc(Tag);
TAG_SKIP: Inc(Tag, Tag^.ti_Data);
TAG_MORE: Tag := PTagItem(Tag^.ti_Data);
else
Break;
end;
until False;
end;
{$PACKRECORDS 2}
type
TAmigaLegacyFakeSegList = record
length: DWord;
next: DWord;
jump: Word;
entry: Pointer;
pad: Word;
end;
{$PACKRECORDS DEFAULT}
var
__amiga_fake_seglist: TAmigaLegacyFakeSegList;
__amiga_fake_seglist_lock: TSignalSemaphore;
__amiga_fake_seglist_lock_inited: boolean = false;
function CreateNewProc(tags: PTagItem): PProcess; public name '_fpc_amiga_createproc';
var
seglistbptr: dword;
name: PAnsiChar;
entryfunc: pointer;
stacksize: dword;
m: pmsgport;
tag: ptagitem;
begin
CreateNewProc:=nil;
entryfunc:=nil;
stacksize:=4000;
name:='New Process';
tag := Tags;
if Assigned(tag) then
begin
repeat
case Tag^.ti_Tag of
NP_Entry: entryfunc := Pointer(Tag^.ti_Data);
NP_StackSize: stacksize := Tag^.ti_Data;
end;
NextTag(Tag);
until tag^.ti_Tag = TAG_END;
end;
if entryfunc = nil then
exit;
{ This is a gigantic hack, and probably only works, because AThreads will always
feed the same function pointer in here (i.e. starts the same function multiple
times, which is a wrapper for FPC threads), and also waits for the subprocess
to properly start before trying to start a new one, but just in case, lets
still have proper-ish locking here, in case one spawns a subthread from a
subthread... (KB) }
if not __amiga_fake_seglist_lock_inited then
begin
InitSemaphore(@__amiga_fake_seglist_lock);
__amiga_fake_seglist_lock_inited:=true;
end;
ObtainSemaphore(@__amiga_fake_seglist_lock);
with __amiga_fake_seglist do
begin
length:=16;
next:=0;
jump:=$4ef9; { JMP }
entry:=entryfunc;
pad:=$4e71; { NOP }
end;
seglistbptr:=ptruint(@__amiga_fake_seglist) shr 2;
m:=CreateProc(name, 0, seglistbptr, stacksize);
if m <> nil then
{ CreateProc returns the MsgPort inside the process structure.
recalculate to the address of the process instead... *yuck* (KB) }
CreateNewProc:=PProcess(pointer(m)-ptruint(@PProcess(nil)^.pr_MsgPort));
ReleaseSemaphore(@__amiga_fake_seglist_lock);
end;
function NameFromLock(lock : LongInt;
buffer: PAnsiChar;
len : LongInt): LongBool; public name '_fpc_amiga_namefromlock';
var
fib_area: array[1..sizeof(TFileInfoBlock) + sizeof(longint)] of byte;
fib: pfileinfoblock;
namelen: longint;
blen: longint;
begin
NameFromLock:=false;
if len <= 0 then
exit;
if (lock = 0) and (len >= 5) then
begin
buffer:='SYS:';
NameFromLock:=true;
exit;
end;
fib:=align(@fib_area[1],sizeof(longint));
buffer[0]:=#0;
dec(len); // always preserve one byte for zero term
blen:=0;
repeat
if Examine(lock,fib) <> 0 then
begin
namelen:=strlen(@fib^.fib_FileName[0]);
if (namelen+1) > (len-blen) then
exit;
move(buffer[0],buffer[namelen+1],blen);
move(fib^.fib_FileName[0],buffer[0],namelen);
lock:=ParentDir(lock);
if lock = 0 then
buffer[namelen]:=':'
else
buffer[namelen]:='/';
inc(blen,namelen+1);
buffer[blen]:=#0;
end
else
exit;
until lock = 0;
if buffer[blen-1]='/' then
buffer[blen-1]:=#0;
NameFromLock:=true;
end;
function NameFromFH(fh : BPTR;
buffer: PAnsiChar;
len : LongInt): LongBool; public name '_fpc_amiga_namefromfh';
begin
{$warning NameFromFH unimplemented!}
{ note that this is only used in sysutils/FileSetDate, but because SetFileDate() (see below)
is not easily possible on KS1.x, so it might not be needed to implement this at all (KB) }
NameFromFH:=false;
end;
function ExamineFH(fh : BPTR;
fib: PFileInfoBlock): LongBool; public name '_fpc_amiga_examinefh';
begin
{$warning ExamineFH unimplemented!}
{ ExamineFH is only used to determine file size, in sysfile.inc/do_filesize(),
but this code is already always falling back to double-seek method on KS1.x, and in
other location is sysutils/FileGetDate(), which deals with this function returning
false. Note that ExamineFH can fail on newer Amiga systems as well, because the
underlying FS needs to support ACTION_EXAMINE_FH which some FSes known not to do,
so the only difference is right now that it always fails on KS1.x... }
ExamineFH:=false;
end;
function LockDosList(flags: Cardinal): PDosList; public name '_fpc_amiga_lockdoslist';
var
dosInfo: PDosInfo;
begin
dosInfo:=PDosInfo(PRootNode(PDosLibrary(AOS_DOSBase)^.dl_Root)^.rn_Info shl 2);
{ Actually, DOS v36+ also does Forbid(); in its LockDosList for
compatibility with old programs (KB) }
Forbid();
LockDosList:=PDosList(dosInfo^.di_DevInfo shl 2);
end;
procedure UnLockDosList(flags: Cardinal); public name '_fpc_amiga_unlockdoslist';
begin
{ To pair with the Forbid(); in LockDosList, see comment there (KB) }
Permit();
end;
function NextDosEntry(dlist: PDosList;
flags: Cardinal): PDosList; public name '_fpc_amiga_nextdosentry';
begin
while true do
begin
dlist:=PDosList(dlist^.dol_Next shl 2);
if dlist = nil then
break;
{ Again, this only supports what's really needed for the RTL at the time of writing
this code, feel free to extend (KB) }
if (((flags and LDF_VOLUMES) = LDF_VOLUMES) and (dlist^.dol_Type = DLT_VOLUME)) or
(((flags and LDF_DEVICES) = LDF_DEVICES) and (dlist^.dol_Type = DLT_DEVICE)) then
break;
end;
NextDosEntry:=dlist;
end;
{ helper function used by MatchFirst, all input is expected to be lowercase }
function NameMatchesPattern(pattern: AnsiString; filename: AnsiString): boolean;
var
ofs: longint;
begin
NameMatchesPattern:=(pattern = '*') or (pattern = '#?') or (pattern = filename);
if not NameMatchesPattern then
begin
{ handle the simple case of #?.<ext> and *.<ext>, which is one of the most often used }
ofs:=Pos('#?',pattern);
if (ofs = 1) then
begin
Delete(pattern,1,length('#?'));
ofs:=Pos(pattern,filename);
NameMatchesPattern:=(ofs > 0) and ((ofs - 1) = length(filename) - length(pattern));
if NameMatchesPattern then
exit;
end;
ofs:=Pos('*',pattern);
if (ofs = 1) then
begin
Delete(pattern,1,length('*'));
ofs:=Pos(pattern,filename);
NameMatchesPattern:=(ofs > 0) and ((ofs - 1) = length(filename) - length(pattern));
if NameMatchesPattern then
exit;
end;
end;
end;
function MatchFirst(pat : PAnsiChar;
anchor: PAnchorPath): LongInt; public name '_fpc_amiga_matchfirst';
var
fib_area: array[1..sizeof(TFileInfoBlock) + sizeof(longint)] of byte;
fib: pfileinfoblock;
p: PAnsiChar;
len: LongInt;
Path,FileN: AnsiString;
LastSeparatorPos: Integer;
i: Integer;
newLock: boolean;
DirLock: BPTR;
Res: LongInt;
NChain: PAChain;
begin
MatchFirst := -1;
if not Assigned(Anchor) then
Exit;
// Search for last '/' or ':' and determine length
Len := strlen(Pat);
P := Pat;
LastSeparatorPos := 0;
for i := 1 to Len do
begin
if (P^ = '/') or (P^ = ':') then
begin
LastSeparatorPos := i;
end;
Inc(P);
end;
// copy Directory name
SetLength(Path, LastSeparatorPos);
Move(Pat^, Path[1], LastSeparatorPos);
// copy filename
SetLength(FileN, Len - LastSeparatorPos);
P := Pat;
Inc(P, LastSeparatorPos);
Move(P^, FileN[1], Len - LastSeparatorPos);
// searchpattern lowercase
FileN := LowerCase(FileN);
// if no path is given use the current working dir, or try to lock the dir
if Path = '' then
begin
newLock := False;
DirLock := CurrentDir(0);
if DirLock <> 0 then
UnLock(CurrentDir(DirLock));
end
else
begin
newLock := True;
DirLock := Lock(PAnsiChar(Path), ACCESS_READ);
end;
//
// no dirlock found -> dir not found
if DirLock = 0 then
begin
MatchFirst := -1;
Exit;
end;
fib:=align(@fib_area[1],sizeof(longint));
// examine the dir to get the fib for ExNext
if Examine(DirLock, fib) = 0 then
begin
MatchFirst := -1;
if newLock then
UnLock(DirLock);
Exit;
end;
// we search here directly what we need to find
// guess it's not meant that way but works
repeat
// get next dir entry
Res := ExNext(DirLock, fib);
// nothing nore found -> exit
if Res = 0 then
break;
// include some nifty pattern compare here? later maybe!
if NameMatchesPattern(FileN, lowercase(AnsiString(fib^.fib_FileName))) then
begin
// Match found
// new chain
NChain := AllocMem(SizeOf(TAChain));
if Assigned(Anchor^.ap_First) then
begin
// put chain entry to the list
Anchor^.ap_Last^.an_Child := NChain;
NChain^.an_Parent := Anchor^.ap_Last;
Anchor^.ap_Last := NChain;
end
else
begin
// first chain Entry
Anchor^.ap_Last := NChain;
Anchor^.ap_First := NChain;
NChain^.an_Parent := Pointer(Anchor);
end;
// copy the fileinfoblock into the chain
Move(fib^, NChain^.an_Info, SizeOf(TFileInfoBlock));
end;
until Res = 0; // useless... we jump out earlier
//
// if we found something
if Assigned(Anchor^.ap_Last) then
begin
// set current to the first entry we found
Anchor^.ap_Last := Anchor^.ap_First;
// we only copy the file info block, rest is not needed for freepascal stuff
Move(Anchor^.ap_First^.an_Info, Anchor^.ap_Info, SizeOf(TFileInfoBlock));
// most importantly set the return code
MatchFirst := 0;
end;
if newLock then
Unlock(DirLock);
end;
function MatchNext(anchor: PAnchorPath): LongInt; public name '_fpc_amiga_matchnext';
begin
MatchNext := -1;
if not Assigned(Anchor) then
Exit;
// was already last entry?
if not Assigned(Anchor^.ap_Last) then
Exit;
// Get the next Chain Entry
anchor^.ap_Last := anchor^.ap_Last^.an_Child;
// check if next one is valid and copy the file infoblock, or just set the error code ;)
if Assigned(anchor^.ap_Last) then
begin
Move(Anchor^.ap_Last^.an_Info, Anchor^.ap_Info, SizeOf(TFileInfoBlock));
MatchNext := 0;
end
else
MatchNext := ERROR_NO_MORE_ENTRIES;
end;
procedure MatchEnd(anchor: PAnchorPath); public name '_fpc_amiga_matchend';
var
p, nextp: PAChain;
begin
if Assigned(Anchor) then
begin
// destroy all the chain entries we created before
p := Anchor^.ap_First;
while Assigned(p) do
begin
Nextp := p^.an_Child;
FreeMem(P);
P := NextP;
end;
// reset the contents (is this needed?)
Anchor^.ap_First := nil;
Anchor^.ap_Last := nil;
end;
end;
// we emulate that by the old execute command, should be enough for most cases
function SystemTagList(command: PAnsiChar;
tags : PTagItem): LongInt; public name '_fpc_amiga_systemtaglist';
var
I,O: BPTR; // in / ouput handles
tag: PTagItem;
begin
i := 0;
O := 0;
tag := Tags;
if Assigned(tag) then
begin
repeat
case Tag^.ti_Tag of
SYS_Input: I := Tag^.ti_Data;
SYS_Output: O := Tag^.ti_Data;
end;
NextTag(Tag);
until tag^.ti_Tag = TAG_END;
end;
if Execute(command, I, O) then
SystemTagList := 0
else
SystemTagList := -1;
end;
function GetVar(name : PAnsiChar;
buffer: PAnsiChar;
size : LongInt;
flags : LongInt): LongInt; public name '_fpc_amiga_getvar';
begin
{$warning GetVar unimplemented!}
GetVar:=-1;
end;
function SetFileDate(name: PAnsiChar;
date: PDateStamp): LongBool; public name '_fpc_amiga_setfiledate';
begin
{$warning SetFileDate unimplemented!}
{ Might not be possible to implement, or implement with a reasonable effort on KS1.x (KS) }
{ Used in: dos/SetFTime, sysutils/FileSetDate }
SetFileDate:=false;
end;
function SetFileSize(fh : LongInt;
pos : LongInt;
mode: LongInt): LongInt; public name '_fpc_amiga_setfilesize';
begin
{$warning SetFileSize unimplemented!}
{ Might not be possible to implement, or implement with a reasonable effort on KS1.x (KS) }
{ Used in: sysfile.inc/do_truncate, sysutils/FileCreate, sysutils/FileTruncate }
SetFileSize:=-1;
end;
function GetProgramName(buf: PAnsiChar;
len: LongInt): LongBool; public name '_fpc_amiga_getprogramname';
var
pr: PProcess;
pn: PAnsiChar;
pl: longint;
pcli: PCommandLineInterface;
begin
GetProgramName:=false;
pl:=0;
if len > 0 then
begin
pr:=PProcess(FindTask(nil));
pcli:=PCommandLineInterface(pr^.pr_CLI shl 2);
if (pcli <> nil) and (pcli^.cli_CommandName <> 0) then
begin
pn:=PAnsiChar(pcli^.cli_CommandName shl 2) + 1;
pl:=Byte(pn[-1]);
if pl > len-1 then
pl:=len-1;
move(pn[0],buf[0],pl);
GetProgramName:=true;
end;
buf[pl]:=#0;
end;
end;
function GetProgramDir: LongInt; public name '_fpc_amiga_getprogramdir';
var
cmd: array[0..255] of AnsiChar;
prglock: LongInt;
begin
{ this is quite minimalistic and only covers the simplest cases }
if GetProgramName(cmd,length(cmd)) then
begin
prglock:=Lock(cmd,SHARED_LOCK);
GetProgramDir:=ParentDir(prglock);
Unlock(prglock);
end
else
GetProgramDir:=0;
end;
var
__fpc_global_args: PAnsiChar; external name '__fpc_args';
__fpc_global_arglen: dword; external name '__fpc_arglen';
__fpc_args_buffer: PAnsiChar;
function GetArgStr: PAnsiChar; public name '_fpc_amiga_getargstr';
var
len: dword;
begin
{ the string we get from pre-v2.0 OS is not empty
or zero terminated on start, so we need to copy it
to an alternate buffer, and zero terminate according
to the length. This allocation will be freed on exit
by the memory pool. }
if __fpc_args_buffer = nil then
begin
len:=__fpc_global_arglen-1;
__fpc_args_buffer:=SysAllocMem(len+1);
if len > 0 then
move(__fpc_global_args^,__fpc_args_buffer^,len);
__fpc_args_buffer[len]:=#0;
end;
GetArgStr:=__fpc_args_buffer;
end;