dosbox_wrapper.pas 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. {$MODE objfpc}{$H+}
  2. uses
  3. SysUtils, StrUtils,
  4. {$ifdef UseSignals}
  5. signals,
  6. {$endif def UseSignals}
  7. Process;
  8. const
  9. use_temp_dir : boolean = true;
  10. need_cwsdpmi : boolean = false;
  11. cwsdpmi_file : string = '';
  12. hide_execution : boolean = true;
  13. do_exit : boolean = true;
  14. verbose : boolean = false;
  15. DosBoxProcess: TProcess = nil;
  16. dosbox_timeout : integer = 100; { default timeout in seconds }
  17. var
  18. OutputFileName : String;
  19. DosBoxDir: string;
  20. ExitCode: Integer = 255;
  21. DosBoxBinaryPath: string;
  22. function GenerateTempDir: string;
  23. var
  24. FileName: string;
  25. TempDir: string;
  26. Done: Boolean = False;
  27. begin
  28. TempDir := GetTempDir(False);
  29. repeat
  30. try
  31. FileName := TempDir + 'dosboxwrappertmp_' + IntToStr(Random(100000));
  32. if verbose then
  33. writeln('Trying to create directory ',Filename);
  34. MkDir(FileName);
  35. Done := True;
  36. except
  37. on E: EInOutError do
  38. begin
  39. { 5 = Access Denied, returned when a file is duplicated }
  40. if E.ErrorCode <> 5 then
  41. begin
  42. Writeln('Directory creation failed');
  43. raise;
  44. end;
  45. end;
  46. end;
  47. until Done;
  48. Result := FileName + DirectorySeparator;
  49. end;
  50. procedure GenerateDosBoxConf(const ADosBoxDir: string);
  51. var
  52. SourceConfFileName, TargetConfFileName: string;
  53. SourceFile, TargetFile: TextFile;
  54. OrigS, S: string;
  55. begin
  56. SourceConfFileName := ExtractFilePath(ParamStr(0)) + 'dosbox.conf';
  57. TargetConfFileName := ADosBoxDir + 'dosbox.conf';
  58. OutputFileName := ADosBoxDir + 'dosbox.out';
  59. if verbose then
  60. Writeln('Using target dosbox.conf ',TargetConfFileName);
  61. AssignFile(SourceFile, SourceConfFileName);
  62. AssignFile(TargetFile, TargetConfFileName);
  63. Reset(SourceFile);
  64. try
  65. Rewrite(TargetFile);
  66. try
  67. while not EoF(SourceFile) do
  68. begin
  69. Readln(SourceFile, S);
  70. OrigS:=S;
  71. S := AnsiReplaceStr(S, '$DosBoxDir', ADosBoxDir);
  72. S := AnsiReplaceStr(S, '$wrapper_output', OutputFileName);
  73. if do_exit then
  74. S := AnsiReplaceStr(S, '$exit', 'exit')
  75. else
  76. S := AnsiReplaceStr(S, '$exit', '');
  77. If verbose and (OrigS <> S) then
  78. Writeln('"',OrigS,'" transformed into "',S,'"');
  79. Writeln(TargetFile, S);
  80. end;
  81. finally
  82. CloseFile(TargetFile);
  83. end;
  84. finally
  85. CloseFile(SourceFile);
  86. end;
  87. end;
  88. procedure CopyFile(ASrcFileName, ADestFileName: string);
  89. var
  90. SrcF, DestF: File;
  91. OldFileMode: Integer;
  92. Buf: array [0..4095] of Byte;
  93. BytesRead: Integer;
  94. begin
  95. if verbose then
  96. Writeln('CopyFile ', ASrcFileName, '->', ADestFileName);
  97. if not AnsiEndsText('.exe', ASrcFileName) then
  98. ASrcFileName := ASrcFileName + '.exe';
  99. OldFileMode := FileMode;
  100. try
  101. AssignFile(SrcF, ASrcFileName);
  102. AssignFile(DestF, ADestFileName);
  103. FileMode := fmOpenRead;
  104. Reset(SrcF, 1);
  105. try
  106. FileMode := fmOpenWrite;
  107. try
  108. Rewrite(DestF, 1);
  109. repeat
  110. BlockRead(SrcF, Buf, SizeOf(Buf), BytesRead);
  111. BlockWrite(DestF, Buf, BytesRead);
  112. until BytesRead < SizeOf(Buf);
  113. finally
  114. CloseFile(DestF);
  115. end;
  116. finally
  117. CloseFile(SrcF);
  118. end;
  119. finally
  120. FileMode := OldFileMode;
  121. end;
  122. end;
  123. { On modified dosbox executable it is possible to get
  124. a copy of all output to CON into a file, simply write it
  125. back to output, so it ends up into testname.elg file.
  126. Skip all until line beginning with 'Drive C is mounted as' }
  127. procedure EchoOutput;
  128. const
  129. SkipUntilText = 'Drive C is mounted as ';
  130. var
  131. StdText : TextFile;
  132. st : string;
  133. line : longint;
  134. SkipUntilSeen : boolean;
  135. begin
  136. if FileExists(OutputFileName) then
  137. begin
  138. if verbose then
  139. Writeln('Trying to open ',OutputFileName);
  140. try
  141. AssignFile(StdText, OutputFileName);
  142. Reset(StdText);
  143. if verbose then
  144. Writeln('Successfully opened ',OutputFileName,', copying content to output');
  145. try
  146. line:=0;
  147. SkipUntilSeen:=false;
  148. while not eof(StdText) do
  149. begin
  150. Readln(StdText,st);
  151. inc(line);
  152. if not SkipUntilSeen then
  153. SkipUntilSeen:=pos(SkipUntilText,st)>0;
  154. if SkipUntilSeen then
  155. Writeln(line,': ',st);
  156. end;
  157. finally
  158. if not SkipUntilSeen then
  159. Writeln('Could not find "',SkipUntilText,'" in file ',OutputFilename);
  160. Flush(output);
  161. CloseFile(StdText);
  162. end;
  163. finally
  164. if use_temp_dir then
  165. DeleteFile(OutputFileName);
  166. end;
  167. end;
  168. end;
  169. function ReadExitCode(const ADosBoxDir: string): Integer;
  170. var
  171. F: TextFile;
  172. begin
  173. AssignFile(F, ADosBoxDir + 'EXITCODE.TXT');
  174. try
  175. Reset(F);
  176. Readln(F, Result);
  177. if Result <> 0 then
  178. Writeln('ExitCode=',Result);
  179. CloseFile(F);
  180. except
  181. Writeln('Unable to read exitcode value');
  182. ReadExitCode:=127*256;
  183. end;
  184. end;
  185. procedure ExecuteDosBox(const ADosBoxBinaryPath, ADosBoxDir: string);
  186. var
  187. Time: Integer = 0;
  188. begin
  189. DosBoxProcess := TProcess.Create(nil);
  190. try
  191. DosBoxProcess.Executable := ADosBoxBinaryPath;
  192. DosBoxProcess.Parameters.Add('-conf');
  193. DosBoxProcess.Parameters.Add(ADosBoxDir + 'dosbox.conf');
  194. if hide_execution then
  195. DosBoxProcess.ShowWindow := swoHIDE;
  196. DosBoxProcess.Execute;
  197. repeat
  198. Inc(Time);
  199. if (Time > 10*dosbox_timeout) and do_exit then
  200. break;
  201. Sleep(100);
  202. until not DosBoxProcess.Running;
  203. if DosBoxProcess.Running then
  204. begin
  205. Writeln('Timeout exceeded. Killing dosbox...');
  206. DosBoxProcess.Terminate(254);
  207. Sleep(100);
  208. end;
  209. finally
  210. DosBoxProcess.Free;
  211. DosBoxProcess:=nil;
  212. EchoOutput;
  213. end;
  214. end;
  215. procedure DeleteIfExists(const AFileName: string);
  216. begin
  217. if FileExists(AFileName) then
  218. DeleteFile(AFileName);
  219. end;
  220. procedure Cleanup(const ADosBoxDir: string);
  221. begin
  222. DeleteIfExists(ADosBoxDir + 'dosbox.conf');
  223. DeleteIfExists(ADosBoxDir + 'EXITCODE.TXT');
  224. DeleteIfExists(ADosBoxDir + 'EXITCODE.EXE');
  225. DeleteIfExists(ADosBoxDir + 'CWSDPMI.EXE');
  226. DeleteIfExists(ADosBoxDir + 'TEST.EXE');
  227. RmDir(ADosBoxDir);
  228. end;
  229. {$ifdef UseSignals}
  230. const
  231. SignalCalled : boolean = false;
  232. SignalNb : longint = 0;
  233. function DosBoxSignal(signal:longint):longint; cdecl;
  234. begin
  235. SignalCalled:=true;
  236. SignalNb:=signal;
  237. end;
  238. {$endif def UseSignals}
  239. procedure ExitProc;
  240. var
  241. count : longint;
  242. begin
  243. if assigned(DosBoxProcess) and (DosBoxProcess.Running) then
  244. begin
  245. Writeln('In ExitProc. Killing dosbox...');
  246. DosBoxProcess.Terminate(254*1024);
  247. Sleep(100);
  248. count:=1;
  249. while (DosBoxProcess.Running) do
  250. begin
  251. Sleep(100);
  252. inc(count);
  253. if (count mod 20=0) then
  254. DosBoxProcess.Terminate(254*1024+count);
  255. end;
  256. if count>1 then
  257. Writeln('In ExitProc. Wait for termination dosbox..., time=',count/10);
  258. EchoOutput;
  259. end;
  260. end;
  261. begin
  262. Randomize;
  263. if GetEnvironmentVariable('DOSBOX_NO_TEMPDIR')<>'' then
  264. begin
  265. use_temp_dir:=false;
  266. Writeln('use_temp_dir set to false');
  267. end;
  268. if GetEnvironmentVariable('DOSBOX_NO_HIDE')<>'' then
  269. begin
  270. hide_execution:=false;
  271. Writeln('hide_execution set to false');
  272. end;
  273. if GetEnvironmentVariable('DOSBOX_NO_EXIT')<>'' then
  274. begin
  275. do_exit:=false;
  276. Writeln('do_exit set to false');
  277. end;
  278. if GetEnvironmentVariable('DOSBOX_VERBOSE')<>'' then
  279. begin
  280. verbose:=true;
  281. Writeln('verbose set to true');
  282. end;
  283. if (GetEnvironmentVariable('DOSBOX_NEEDS_CWSDPMI')<>'') or
  284. (GetEnvironmentVariable('TEST_OS_TARGET')='go32v2') then
  285. begin
  286. need_cwsdpmi:=true;
  287. Writeln('need_cwsdpmi set to true');
  288. end;
  289. if GetEnvironmentVariable('DOSBOX_TIMEOUT')<>'' then
  290. begin
  291. dosbox_timeout:=StrToInt(GetEnvironmentVariable('DOSBOX_TIMEOUT'));
  292. Writeln('dosbox_timeout set to ', dosbox_timeout, ' seconds');
  293. end;
  294. if ParamCount = 0 then
  295. begin
  296. Writeln('Usage: ' + ParamStr(0) + ' <executable>');
  297. Writeln('Set DOSBOX_NO_TEMPDIR env variable to 1 to avoid using a temporary directory');
  298. Writeln('Set DOSBOX_NO_HIDE to avoid running dosbox in an hidden window');
  299. Writeln('Set DOSBOX_NO_EXIT to avoid exiting dosbox after test has been run');
  300. Writeln('Set DOSBOX_TIMEOUT to set the timeout in seconds before killing the dosbox process, assuming the test has hanged');
  301. halt(1);
  302. end;
  303. DosBoxBinaryPath := GetEnvironmentVariable('DOSBOX');
  304. if DosBoxBinaryPath = '' then
  305. begin
  306. Writeln('Please set the DOSBOX environment variable to the dosbox executable');
  307. halt(1);
  308. end
  309. else
  310. begin
  311. Writeln('Using DOSBOX executable: ',DosBoxBinaryPath);
  312. end;
  313. { DosBoxDir is used inside dosbox.conf as a MOUNT parameter }
  314. if use_temp_dir then
  315. DosBoxDir := GenerateTempDir
  316. else
  317. begin
  318. Writeln('Using ',ParamStr(1));
  319. DosBoxDir:=ExtractFilePath(ParamStr(1));
  320. if DosBoxDir='' then
  321. DosBoxDir:=GetCurrentDir+DirectorySeparator;
  322. Writeln('Using DosBoxDir=',DosBoxDir);
  323. { Get rid of previous exicode.txt file }
  324. DeleteIfExists(DosBoxDir + 'EXITCODE.TXT');
  325. end;
  326. try
  327. {$ifdef UseSignals}
  328. Signal(SIGINT,@DosBoxSignal);
  329. Signal(SIGQUIT,@DosBoxSignal);
  330. Signal(SIGTERM,@DosBoxSignal);
  331. {$endif def UseSignals}
  332. GenerateDosBoxConf(DosBoxDir);
  333. CopyFile(ExtractFilePath(ParamStr(0)) + 'exitcode.exe', DosBoxDir + 'EXITCODE.EXE');
  334. CopyFile(ParamStr(1), DosBoxDir + 'TEST.EXE');
  335. if need_cwsdpmi then
  336. begin
  337. cwsdpmi_file:=FileSearch('cwsdpmi.exe',GetEnvironmentVariable('PATH'));
  338. if cwsdpmi_file<>'' then
  339. CopyFile(cwsdpmi_file, DosBoxDir + 'CWSDPMI.EXE')
  340. else if verbose then
  341. writeln('cwsdpmi executable missing');
  342. end;
  343. ExecuteDosBox(DosBoxBinaryPath, DosBoxDir);
  344. finally
  345. ExitProc;
  346. end;
  347. {$ifdef UseSignals}
  348. if SignalCalled then
  349. begin
  350. Writeln('Signal ',SignalNb,' called');
  351. end;
  352. {$endif def UseSignals}
  353. ExitProc;
  354. ExitCode:=ReadExitCode(DosBoxDir);
  355. if use_temp_dir then
  356. Cleanup(DosBoxDir);
  357. halt(ExitCode);
  358. end.