legacydos.inc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 2020 Karoly Balogh, Free Pascal Development team
  4. Amiga dos.library legacy (OS 1.x/2.x) support functions
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. {
  12. This unit implements some missing functions of OS 1.x (and some OS 2.x)
  13. dos.library, so the legacy OS support can be implemented with minimal
  14. changes to the normal system unit and common Amiga-like code
  15. Please note that this code doesn't aim to be API feature complete, just
  16. functional enough for the RTL code.
  17. }
  18. procedure NextTag(var Tag: PTagItem); inline;
  19. begin
  20. if Tag^.ti_Tag = TAG_END then
  21. Exit;
  22. Inc(Tag);
  23. repeat
  24. case Tag^.ti_Tag of
  25. TAG_IGNORE: Inc(Tag);
  26. TAG_SKIP: Inc(Tag, Tag^.ti_Data);
  27. TAG_MORE: Tag := PTagItem(Tag^.ti_Data);
  28. else
  29. Break;
  30. end;
  31. until False;
  32. end;
  33. {$PACKRECORDS 2}
  34. type
  35. TAmigaLegacyFakeSegList = record
  36. length: DWord;
  37. next: DWord;
  38. jump: Word;
  39. entry: Pointer;
  40. pad: Word;
  41. end;
  42. {$PACKRECORDS DEFAULT}
  43. var
  44. __amiga_fake_seglist: TAmigaLegacyFakeSegList;
  45. __amiga_fake_seglist_lock: TSignalSemaphore;
  46. __amiga_fake_seglist_lock_inited: boolean = false;
  47. function CreateNewProc(tags: PTagItem): PProcess; public name '_fpc_amiga_createproc';
  48. var
  49. seglistbptr: dword;
  50. name: PAnsiChar;
  51. entryfunc: pointer;
  52. stacksize: dword;
  53. m: pmsgport;
  54. tag: ptagitem;
  55. begin
  56. CreateNewProc:=nil;
  57. entryfunc:=nil;
  58. stacksize:=4000;
  59. name:='New Process';
  60. tag := Tags;
  61. if Assigned(tag) then
  62. begin
  63. repeat
  64. case Tag^.ti_Tag of
  65. NP_Entry: entryfunc := Pointer(Tag^.ti_Data);
  66. NP_StackSize: stacksize := Tag^.ti_Data;
  67. end;
  68. NextTag(Tag);
  69. until tag^.ti_Tag = TAG_END;
  70. end;
  71. if entryfunc = nil then
  72. exit;
  73. { This is a gigantic hack, and probably only works, because AThreads will always
  74. feed the same function pointer in here (i.e. starts the same function multiple
  75. times, which is a wrapper for FPC threads), and also waits for the subprocess
  76. to properly start before trying to start a new one, but just in case, lets
  77. still have proper-ish locking here, in case one spawns a subthread from a
  78. subthread... (KB) }
  79. if not __amiga_fake_seglist_lock_inited then
  80. begin
  81. InitSemaphore(@__amiga_fake_seglist_lock);
  82. __amiga_fake_seglist_lock_inited:=true;
  83. end;
  84. ObtainSemaphore(@__amiga_fake_seglist_lock);
  85. with __amiga_fake_seglist do
  86. begin
  87. length:=16;
  88. next:=0;
  89. jump:=$4ef9; { JMP }
  90. entry:=entryfunc;
  91. pad:=$4e71; { NOP }
  92. end;
  93. seglistbptr:=ptruint(@__amiga_fake_seglist) shr 2;
  94. m:=CreateProc(name, 0, seglistbptr, stacksize);
  95. if m <> nil then
  96. { CreateProc returns the MsgPort inside the process structure.
  97. recalculate to the address of the process instead... *yuck* (KB) }
  98. CreateNewProc:=PProcess(pointer(m)-ptruint(@PProcess(nil)^.pr_MsgPort));
  99. ReleaseSemaphore(@__amiga_fake_seglist_lock);
  100. end;
  101. function NameFromLock(lock : LongInt;
  102. buffer: PAnsiChar;
  103. len : LongInt): LongBool; public name '_fpc_amiga_namefromlock';
  104. var
  105. fib_area: array[1..sizeof(TFileInfoBlock) + sizeof(longint)] of byte;
  106. fib: pfileinfoblock;
  107. namelen: longint;
  108. blen: longint;
  109. begin
  110. NameFromLock:=false;
  111. if len <= 0 then
  112. exit;
  113. if (lock = 0) and (len >= 5) then
  114. begin
  115. buffer:='SYS:';
  116. NameFromLock:=true;
  117. exit;
  118. end;
  119. fib:=align(@fib_area[1],sizeof(longint));
  120. buffer[0]:=#0;
  121. dec(len); // always preserve one byte for zero term
  122. blen:=0;
  123. repeat
  124. if Examine(lock,fib) <> 0 then
  125. begin
  126. namelen:=strlen(@fib^.fib_FileName[0]);
  127. if (namelen+1) > (len-blen) then
  128. exit;
  129. move(buffer[0],buffer[namelen+1],blen);
  130. move(fib^.fib_FileName[0],buffer[0],namelen);
  131. lock:=ParentDir(lock);
  132. if lock = 0 then
  133. buffer[namelen]:=':'
  134. else
  135. buffer[namelen]:='/';
  136. inc(blen,namelen+1);
  137. buffer[blen]:=#0;
  138. end
  139. else
  140. exit;
  141. until lock = 0;
  142. if buffer[blen-1]='/' then
  143. buffer[blen-1]:=#0;
  144. NameFromLock:=true;
  145. end;
  146. function NameFromFH(fh : BPTR;
  147. buffer: PAnsiChar;
  148. len : LongInt): LongBool; public name '_fpc_amiga_namefromfh';
  149. begin
  150. {$warning NameFromFH unimplemented!}
  151. { note that this is only used in sysutils/FileSetDate, but because SetFileDate() (see below)
  152. is not easily possible on KS1.x, so it might not be needed to implement this at all (KB) }
  153. NameFromFH:=false;
  154. end;
  155. function ExamineFH(fh : BPTR;
  156. fib: PFileInfoBlock): LongBool; public name '_fpc_amiga_examinefh';
  157. begin
  158. {$warning ExamineFH unimplemented!}
  159. { ExamineFH is only used to determine file size, in sysfile.inc/do_filesize(),
  160. but this code is already always falling back to double-seek method on KS1.x, and in
  161. other location is sysutils/FileGetDate(), which deals with this function returning
  162. false. Note that ExamineFH can fail on newer Amiga systems as well, because the
  163. underlying FS needs to support ACTION_EXAMINE_FH which some FSes known not to do,
  164. so the only difference is right now that it always fails on KS1.x... }
  165. ExamineFH:=false;
  166. end;
  167. function LockDosList(flags: Cardinal): PDosList; public name '_fpc_amiga_lockdoslist';
  168. var
  169. dosInfo: PDosInfo;
  170. begin
  171. dosInfo:=PDosInfo(PRootNode(PDosLibrary(AOS_DOSBase)^.dl_Root)^.rn_Info shl 2);
  172. { Actually, DOS v36+ also does Forbid(); in its LockDosList for
  173. compatibility with old programs (KB) }
  174. Forbid();
  175. LockDosList:=PDosList(dosInfo^.di_DevInfo shl 2);
  176. end;
  177. procedure UnLockDosList(flags: Cardinal); public name '_fpc_amiga_unlockdoslist';
  178. begin
  179. { To pair with the Forbid(); in LockDosList, see comment there (KB) }
  180. Permit();
  181. end;
  182. function NextDosEntry(dlist: PDosList;
  183. flags: Cardinal): PDosList; public name '_fpc_amiga_nextdosentry';
  184. begin
  185. while true do
  186. begin
  187. dlist:=PDosList(dlist^.dol_Next shl 2);
  188. if dlist = nil then
  189. break;
  190. { Again, this only supports what's really needed for the RTL at the time of writing
  191. this code, feel free to extend (KB) }
  192. if (((flags and LDF_VOLUMES) = LDF_VOLUMES) and (dlist^.dol_Type = DLT_VOLUME)) or
  193. (((flags and LDF_DEVICES) = LDF_DEVICES) and (dlist^.dol_Type = DLT_DEVICE)) then
  194. break;
  195. end;
  196. NextDosEntry:=dlist;
  197. end;
  198. { helper function used by MatchFirst, all input is expected to be lowercase }
  199. function NameMatchesPattern(pattern: AnsiString; filename: AnsiString): boolean;
  200. var
  201. ofs: longint;
  202. begin
  203. NameMatchesPattern:=(pattern = '*') or (pattern = '#?') or (pattern = filename);
  204. if not NameMatchesPattern then
  205. begin
  206. { handle the simple case of #?.<ext> and *.<ext>, which is one of the most often used }
  207. ofs:=Pos('#?',pattern);
  208. if (ofs = 1) then
  209. begin
  210. Delete(pattern,1,length('#?'));
  211. ofs:=Pos(pattern,filename);
  212. NameMatchesPattern:=(ofs > 0) and ((ofs - 1) = length(filename) - length(pattern));
  213. if NameMatchesPattern then
  214. exit;
  215. end;
  216. ofs:=Pos('*',pattern);
  217. if (ofs = 1) then
  218. begin
  219. Delete(pattern,1,length('*'));
  220. ofs:=Pos(pattern,filename);
  221. NameMatchesPattern:=(ofs > 0) and ((ofs - 1) = length(filename) - length(pattern));
  222. if NameMatchesPattern then
  223. exit;
  224. end;
  225. end;
  226. end;
  227. function MatchFirst(pat : PAnsiChar;
  228. anchor: PAnchorPath): LongInt; public name '_fpc_amiga_matchfirst';
  229. var
  230. fib_area: array[1..sizeof(TFileInfoBlock) + sizeof(longint)] of byte;
  231. fib: pfileinfoblock;
  232. p: PAnsiChar;
  233. len: LongInt;
  234. Path,FileN: AnsiString;
  235. LastSeparatorPos: Integer;
  236. i: Integer;
  237. newLock: boolean;
  238. DirLock: BPTR;
  239. Res: LongInt;
  240. NChain: PAChain;
  241. begin
  242. MatchFirst := -1;
  243. if not Assigned(Anchor) then
  244. Exit;
  245. // Search for last '/' or ':' and determine length
  246. Len := strlen(Pat);
  247. P := Pat;
  248. LastSeparatorPos := 0;
  249. for i := 1 to Len do
  250. begin
  251. if (P^ = '/') or (P^ = ':') then
  252. begin
  253. LastSeparatorPos := i;
  254. end;
  255. Inc(P);
  256. end;
  257. // copy Directory name
  258. SetLength(Path, LastSeparatorPos);
  259. Move(Pat^, Path[1], LastSeparatorPos);
  260. // copy filename
  261. SetLength(FileN, Len - LastSeparatorPos);
  262. P := Pat;
  263. Inc(P, LastSeparatorPos);
  264. Move(P^, FileN[1], Len - LastSeparatorPos);
  265. // searchpattern lowercase
  266. FileN := LowerCase(FileN);
  267. // if no path is given use the current working dir, or try to lock the dir
  268. if Path = '' then
  269. begin
  270. newLock := False;
  271. DirLock := CurrentDir(0);
  272. if DirLock <> 0 then
  273. UnLock(CurrentDir(DirLock));
  274. end
  275. else
  276. begin
  277. newLock := True;
  278. DirLock := Lock(PAnsiChar(Path), ACCESS_READ);
  279. end;
  280. //
  281. // no dirlock found -> dir not found
  282. if DirLock = 0 then
  283. begin
  284. MatchFirst := -1;
  285. Exit;
  286. end;
  287. fib:=align(@fib_area[1],sizeof(longint));
  288. // examine the dir to get the fib for ExNext
  289. if Examine(DirLock, fib) = 0 then
  290. begin
  291. MatchFirst := -1;
  292. if newLock then
  293. UnLock(DirLock);
  294. Exit;
  295. end;
  296. // we search here directly what we need to find
  297. // guess it's not meant that way but works
  298. repeat
  299. // get next dir entry
  300. Res := ExNext(DirLock, fib);
  301. // nothing nore found -> exit
  302. if Res = 0 then
  303. break;
  304. // include some nifty pattern compare here? later maybe!
  305. if NameMatchesPattern(FileN, lowercase(AnsiString(fib^.fib_FileName))) then
  306. begin
  307. // Match found
  308. // new chain
  309. NChain := AllocMem(SizeOf(TAChain));
  310. if Assigned(Anchor^.ap_First) then
  311. begin
  312. // put chain entry to the list
  313. Anchor^.ap_Last^.an_Child := NChain;
  314. NChain^.an_Parent := Anchor^.ap_Last;
  315. Anchor^.ap_Last := NChain;
  316. end
  317. else
  318. begin
  319. // first chain Entry
  320. Anchor^.ap_Last := NChain;
  321. Anchor^.ap_First := NChain;
  322. NChain^.an_Parent := Pointer(Anchor);
  323. end;
  324. // copy the fileinfoblock into the chain
  325. Move(fib^, NChain^.an_Info, SizeOf(TFileInfoBlock));
  326. end;
  327. until Res = 0; // useless... we jump out earlier
  328. //
  329. // if we found something
  330. if Assigned(Anchor^.ap_Last) then
  331. begin
  332. // set current to the first entry we found
  333. Anchor^.ap_Last := Anchor^.ap_First;
  334. // we only copy the file info block, rest is not needed for freepascal stuff
  335. Move(Anchor^.ap_First^.an_Info, Anchor^.ap_Info, SizeOf(TFileInfoBlock));
  336. // most importantly set the return code
  337. MatchFirst := 0;
  338. end;
  339. if newLock then
  340. Unlock(DirLock);
  341. end;
  342. function MatchNext(anchor: PAnchorPath): LongInt; public name '_fpc_amiga_matchnext';
  343. begin
  344. MatchNext := -1;
  345. if not Assigned(Anchor) then
  346. Exit;
  347. // was already last entry?
  348. if not Assigned(Anchor^.ap_Last) then
  349. Exit;
  350. // Get the next Chain Entry
  351. anchor^.ap_Last := anchor^.ap_Last^.an_Child;
  352. // check if next one is valid and copy the file infoblock, or just set the error code ;)
  353. if Assigned(anchor^.ap_Last) then
  354. begin
  355. Move(Anchor^.ap_Last^.an_Info, Anchor^.ap_Info, SizeOf(TFileInfoBlock));
  356. MatchNext := 0;
  357. end
  358. else
  359. MatchNext := ERROR_NO_MORE_ENTRIES;
  360. end;
  361. procedure MatchEnd(anchor: PAnchorPath); public name '_fpc_amiga_matchend';
  362. var
  363. p, nextp: PAChain;
  364. begin
  365. if Assigned(Anchor) then
  366. begin
  367. // destroy all the chain entries we created before
  368. p := Anchor^.ap_First;
  369. while Assigned(p) do
  370. begin
  371. Nextp := p^.an_Child;
  372. FreeMem(P);
  373. P := NextP;
  374. end;
  375. // reset the contents (is this needed?)
  376. Anchor^.ap_First := nil;
  377. Anchor^.ap_Last := nil;
  378. end;
  379. end;
  380. // we emulate that by the old execute command, should be enough for most cases
  381. function SystemTagList(command: PAnsiChar;
  382. tags : PTagItem): LongInt; public name '_fpc_amiga_systemtaglist';
  383. var
  384. I,O: BPTR; // in / ouput handles
  385. tag: PTagItem;
  386. begin
  387. i := 0;
  388. O := 0;
  389. tag := Tags;
  390. if Assigned(tag) then
  391. begin
  392. repeat
  393. case Tag^.ti_Tag of
  394. SYS_Input: I := Tag^.ti_Data;
  395. SYS_Output: O := Tag^.ti_Data;
  396. end;
  397. NextTag(Tag);
  398. until tag^.ti_Tag = TAG_END;
  399. end;
  400. if Execute(command, I, O) then
  401. SystemTagList := 0
  402. else
  403. SystemTagList := -1;
  404. end;
  405. function GetVar(name : PAnsiChar;
  406. buffer: PAnsiChar;
  407. size : LongInt;
  408. flags : LongInt): LongInt; public name '_fpc_amiga_getvar';
  409. begin
  410. {$warning GetVar unimplemented!}
  411. GetVar:=-1;
  412. end;
  413. function SetFileDate(name: PAnsiChar;
  414. date: PDateStamp): LongBool; public name '_fpc_amiga_setfiledate';
  415. begin
  416. {$warning SetFileDate unimplemented!}
  417. { Might not be possible to implement, or implement with a reasonable effort on KS1.x (KS) }
  418. { Used in: dos/SetFTime, sysutils/FileSetDate }
  419. SetFileDate:=false;
  420. end;
  421. function SetFileSize(fh : LongInt;
  422. pos : LongInt;
  423. mode: LongInt): LongInt; public name '_fpc_amiga_setfilesize';
  424. begin
  425. {$warning SetFileSize unimplemented!}
  426. { Might not be possible to implement, or implement with a reasonable effort on KS1.x (KS) }
  427. { Used in: sysfile.inc/do_truncate, sysutils/FileCreate, sysutils/FileTruncate }
  428. SetFileSize:=-1;
  429. end;
  430. function GetProgramName(buf: PAnsiChar;
  431. len: LongInt): LongBool; public name '_fpc_amiga_getprogramname';
  432. var
  433. pr: PProcess;
  434. pn: PAnsiChar;
  435. pl: longint;
  436. pcli: PCommandLineInterface;
  437. begin
  438. GetProgramName:=false;
  439. pl:=0;
  440. if len > 0 then
  441. begin
  442. pr:=PProcess(FindTask(nil));
  443. pcli:=PCommandLineInterface(pr^.pr_CLI shl 2);
  444. if (pcli <> nil) and (pcli^.cli_CommandName <> 0) then
  445. begin
  446. pn:=PAnsiChar(pcli^.cli_CommandName shl 2) + 1;
  447. pl:=Byte(pn[-1]);
  448. if pl > len-1 then
  449. pl:=len-1;
  450. move(pn[0],buf[0],pl);
  451. GetProgramName:=true;
  452. end;
  453. buf[pl]:=#0;
  454. end;
  455. end;
  456. function GetProgramDir: LongInt; public name '_fpc_amiga_getprogramdir';
  457. var
  458. cmd: array[0..255] of AnsiChar;
  459. prglock: LongInt;
  460. begin
  461. { this is quite minimalistic and only covers the simplest cases }
  462. if GetProgramName(cmd,length(cmd)) then
  463. begin
  464. prglock:=Lock(cmd,SHARED_LOCK);
  465. GetProgramDir:=ParentDir(prglock);
  466. Unlock(prglock);
  467. end
  468. else
  469. GetProgramDir:=0;
  470. end;
  471. var
  472. __fpc_global_args: PAnsiChar; external name '__fpc_args';
  473. __fpc_global_arglen: dword; external name '__fpc_arglen';
  474. __fpc_args_buffer: PAnsiChar;
  475. function GetArgStr: PAnsiChar; public name '_fpc_amiga_getargstr';
  476. var
  477. len: dword;
  478. begin
  479. { the string we get from pre-v2.0 OS is not empty
  480. or zero terminated on start, so we need to copy it
  481. to an alternate buffer, and zero terminate according
  482. to the length. This allocation will be freed on exit
  483. by the memory pool. }
  484. if __fpc_args_buffer = nil then
  485. begin
  486. len:=__fpc_global_arglen-1;
  487. __fpc_args_buffer:=SysAllocMem(len+1);
  488. if len > 0 then
  489. move(__fpc_global_args^,__fpc_args_buffer^,len);
  490. __fpc_args_buffer[len]:=#0;
  491. end;
  492. GetArgStr:=__fpc_args_buffer;
  493. end;