mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-07 07:28:26 +02:00
541 lines
15 KiB
PHP
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;
|