dosbox_wrapper.pas 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. {$MODE objfpc}{$H+}
  2. uses
  3. SysUtils, StrUtils,
  4. {$ifdef UseSignals}
  5. signals,
  6. {$endif def UseSignals}
  7. testu, classes,
  8. Process;
  9. const
  10. use_temp_dir : boolean = true;
  11. need_cwsdpmi : boolean = false;
  12. cwsdpmi_file : string = '';
  13. hide_execution : boolean = true;
  14. do_exit : boolean = true;
  15. verbose : boolean = false;
  16. DosBoxProcess: TProcess = nil;
  17. dosbox_timeout : integer = 400; { default timeout in seconds }
  18. var
  19. OutputFileName : String;
  20. SourceFileName : String;
  21. StartDir, DosBoxDir: string;
  22. ExitCode: Integer = 255;
  23. DosBoxBinaryPath: string;
  24. TmpFileList : TStringList;
  25. function GenerateTempDir: string;
  26. var
  27. FileName: string;
  28. TempDir: string;
  29. Done: Boolean = False;
  30. begin
  31. TempDir := GetTempDir(False);
  32. repeat
  33. try
  34. FileName := TempDir + 'dosboxwrappertmp_' + IntToStr(Random(100000));
  35. if verbose then
  36. writeln('Trying to create directory ',Filename);
  37. MkDir(FileName);
  38. Done := True;
  39. except
  40. on E: EInOutError do
  41. begin
  42. { 5 = Access Denied, returned when a file is duplicated }
  43. if E.ErrorCode <> 5 then
  44. begin
  45. Writeln('Directory creation failed');
  46. raise;
  47. end;
  48. end;
  49. end;
  50. until Done;
  51. Result := FileName + DirectorySeparator;
  52. end;
  53. procedure GenerateDosBoxConf(const ADosBoxDir: string);
  54. var
  55. SourceConfFileName, TargetConfFileName: string;
  56. SourceFile, TargetFile: TextFile;
  57. OrigS, S: string;
  58. begin
  59. SourceConfFileName := ExtractFilePath(ParamStr(0)) + 'dosbox.conf';
  60. TargetConfFileName := ADosBoxDir + 'dosbox.conf';
  61. OutputFileName := ADosBoxDir + 'dosbox.out';
  62. if verbose then
  63. Writeln('Using target dosbox.conf ',TargetConfFileName);
  64. AssignFile(SourceFile, SourceConfFileName);
  65. AssignFile(TargetFile, TargetConfFileName);
  66. Reset(SourceFile);
  67. try
  68. Rewrite(TargetFile);
  69. try
  70. while not EoF(SourceFile) do
  71. begin
  72. Readln(SourceFile, S);
  73. OrigS:=S;
  74. S := AnsiReplaceStr(S, '$DosBoxDir', ADosBoxDir);
  75. S := AnsiReplaceStr(S, '$wrapper_output', OutputFileName);
  76. if do_exit then
  77. S := AnsiReplaceStr(S, '$exit', 'exit')
  78. else
  79. S := AnsiReplaceStr(S, '$exit', '');
  80. If verbose and (OrigS <> S) then
  81. Writeln('"',OrigS,'" transformed into "',S,'"');
  82. Writeln(TargetFile, S);
  83. end;
  84. finally
  85. CloseFile(TargetFile);
  86. end;
  87. finally
  88. CloseFile(SourceFile);
  89. end;
  90. end;
  91. { File names in Config entries assume that
  92. executables have no suffix }
  93. function TargetFileExists(AName : string) : boolean;
  94. begin
  95. result:=SysUtils.FileExists(AName);
  96. if not result then
  97. result:=SysUtils.FileExists(AName+'.exe');
  98. if not result then
  99. result:=SysUtils.FileExists(AName+'.EXE');
  100. end;
  101. procedure CopyFile(ASrcFileName, ADestFileName: string);
  102. var
  103. SrcF, DestF: File;
  104. OldFileMode: Integer;
  105. Buf: array [0..4095] of Byte;
  106. BytesRead: Integer;
  107. begin
  108. if not AnsiEndsText('.exe', ASrcFileName) and AnsiEndsText('.EXE',ADestFileName) then
  109. ASrcFileName := ASrcFileName + '.exe';
  110. if not FileExists(ASrcFileName) then
  111. begin
  112. ASrcFileName:=ASrcFileName+'.exe';
  113. ADestFileName:=ADestFileName+'.exe';
  114. end;
  115. if verbose then
  116. Writeln('CopyFile "', ASrcFileName, '" -> "', ADestFileName,'"');
  117. OldFileMode := FileMode;
  118. try
  119. try
  120. AssignFile(SrcF, ASrcFileName);
  121. AssignFile(DestF, ADestFileName);
  122. FileMode := fmOpenRead;
  123. Reset(SrcF, 1);
  124. try
  125. FileMode := fmOpenWrite;
  126. try
  127. Rewrite(DestF, 1);
  128. repeat
  129. BlockRead(SrcF, Buf, SizeOf(Buf), BytesRead);
  130. BlockWrite(DestF, Buf, BytesRead);
  131. until BytesRead < SizeOf(Buf);
  132. finally
  133. CloseFile(DestF);
  134. end;
  135. finally
  136. CloseFile(SrcF);
  137. end;
  138. finally
  139. FileMode := OldFileMode;
  140. end;
  141. except
  142. on E : Exception do
  143. writeln('Error: '+ E.ClassName + #13#10 + E.Message );
  144. end;
  145. end;
  146. function ForceExtension(Const HStr,ext:String):String;
  147. {
  148. Return a filename which certainly has the extension ext
  149. }
  150. var
  151. j : longint;
  152. begin
  153. j:=length(Hstr);
  154. while (j>0) and (Hstr[j]<>'.') do
  155. dec(j);
  156. if j=0 then
  157. j:=length(Hstr)+1;
  158. if Ext<>'' then
  159. begin
  160. if Ext[1]='.' then
  161. ForceExtension:=Copy(Hstr,1,j-1)+Ext
  162. else
  163. ForceExtension:=Copy(Hstr,1,j-1)+'.'+Ext
  164. end
  165. else
  166. ForceExtension:=Copy(Hstr,1,j-1);
  167. end;
  168. procedure CopyNeededFiles;
  169. var
  170. Config : TConfig;
  171. LocalFile, RemoteFile, s: string;
  172. LocalPath: string;
  173. i : integer;
  174. FileList : TStringList;
  175. RelativeToConfigMarker : TObject;
  176. function SplitPath(const s:string):string;
  177. var
  178. i : longint;
  179. begin
  180. i:=Length(s);
  181. while (i>0) and not(s[i] in ['/','\'{$IFDEF MACOS},':'{$ENDIF}]) do
  182. dec(i);
  183. SplitPath:=Copy(s,1,i);
  184. end;
  185. function BuildFileList: TStringList;
  186. var
  187. dfl, fl : string;
  188. begin
  189. fl:=Trim(Config.Files);
  190. dfl:=Trim(Config.DelFiles);
  191. if (fl='') and (dfl='') and (Config.ConfigFileSrc='') then
  192. begin
  193. Result:=nil;
  194. exit;
  195. end;
  196. Result:=TStringList.Create;
  197. while fl<>'' do
  198. begin
  199. LocalFile:=Trim(GetToken(fl, [' ',',',';']));
  200. Result.Add(LocalFile);
  201. if verbose then
  202. writeln('Adding file ',LocalFile,' from Config.Files');
  203. end;
  204. if Config.ConfigFileSrc<>'' then
  205. begin
  206. if Config.ConfigFileSrc=Config.ConfigFileDst then
  207. Result.AddObject(Config.ConfigFileSrc,RelativeToConfigMarker)
  208. else
  209. Result.AddObject(Config.ConfigFileSrc+'='+Config.ConfigFileDst,RelativeToConfigMarker);
  210. if verbose then
  211. writeln('Adding config file Src=',Config.ConfigFileSrc,' Dst=',Config.ConfigFileDst);
  212. end;
  213. while dfl <> '' do
  214. begin
  215. LocalFile:=Trim(GetToken(dfl, [' ',',',';']));
  216. Result.Add(LocalFile);
  217. if verbose then
  218. writeln('Adding file ',LocalFile,' from Config.DelFiles');
  219. end;
  220. end;
  221. var
  222. ddir : string;
  223. param1_dir : string;
  224. begin
  225. param1_dir:=ExtractFilePath(ParamStr(1));
  226. if not IsAbsolute(SourceFileName) and not TargetFileExists(SourceFileName) then
  227. begin
  228. ddir:=GetEnvironmentVariable('BASEDIR');
  229. if ddir='' then
  230. GetDir(0,ddir);
  231. // writeln('Start ddir=',ddir);
  232. while (ddir<>'') do
  233. begin
  234. if TargetFileExists(ddir+DirectorySeparator+SourceFileName) then
  235. begin
  236. SourceFileName:=ddir+DirectorySeparator+SourceFileName;
  237. break;
  238. end
  239. else
  240. begin
  241. if ddir=splitpath(ddir) then
  242. break
  243. else
  244. ddir:=splitpath(ddir);
  245. if ddir[length(ddir)]=DirectorySeparator then
  246. ddir:=copy(ddir,1,length(ddir)-1);
  247. // writeln('Next ddir=',ddir);
  248. end;
  249. end;
  250. end;
  251. if not TargetFileExists(SourceFileName) then
  252. begin
  253. writeln('File ',SourceFileName,' not found');
  254. exit;
  255. end
  256. else if verbose then
  257. writeln('Analyzing source file ',SourceFileName);
  258. if not GetConfig(SourceFileName,config) then
  259. exit;
  260. RelativeToConfigMarker:=TObject.Create;
  261. FileList:=BuildFileList;
  262. TmpFileList:=TStringList.Create;
  263. if assigned(FileList) then
  264. begin
  265. LocalPath:=SplitPath(SourceFileName);
  266. if (Length(LocalPath) > 0) and (LocalPath[Length(LocalPath)]<>DirectorySeparator) then
  267. LocalPath:=LocalPath+DirectorySeparator;
  268. for i:=0 to FileList.count-1 do
  269. begin
  270. if FileList.Names[i]<>'' then
  271. begin
  272. LocalFile:=FileList.Names[i];
  273. RemoteFile:=FileList.ValueFromIndex[i];
  274. end
  275. else
  276. begin
  277. LocalFile:=FileList[i];
  278. RemoteFile:=LocalFile;
  279. end;
  280. if FileList.Objects[i]=RelativeToConfigMarker then
  281. s:='config/'+LocalFile
  282. else
  283. s:=LocalPath+LocalFile;
  284. if not TargetFileExists(s) then
  285. if TargetFileExists(param1_dir+DirectorySeparator+LocalFile) then
  286. s:=param1_dir+DirectorySeparator+LocalFile;
  287. CopyFile(s,DosBoxDir+RemoteFile);
  288. TmpFileList.Add(RemoteFile);
  289. end;
  290. FileList.Free;
  291. end;
  292. RelativeToConfigMarker.Free;
  293. end;
  294. { On modified dosbox executable it is possible to get
  295. a copy of all output to CON into a file, simply write it
  296. back to output, so it ends up into testname.elg file.
  297. Skip all until line beginning with 'Drive C is mounted as' }
  298. procedure EchoOutput;
  299. const
  300. SkipUntilText = 'Drive C is mounted as ';
  301. var
  302. StdText : TextFile;
  303. st : string;
  304. line : longint;
  305. SkipUntilSeen : boolean;
  306. begin
  307. if FileExists(OutputFileName) then
  308. begin
  309. if verbose then
  310. Writeln('Trying to open ',OutputFileName);
  311. try
  312. AssignFile(StdText, OutputFileName);
  313. Reset(StdText);
  314. if verbose then
  315. Writeln('Successfully opened ',OutputFileName,', copying content to output');
  316. try
  317. line:=0;
  318. SkipUntilSeen:=false;
  319. while not eof(StdText) do
  320. begin
  321. Readln(StdText,st);
  322. inc(line);
  323. if not SkipUntilSeen then
  324. SkipUntilSeen:=pos(SkipUntilText,st)>0;
  325. if SkipUntilSeen then
  326. Writeln(line,': ',st);
  327. end;
  328. finally
  329. if not SkipUntilSeen then
  330. Writeln('Could not find "',SkipUntilText,'" in file ',OutputFilename);
  331. Flush(output);
  332. CloseFile(StdText);
  333. end;
  334. finally
  335. if use_temp_dir then
  336. DeleteFile(OutputFileName);
  337. end;
  338. end;
  339. end;
  340. function ReadExitCode(const ADosBoxDir: string): Integer;
  341. var
  342. F: TextFile;
  343. begin
  344. AssignFile(F, ADosBoxDir + 'EXITCODE.TXT');
  345. try
  346. Reset(F);
  347. Readln(F, Result);
  348. if Result <> 0 then
  349. Writeln('ExitCode=',Result);
  350. CloseFile(F);
  351. except
  352. Writeln('Unable to read exitcode value');
  353. ReadExitCode:=127*256;
  354. end;
  355. end;
  356. procedure ExecuteDosBox(const ADosBoxBinaryPath, ADosBoxDir: string);
  357. var
  358. Time: Integer = 0;
  359. begin
  360. DosBoxProcess := TProcess.Create(nil);
  361. try
  362. DosBoxProcess.Executable := ADosBoxBinaryPath;
  363. DosBoxProcess.Parameters.Add('-conf');
  364. DosBoxProcess.Parameters.Add(ADosBoxDir + 'dosbox.conf');
  365. if hide_execution then
  366. DosBoxProcess.ShowWindow := swoHIDE;
  367. DosBoxProcess.Execute;
  368. repeat
  369. Inc(Time);
  370. if (Time > 10*dosbox_timeout) and do_exit then
  371. break;
  372. Sleep(100);
  373. until not DosBoxProcess.Running;
  374. if DosBoxProcess.Running then
  375. begin
  376. Writeln('Timeout exceeded. Killing dosbox...');
  377. DosBoxProcess.Terminate(254);
  378. Sleep(100);
  379. end;
  380. finally
  381. DosBoxProcess.Free;
  382. DosBoxProcess:=nil;
  383. EchoOutput;
  384. end;
  385. end;
  386. function DeleteIfExists(const AFileName: string) : boolean;
  387. begin
  388. result:=false;
  389. if FileExists(AFileName) then
  390. result:=DeleteFile(AFileName);
  391. if not result and FileExists(AFileName+'.exe') then
  392. result:=DeleteFile(AFileName+'.exe');
  393. if not result and FileExists(AFileName+'.EXE') then
  394. result:=DeleteFile(AFileName+'.EXE');
  395. end;
  396. { RemoveDir, with removal of files or subdirectories inside first.
  397. ADirName is supposed to finish with DirectorySeparator }
  398. function RemoveDir(const ADirName: string) : boolean;
  399. var
  400. Info : TSearchRec;
  401. begin
  402. Result:=true;
  403. If FindFirst (AdirName+'*',faAnyFile and faDirectory,Info)=0 then
  404. begin
  405. repeat
  406. with Info do
  407. begin
  408. If (Attr and faDirectory) = faDirectory then
  409. begin
  410. { Skip present and parent directory }
  411. if (Name<>'..') and (Name<>'.') then
  412. if not RemoveDir(ADirName+Name+DirectorySeparator) then
  413. begin
  414. writeln('Failed to remove dir '+ADirName+Name+DirectorySeparator);
  415. result:=false;
  416. FindClose(Info);
  417. exit;
  418. end;
  419. end
  420. else
  421. if not DeleteFile(ADirName+Name) then
  422. begin
  423. writeln('Failed to remove file '+ADirName+Name);
  424. result:=false;
  425. FindClose(Info);
  426. exit;
  427. end;
  428. end;
  429. Until FindNext(info)<>0;
  430. end;
  431. FindClose(Info);
  432. RemoveDir:=SysUtils.RemoveDir(ADirName);
  433. end;
  434. procedure Cleanup(const ADosBoxDir: string);
  435. var
  436. i : longint;
  437. begin
  438. DeleteIfExists(ADosBoxDir + 'dosbox.conf');
  439. DeleteIfExists(ADosBoxDir + 'EXITCODE.TXT');
  440. DeleteIfExists(ADosBoxDir + 'EXITCODE.EXE');
  441. DeleteIfExists(ADosBoxDir + 'CWSDPMI.EXE');
  442. DeleteIfExists(ADosBoxDir + 'TEST.EXE');
  443. if Assigned(TmpFileList) then
  444. begin
  445. for i:=0 to TmpFileList.count-1 do
  446. if TmpFileList[i]<>'' then
  447. DeleteIfExists(ADosBoxDir + TmpFileList[i]);
  448. end;
  449. TmpFileList.Free;
  450. ChDir(StartDir);
  451. if not RemoveDir(ADosBoxDir) then
  452. writeln('Failed to remove dir ',ADosBoxDir);
  453. end;
  454. {$ifdef UseSignals}
  455. const
  456. SignalCalled : boolean = false;
  457. SignalNb : longint = 0;
  458. function DosBoxSignal(signal:longint):longint; cdecl;
  459. begin
  460. SignalCalled:=true;
  461. SignalNb:=signal;
  462. end;
  463. {$endif def UseSignals}
  464. procedure ExitProc;
  465. var
  466. count : longint;
  467. begin
  468. if assigned(DosBoxProcess) and (DosBoxProcess.Running) then
  469. begin
  470. Writeln('In ExitProc. Killing dosbox...');
  471. DosBoxProcess.Terminate(254*1024);
  472. Sleep(100);
  473. count:=1;
  474. while (DosBoxProcess.Running) do
  475. begin
  476. Sleep(100);
  477. inc(count);
  478. if (count mod 20=0) then
  479. DosBoxProcess.Terminate(254*1024+count);
  480. end;
  481. if count>1 then
  482. Writeln('In ExitProc. Wait for termination dosbox..., time=',count/10);
  483. EchoOutput;
  484. end;
  485. end;
  486. begin
  487. Randomize;
  488. if GetEnvironmentVariable('DOSBOX_NO_TEMPDIR')<>'' then
  489. begin
  490. use_temp_dir:=false;
  491. Writeln('use_temp_dir set to false');
  492. end;
  493. if GetEnvironmentVariable('DOSBOX_NO_HIDE')<>'' then
  494. begin
  495. hide_execution:=false;
  496. Writeln('hide_execution set to false');
  497. end;
  498. if GetEnvironmentVariable('DOSBOX_NO_EXIT')<>'' then
  499. begin
  500. do_exit:=false;
  501. Writeln('do_exit set to false');
  502. end;
  503. if GetEnvironmentVariable('DOSBOX_VERBOSE')<>'' then
  504. begin
  505. verbose:=true;
  506. Writeln('verbose set to true');
  507. end;
  508. if (GetEnvironmentVariable('DOSBOX_NEEDS_CWSDPMI')<>'') or
  509. (GetEnvironmentVariable('TEST_OS_TARGET')='go32v2') then
  510. begin
  511. need_cwsdpmi:=true;
  512. Writeln('need_cwsdpmi set to true');
  513. end;
  514. if GetEnvironmentVariable('DOSBOX_TIMEOUT')<>'' then
  515. begin
  516. dosbox_timeout:=StrToInt(GetEnvironmentVariable('DOSBOX_TIMEOUT'));
  517. Writeln('dosbox_timeout set to ', dosbox_timeout, ' seconds');
  518. end;
  519. if ParamCount = 0 then
  520. begin
  521. Writeln('Usage: ' + ParamStr(0) + ' <executable> (-Ssourcename)');
  522. Writeln('Set DOSBOX_NO_TEMPDIR env variable to 1 to avoid using a temporary directory');
  523. Writeln('Set DOSBOX_NO_HIDE to avoid running dosbox in an hidden window');
  524. Writeln('Set DOSBOX_NO_EXIT to avoid exiting dosbox after test has been run');
  525. Writeln('Set DOSBOX_TIMEOUT to set the timeout in seconds before killing the dosbox process, assuming the test has hanged');
  526. halt(1);
  527. end;
  528. DosBoxBinaryPath := GetEnvironmentVariable('DOSBOX');
  529. if DosBoxBinaryPath = '' then
  530. begin
  531. Writeln('Please set the DOSBOX environment variable to the dosbox executable');
  532. halt(1);
  533. end
  534. else
  535. begin
  536. Writeln('Using DOSBOX executable: ',DosBoxBinaryPath);
  537. end;
  538. { DosBoxDir is used inside dosbox.conf as a MOUNT parameter }
  539. if use_temp_dir then
  540. begin
  541. GetDir(0,StartDir);
  542. DosBoxDir := GenerateTempDir;
  543. { All executable test have t.*.pp pattern }
  544. if (paramcount>1) and (copy(paramstr(2),1,2)='-S') then
  545. SourceFileName:=copy(paramstr(2),3,length(paramstr(2)))
  546. else
  547. SourceFileName:=ForceExtension(Paramstr(1),'.pp');
  548. CopyNeededFiles;
  549. end
  550. else
  551. begin
  552. Writeln('Using ',ParamStr(1));
  553. DosBoxDir:=ExtractFilePath(ParamStr(1));
  554. if DosBoxDir='' then
  555. DosBoxDir:=GetCurrentDir+DirectorySeparator;
  556. Writeln('Using DosBoxDir=',DosBoxDir);
  557. { Get rid of previous exicode.txt file }
  558. DeleteIfExists(DosBoxDir + 'EXITCODE.TXT');
  559. end;
  560. try
  561. {$ifdef UseSignals}
  562. Signal(SIGINT,@DosBoxSignal);
  563. Signal(SIGQUIT,@DosBoxSignal);
  564. Signal(SIGTERM,@DosBoxSignal);
  565. {$endif def UseSignals}
  566. GenerateDosBoxConf(DosBoxDir);
  567. CopyFile(ExtractFilePath(ParamStr(0)) + 'exitcode.exe', DosBoxDir + 'EXITCODE.EXE');
  568. CopyFile(ParamStr(1), DosBoxDir + 'TEST.EXE');
  569. if need_cwsdpmi then
  570. begin
  571. cwsdpmi_file:=FileSearch('cwsdpmi.exe',GetEnvironmentVariable('PATH'));
  572. if cwsdpmi_file<>'' then
  573. CopyFile(cwsdpmi_file, DosBoxDir + 'CWSDPMI.EXE')
  574. else if verbose then
  575. writeln('cwsdpmi executable missing');
  576. end;
  577. ExecuteDosBox(DosBoxBinaryPath, DosBoxDir);
  578. finally
  579. ExitProc;
  580. end;
  581. {$ifdef UseSignals}
  582. if SignalCalled then
  583. begin
  584. Writeln('Signal ',SignalNb,' called');
  585. end;
  586. {$endif def UseSignals}
  587. ExitProc;
  588. ExitCode:=ReadExitCode(DosBoxDir);
  589. if use_temp_dir then
  590. Cleanup(DosBoxDir);
  591. halt(ExitCode);
  592. end.