process.inc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. {
  2. This file is part of the Free Component Library (FCL)
  3. Copyright (c) 1999-2008 by the Free Pascal development team
  4. See the file COPYING.FPC, included in this distribution,
  5. for details about the copyright.
  6. This program is distributed in the hope that it will be useful,
  7. but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  9. **********************************************************************}
  10. {$DEFINE OS_HASEXITCODE}
  11. uses
  12. ctypes,
  13. UnixType,
  14. Unix,
  15. Baseunix;
  16. Resourcestring
  17. SNoCommandLine = 'Cannot execute empty command-line';
  18. SErrNoSuchProgram = 'Executable not found: "%s"';
  19. SErrNoTerminalProgram = 'Could not detect X-Terminal program';
  20. SErrCannotFork = 'Failed to Fork process';
  21. SErrCannotCreatePipes = 'Failed to create pipes';
  22. Const
  23. // unused as of yet, I assume these are nice levels ?
  24. PriorityConstants : Array [TProcessPriority] of Integer =
  25. (-10,19,0,-20,5,-5); // was (20,20,0,-20) before introduction of last two levels.
  26. Const
  27. GeometryOption : String = '-geometry';
  28. TitleOption : String ='-title';
  29. procedure TProcess.CloseProcessHandles;
  30. begin
  31. // Do nothing. Win32 call.
  32. end;
  33. Function TProcess.GetExitCode : Integer;
  34. begin
  35. if (Not Running) and wifexited(FExitCode) then
  36. Result:=wexitstatus(FExitCode)
  37. else
  38. Result:=0;
  39. end;
  40. Function TProcess.PeekExitStatus : Boolean;
  41. var
  42. res: cint;
  43. begin
  44. repeat
  45. res:=fpWaitPid(Handle,pcint(@FExitCode),WNOHANG);
  46. until (res<>-1) or (fpgeterrno<>ESysEINTR);
  47. result:=res=Handle;
  48. If Not Result then
  49. FexitCode:=cardinal(-1); // was 0, better testable for abnormal exit.
  50. end;
  51. Type
  52. TPCharArray = Array[Word] of pchar;
  53. PPCharArray = ^TPcharArray;
  54. Function StringsToPCharList(List : TStrings) : PPChar;
  55. Var
  56. I : Integer;
  57. S : String;
  58. begin
  59. I:=(List.Count)+1;
  60. GetMem(Result,I*sizeOf(PChar));
  61. PPCharArray(Result)^[List.Count]:=Nil;
  62. For I:=0 to List.Count-1 do
  63. begin
  64. S:=List[i];
  65. Result[i]:=StrNew(PChar(S));
  66. end;
  67. end;
  68. Procedure FreePCharList(List : PPChar);
  69. Var
  70. I : integer;
  71. begin
  72. I:=0;
  73. While List[i]<>Nil do
  74. begin
  75. StrDispose(List[i]);
  76. Inc(I);
  77. end;
  78. FreeMem(List);
  79. end;
  80. Function DetectXterm : String;
  81. Function TestTerminal(S : String) : Boolean;
  82. begin
  83. Result:=FileSearch(s,GetEnvironmentVariable('PATH'),False)<>'';
  84. If Result then
  85. XTermProgram:=S;
  86. end;
  87. Function TestTerminals(Terminals : Array of String) : Boolean;
  88. Var
  89. I : integer;
  90. begin
  91. I:=Low(Terminals);
  92. Result:=False;
  93. While (Not Result) and (I<=High(Terminals)) do
  94. begin
  95. Result:=TestTerminal(Terminals[i]);
  96. inc(i);
  97. end;
  98. end;
  99. Const
  100. Konsole = 'konsole';
  101. GNomeTerm = 'gnome-terminal';
  102. DefaultTerminals : Array [1..6] of string
  103. = ('x-terminal-emulator','xterm','aterm','wterm','rxvt','xfce4-terminal');
  104. Var
  105. D :String;
  106. begin
  107. If (XTermProgram='') then
  108. begin
  109. // try predefined
  110. If Length(TryTerminals)>0 then
  111. TestTerminals(TryTerminals);
  112. // try session-specific terminal
  113. if (XTermProgram='') then
  114. begin
  115. D:=LowerCase(GetEnvironmentVariable('DESKTOP_SESSION'));
  116. If (Pos('kde',D)<>0) then
  117. begin
  118. TestTerminal('konsole');
  119. end
  120. else if (D='gnome') then
  121. begin
  122. TestTerminal('gnome-terminal');
  123. end
  124. else if (D='windowmaker') then
  125. begin
  126. If not TestTerminal('aterm') then
  127. TestTerminal('wterm');
  128. end
  129. else if (D='xfce') then
  130. TestTerminal('xfce4-terminal');
  131. end;
  132. if (XTermProgram='') then
  133. TestTerminals(DefaultTerminals)
  134. end;
  135. Result:=XTermProgram;
  136. If (Result='') then
  137. Raise EProcess.Create(SErrNoTerminalProgram);
  138. end;
  139. Function MakeCommand(P : TProcess) : PPchar;
  140. {$ifdef darwin}
  141. Const
  142. TerminalApp = 'open';
  143. {$endif}
  144. {$ifdef haiku}
  145. Const
  146. TerminalApp = 'Terminal';
  147. {$endif}
  148. Var
  149. Cmd : String;
  150. S : TStringList;
  151. G : String;
  152. begin
  153. If (P.ApplicationName='') and (P.CommandLine='') and (P.Executable='') then
  154. Raise EProcess.Create(SNoCommandline);
  155. S:=TStringList.Create;
  156. try
  157. if (P.ApplicationName='') and (P.CommandLine='') then
  158. begin
  159. S.Assign(P.Parameters);
  160. S.Insert(0,P.Executable);
  161. end
  162. else
  163. begin
  164. If (P.CommandLine='') then
  165. Cmd:=P.ApplicationName
  166. else
  167. Cmd:=P.CommandLine;
  168. CommandToList(Cmd,S);
  169. end;
  170. if poNewConsole in P.Options then
  171. begin
  172. {$ifdef haiku}
  173. If (P.ApplicationName<>'') then
  174. begin
  175. S.Insert(0,P.ApplicationName);
  176. S.Insert(0,'--title');
  177. end;
  178. {$endif}
  179. {$if defined(darwin) or defined(haiku)}
  180. S.Insert(0,TerminalApp);
  181. {$else}
  182. S.Insert(0,'-e');
  183. If (P.ApplicationName<>'') then
  184. begin
  185. S.Insert(0,P.ApplicationName);
  186. S.Insert(0,'-title');
  187. end;
  188. if suoUseCountChars in P.StartupOptions then
  189. begin
  190. S.Insert(0,Format('%dx%d',[P.dwXCountChars,P.dwYCountChars]));
  191. S.Insert(0,'-geometry');
  192. end;
  193. If (P.XTermProgram<>'') then
  194. S.Insert(0,P.XTermProgram)
  195. else
  196. S.Insert(0,DetectXterm);
  197. {$endif}
  198. end;
  199. {$ifndef haiku}
  200. if (P.ApplicationName<>'') then
  201. begin
  202. S.Add(TitleOption);
  203. S.Add(P.ApplicationName);
  204. end;
  205. G:='';
  206. if (suoUseSize in P.StartupOptions) then
  207. g:=format('%dx%d',[P.dwXSize,P.dwYsize]);
  208. if (suoUsePosition in P.StartupOptions) then
  209. g:=g+Format('+%d+%d',[P.dwX,P.dwY]);
  210. if G<>'' then
  211. begin
  212. S.Add(GeometryOption);
  213. S.Add(g);
  214. end;
  215. {$endif}
  216. Result:=StringsToPcharList(S);
  217. Finally
  218. S.free;
  219. end;
  220. end;
  221. Function GetLastError : Integer;
  222. begin
  223. Result:=-1;
  224. end;
  225. Type
  226. TPipeEnd = (peRead,peWrite);
  227. TPipePair = Array[TPipeEnd] of cint;
  228. Procedure CreatePipes(Var HI,HO,HE : TPipePair; CI, CE : Boolean);
  229. Procedure CreatePair(Var P : TPipePair);
  230. begin
  231. If not CreatePipeHandles(P[peRead],P[peWrite]) then
  232. Raise EProcess.Create(SErrCannotCreatePipes);
  233. end;
  234. Procedure ClosePair(Var P : TPipePair);
  235. begin
  236. if (P[peRead]<>-1) then
  237. FileClose(P[peRead]);
  238. if (P[peWrite]<>-1) then
  239. FileClose(P[peWrite]);
  240. end;
  241. begin
  242. HO[peRead]:=-1;HO[peWrite]:=-1;
  243. HI[peRead]:=-1;HI[peWrite]:=-1;
  244. HE[peRead]:=-1;HE[peWrite]:=-1;
  245. Try
  246. CreatePair(HO);
  247. if CI then
  248. CreatePair(HI);
  249. If CE then
  250. CreatePair(HE);
  251. except
  252. ClosePair(HO);
  253. ClosePair(HI);
  254. If CE then
  255. ClosePair(HE);
  256. Raise;
  257. end;
  258. end;
  259. Function safefpdup2(fildes, fildes2 : cInt): cInt;
  260. begin
  261. repeat
  262. safefpdup2:=fpdup2(fildes,fildes2);
  263. until (safefpdup2<>-1) or (fpgeterrno<>ESysEINTR);
  264. end;
  265. Procedure TProcess.Execute;
  266. Var
  267. HI,HO,HE : TPipePair;
  268. PID : Longint;
  269. FEnv : PPChar;
  270. Argv : PPChar;
  271. fd : Integer;
  272. res : cint;
  273. FoundName,
  274. PName : String;
  275. begin
  276. If (poUsePipes in Options) then
  277. CreatePipes(HI, HO, HE, Not(poPassInput in Options), Not (poStdErrToOutPut in Options));
  278. Try
  279. if FEnvironment.Count<>0 then
  280. FEnv:=StringsToPcharList(FEnvironment)
  281. else
  282. FEnv:=Nil;
  283. Try
  284. Argv:=MakeCommand(Self);
  285. Try
  286. If (Argv<>Nil) and (ArgV[0]<>Nil) then
  287. PName:=StrPas(Argv[0])
  288. else
  289. begin
  290. // This should never happen, actually.
  291. PName:=ApplicationName;
  292. If (PName='') then
  293. PName:=CommandLine;
  294. end;
  295. if not FileExists(PName) then begin
  296. FoundName := ExeSearch(Pname,fpgetenv('PATH'));
  297. if FoundName<>'' then
  298. PName:=FoundName
  299. else
  300. raise EProcess.CreateFmt(SErrNoSuchProgram,[PName]);
  301. end;
  302. {$if (defined(DARWIN) or defined(SUNOS))}
  303. { can't use vfork in case the child has to be
  304. suspended immediately, because with vfork the
  305. child borrows the execution thread of the parent
  306. unit it either exits or execs -> potential
  307. deadlock depending on how quickly the SIGSTOP
  308. signal is delivered
  309. We also can't use vfork in case we have to change the working
  310. directory, use pipes or not use a console since calling anything but
  311. exec* or _exit after vfork is unsupported. For the same reason, also
  312. don't use vfork in case there is a forkevent (since we don't know
  313. what that one will call) }
  314. if (([poRunSuspended,PoUsePipes,poNoConsole] * Options) = []) and
  315. (FCurrentDirectory='') and
  316. not assigned(FForkEvent) then
  317. Pid:=fpvfork
  318. else
  319. Pid:=fpfork;
  320. {$else}
  321. Pid:=fpfork;
  322. {$endif}
  323. if Pid<0 then
  324. Raise EProcess.Create(SErrCannotFork);
  325. if (PID>0) then
  326. begin
  327. // Parent process. Copy process information.
  328. FProcessHandle:=PID;
  329. FThreadHandle:=PID;
  330. FProcessId:=PID;
  331. //FThreadId:=PID;
  332. end
  333. else
  334. begin
  335. { We're in the child }
  336. if (FCurrentDirectory<>'') then
  337. begin
  338. {$push}{$i-}
  339. ChDir(FCurrentDirectory);
  340. { exit if the requested working directory does not exist (can
  341. use DirectoryExists, that would not be atomic; cannot change
  342. before forking because that would also change the CWD of the
  343. parent, which could influence other threads }
  344. if ioresult<>0 then
  345. fpexit(127);
  346. {$pop}
  347. end;
  348. if PoUsePipes in Options then
  349. begin
  350. if not (poPassInput in Options) then
  351. begin
  352. FileClose(HI[peWrite]);
  353. safefpdup2(HI[peRead],0);
  354. end;
  355. FileClose(HO[peRead]);
  356. safefpdup2(HO[peWrite],1);
  357. if (poStdErrToOutPut in Options) then
  358. safefpdup2(HO[peWrite],2)
  359. else
  360. begin
  361. FileClose(HE[peRead]);
  362. safefpdup2(HE[peWrite],2);
  363. end
  364. end
  365. else if poNoConsole in Options then
  366. begin
  367. fd:=FileOpen('/dev/null',fmOpenReadWrite or fmShareDenyNone);
  368. safefpdup2(fd,0);
  369. safefpdup2(fd,1);
  370. safefpdup2(fd,2);
  371. end;
  372. if Assigned(FForkEvent) then
  373. FForkEvent(Self);
  374. if (poRunSuspended in Options) then
  375. fpkill(fpgetpid,SIGSTOP);
  376. if FEnv<>Nil then
  377. fpexecve(PName,Argv,Fenv)
  378. else
  379. fpexecv(PName,argv);
  380. fpExit(127);
  381. end
  382. Finally
  383. FreePcharList(Argv);
  384. end;
  385. Finally
  386. If (FEnv<>Nil) then
  387. FreePCharList(FEnv);
  388. end;
  389. Finally
  390. if POUsePipes in Options then
  391. begin
  392. FileClose(HO[peWrite]);
  393. if not (POPassInput in Options) then
  394. FileClose(HI[peRead]);
  395. if Not (poStdErrToOutPut in FProcessOptions) then
  396. FileClose(HE[peWrite]);
  397. CreateStreams(HI[peWrite],HO[peRead],HE[peRead]);
  398. end;
  399. end;
  400. FRunning:=True;
  401. if not (csDesigning in ComponentState) and // This would hang the IDE !
  402. (poWaitOnExit in FProcessOptions) and
  403. not (poRunSuspended in FProcessOptions) then
  404. WaitOnExit;
  405. end;
  406. Function TProcess.WaitOnExit : Boolean;
  407. Var
  408. R : Dword;
  409. begin
  410. if FRunning then
  411. fexitcode:=waitprocess(handle);
  412. Result:=(fexitcode>=0);
  413. FRunning:=False;
  414. end;
  415. { maybe some unixes might need a simpler solution }
  416. {$define USE_GETTIMEOFDAY}
  417. Function TProcess.WaitOnExit(Timeout : DWord) : Boolean;
  418. var
  419. res: cint;
  420. {$ifdef USE_GETTIMEOFDAY}
  421. timeout_tv,t : timeval;
  422. tz : timezone;
  423. {$endif USE_GETTIMEOFDAY}
  424. begin
  425. Result:=false;
  426. {$ifdef USE_GETTIMEOFDAY}
  427. fpGetTimeOfDay(@timeout_tv,@tz);
  428. inc(timeout_tv.tv_sec,Timeout div 1000);
  429. inc(timeout_tv.tv_usec,(Timeout mod 1000)*1000);
  430. {$endif USE_GETTIMEOFDAY}
  431. res:=fpWaitPid(Handle,pcint(@FExitCode),WNOHANG);
  432. while res=0 do
  433. begin
  434. {$ifdef USE_GETTIMEOFDAY}
  435. fpGetTimeOfDay(@t,@tz);
  436. if (t.tv_sec>timeout_tv.tv_sec) or
  437. ((t.tv_sec=timeout_tv.tv_sec) and (t.tv_usec>timeout_tv.tv_usec)) then
  438. exit;
  439. {$else USE_GETTIMEOFDAY}
  440. if Timeout=0 then
  441. Exit;
  442. {$endif USE_GETTIMEOFDAY}
  443. Sleep(1);
  444. dec(Timeout);
  445. res:=fpWaitPid(Handle,pcint(@FExitCode),WNOHANG);
  446. end;
  447. result:=res=Handle;
  448. If Not Result then
  449. FexitCode:=cardinal(-1) // was 0, better testable for abnormal exit.
  450. else
  451. FRunning:=False;
  452. end;
  453. Function TProcess.Suspend : Longint;
  454. begin
  455. If fpkill(Handle,SIGSTOP)<>0 then
  456. Result:=-1
  457. else
  458. Result:=1;
  459. end;
  460. Function TProcess.Resume : LongInt;
  461. begin
  462. If fpKill(Handle,SIGCONT)<>0 then
  463. Result:=-1
  464. else
  465. Result:=0;
  466. end;
  467. Function TProcess.Terminate(AExitCode : Integer) : Boolean;
  468. begin
  469. Result:=False;
  470. Result:=fpkill(Handle,SIGTERM)=0;
  471. If Result then
  472. begin
  473. If Running then
  474. Result:=fpkill(Handle,SIGKILL)=0;
  475. end;
  476. { the fact that the signal has been sent does not
  477. mean that the process has already handled the
  478. signal -> wait instead of calling getexitstatus }
  479. if Result then
  480. WaitOnExit;
  481. end;
  482. Procedure TProcess.SetShowWindow (Value : TShowWindowOptions);
  483. begin
  484. FShowWindow:=Value;
  485. end;