FIX: Execute files under Unix

This commit is contained in:
Alexander Koblov 2015-06-28 08:19:42 +00:00
commit da13e89f38
2 changed files with 150 additions and 102 deletions

View file

@ -84,7 +84,23 @@ type
function NtfsHourTimeDelay(const SourceName, TargetName: UTF8String): Boolean;
function FileIsLinkToFolder(const FileName: UTF8String; out LinkTarget: UTF8String): Boolean;
function ExecCmdFork(sCmd:String; sParams:string=''; sStartPath:String=''; bShowCommandLinePriorToExecute:boolean=False; bTerm:Boolean=False; bKeepTerminalOpen:tTerminalEndindMode=termStayOpen):Boolean;
{en
Execute command line
}
function ExecCmdFork(sCmd: String): Boolean;
{en
Execute external commands
@param(sCmd The executable or command itself)
@param(sParams The optional paramters)
@param(sStartPath The initial working directory)
@param(bShowCommandLinePriorToExecute Flag indicating if we want the user to be prompted at the very last
seconds prior to launch execution by offering a dialog window where
he can adjust/confirm the three above parameters.)
@param(bTerm Flag indicating if it should be launch through terminal)
@param(bKeepTerminalOpen Value indicating the type of terminal to use (closed at the end, remain opened, etc.))
}
function ExecCmdFork(sCmd: String; sParams: String; sStartPath: String = ''; bShowCommandLinePriorToExecute: Boolean = False;
bTerm: Boolean = False; bKeepTerminalOpen: tTerminalEndindMode = termStayOpen): Boolean;
{en
Opens a file or URL in the user's preferred application
@param(URL File name or URL)
@ -223,44 +239,6 @@ uses
{$ENDIF}
;
{$IFDEF UNIX}
type
{en
Waits for a child process to finish and collects its exit status,
causing it to be released by the system (prevents defunct processes).
Instead of the wait-thread we could just ignore or handle SIGCHLD signal
for the process, but this way we don't interfere with the signal handling.
The downside is that there's a thread for every child process running.
Another method is to periodically do a cleanup, for example from OnIdle
or OnTimer event. Remember PIDs of spawned child processes and when
cleaning call FpWaitpid(PID, nil, WNOHANG) on each PID. Downside is they
are not released immediately after the child process finish (may be relevant
if we want to display exit status to the user).
}
TWaitForPidThread = class(TThread)
private
FPID: TPid;
protected
procedure Execute; override;
public
constructor Create(WaitForPid: TPid); overload;
end;
constructor TWaitForPidThread.Create(WaitForPid: TPid);
begin
inherited Create(True);
FPID := WaitForPid;
FreeOnTerminate := True;
end;
procedure TWaitForPidThread.Execute;
begin
while (FpWaitPid(FPID, nil, 0) = -1) and (fpgeterrno() = ESysEINTR) do;
end;
{$ENDIF}
function FileIsLinkToFolder(const FileName: UTF8String; out
LinkTarget: UTF8String): Boolean;
{$IF DEFINED(MSWINDOWS)}
@ -277,23 +255,10 @@ begin
end;
{$ENDIF}
(* Execute external commands *)
// Description of paramters:
// sCmd : The executable or command itself
// sParams : The optional paramters
// sStartPath : The initial working directory
// bShowCommandLinePriorToExecute : Flag indicating if we want the user to be prompted at the very last
// seconds prior to launch execution by offering a dialog window where
// he can adjust/confirm the three above parameters.
// bTerm : Flag indicating if it should be launch through terminal
// bKeepTerminalOpen : Value indicating the type of terminal to use (closed at the end, remain opened, etc.)
//
function ExecCmdFork(sCmd, sParams, sStartPath:String; bShowCommandLinePriorToExecute, bTerm : Boolean; bKeepTerminalOpen: tTerminalEndindMode) : Boolean;
{$IFDEF UNIX}
var
pid : LongInt;
Args : TDynamicStringArray;
WaitForPidThread: TWaitForPidThread;
bFlagKeepGoing: boolean = True;
begin
result:=False;
@ -313,50 +278,15 @@ begin
sCmd := RemoveQuotation(UTF8ToSys(sCmd));
SplitCommandArgs(UTF8ToSys(sParams), Args);
{$IFDEF DARWIN}
// If we run application bundle (*.app) then
// execute it by 'open -a' command (see 'man open' for details)
if StrEnds(sCmd, '.app') then
Result := ExecuteCommand(sCmd, Args, sStartPath);
if (log_commandlineexecution in gLogOptions) then
begin
SetLength(Args, Length(Args) + 2);
for pid := High(Args) downto Low(Args) + 2 do
Args[pid]:= Args[pid - 2];
Args[0] := '-a';
Args[1] := sCmd;
sCmd := 'open';
if Result then
logWrite(rsMsgLogExtCmdResult+': '+rsSimpleWordResult+'='+'Success!'+' / '+rsSimpleWordFilename+'='+sCmd+' / '+rsSimpleWordParameter+'='+sParams+' / '+rsSimpleWordWorkDir+'='+sStartPath)
else
logWrite(rsMsgLogExtCmdResult+': '+rsSimpleWordResult+'='+'Failed!'+' / '+rsSimpleWordFilename+'='+sCmd+' / '+rsSimpleWordParameter+'='+sParams+' / '+rsSimpleWordWorkDir+'='+sStartPath);
end;
{$ENDIF}
pid := fpFork;
if pid = 0 then
begin
{ Set child current directory }
if Length(sStartPath) > 0 then
mbSetCurrentDir(sStartPath);
{ The child does the actual exec, and then exits }
if FpExecLP(sCmd, Args) = -1 then
Writeln(Format('Execute error %d: %s', [fpgeterrno, SysErrorMessageUTF8(fpgeterrno)]));
{ If the FpExecLP fails, we return an exitvalue of 127, to let it be known }
fpExit(127);
end
else if pid = -1 then { Fork failed }
begin
raise Exception.Create('Fork failed: ' + sCmd);
end
else if pid > 0 then { Parent }
begin
WaitForPidThread := TWaitForPidThread.Create(pid);
WaitForPidThread.Start;
end;
Result := (pid > 0);
if Result and (log_commandlineexecution in gLogOptions) then
logWrite(rsMsgLogExtCmdResult+': '+rsSimpleWordResult+'='+'Success!'+' / '+rsSimpleWordFilename+'='+sCmd+' / '+rsSimpleWordParameter+'='+sParams+' / '+rsSimpleWordWorkDir+'='+sStartPath)
else
logWrite(rsMsgLogExtCmdResult+': '+rsSimpleWordResult+'='+'Failed!'+' / '+rsSimpleWordFilename+'='+sCmd+' / '+rsSimpleWordParameter+'='+sParams+' / '+rsSimpleWordWorkDir+'='+sStartPath);
end
else
begin
@ -415,6 +345,51 @@ begin
end;
{$ENDIF}
function ExecCmdFork(sCmd: String): Boolean;
{$IFDEF UNIX}
var
Command: String;
Args : TDynamicStringArray;
begin
SplitCmdLine(sCmd, Command, Args);
if (log_commandlineexecution in gLogOptions) then logWrite(rsMsgLogExtCmdLaunch + ': ' + rsSimpleWordCommand + '=' + sCmd);
Result:= ExecuteCommand(Command, Args, EmptyStr);
if (log_commandlineexecution in gLogOptions) then
begin
if Result then
logWrite(rsMsgLogExtCmdResult + ': ' + rsSimpleWordResult + '=' + 'Success!' + ' / ' + rsSimpleWordCommand + '=' + sCmd)
else
logWrite(rsMsgLogExtCmdResult + ': ' + rsSimpleWordResult + '=' + 'Failed!' + ' / ' + rsSimpleWordCommand + '=' + sCmd);
end;
end;
{$ELSE}
var
sFileName,
sParams: String;
ExecutionResult: HINST;
begin
SplitCmdLine(sCmd, sFileName, sParams);
sFileName:= NormalizePathDelimiters(sFileName);
if (log_commandlineexecution in gLogOptions) then
logWrite(rsMsgLogExtCmdLaunch + ': ' + rsSimpleWordFilename + '=' + sCmd + ' / ' + rsSimpleWordParameter + '=' + sParams);
ExecutionResult := ShellExecuteW(0, nil, PWideChar(UTF8Decode(sFileName)), PWideChar(UTF8Decode(sParams)), nil, SW_SHOW);
if (log_commandlineexecution in gLogOptions) then
begin
logWrite(rsMsgLogExtCmdResult + ': ' + rsSimpleWordResult + '=' + IfThen((ExecutionResult > 32), 'Success!',
IntToStr(ExecutionResult) + ':' + SysErrorMessage(ExecutionResult)) +' / ' + rsSimpleWordFilename +
'=' + sCmd + ' / ' + rsSimpleWordParameter + '=' + sParams);
end;
Result := (ExecutionResult > 32);
end;
{$ENDIF}
function ShellExecute(URL: UTF8String): Boolean;
{$IF DEFINED(MSWINDOWS)}
var

View file

@ -31,7 +31,7 @@ unit uMyUnix;
interface
uses
Classes, SysUtils, BaseUnix, uDrive;
Classes, SysUtils, BaseUnix, DCBasicTypes, uDrive;
const
libc = 'c';
@ -205,6 +205,8 @@ function MountDrive(Drive: PDrive): Boolean;
function UnmountDrive(Drive: PDrive): Boolean;
function EjectDrive(Drive: PDrive): Boolean;
function ExecuteCommand(Command: String; Args: TDynamicStringArray; StartPath: String): Boolean;
{$IF DEFINED(BSD)}
const
MNT_WAIT = 1; // synchronously wait for I/O to complete
@ -240,7 +242,7 @@ var
implementation
uses
URIParser, Unix, FileUtil, DCClassesUtf8, DCStrUtils, uDCUtils, DCBasicTypes, uOSUtils
URIParser, Unix, FileUtil, DCOSUtils, DCClassesUtf8, DCStrUtils, uDCUtils, uOSUtils
{$IF (NOT DEFINED(FPC_USE_LIBC)) or (DEFINED(BSD) AND NOT DEFINED(DARWIN))}
, SysCall
{$ENDIF}
@ -579,14 +581,85 @@ begin
Result := fpSystemStatus('eject ' + Drive^.DeviceId) = 0;
end;
{$IF DEFINED(UNIX) AND NOT DEFINED(DARWIN)}
initialization
DesktopEnv := GetDesktopEnvironment;
{$IFDEF LINUX}
CheckPMount;
CheckUDisksCtl;
function ExecuteCommand(Command: String; Args: TDynamicStringArray; StartPath: String): Boolean;
var
pid : TPid;
begin
{$IFDEF DARWIN}
// If we run application bundle (*.app) then
// execute it by 'open -a' command (see 'man open' for details)
if StrEnds(Command, '.app') then
begin
SetLength(Args, Length(Args) + 2);
for pid := High(Args) downto Low(Args) + 2 do
Args[pid]:= Args[pid - 2];
Args[0] := '-a';
Args[1] := Command;
Command := 'open';
end;
{$ENDIF}
pid := fpFork;
if pid = 0 then
begin
{ Set child current directory }
if Length(StartPath) > 0 then fpChdir(StartPath);
{ The child does the actual exec, and then exits }
if FpExecLP(Command, Args) = -1 then
Writeln(Format('Execute error %d: %s', [fpgeterrno, SysErrorMessage(fpgeterrno)]));
{ If the FpExecLP fails, we return an exitvalue of 127, to let it be known }
fpExit(127);
end
else if pid = -1 then { Fork failed }
begin
raise Exception.Create('Fork failed: ' + Command);
end
else if pid > 0 then { Parent }
begin
// Success
end;
Result := (pid > 0);
end;
{
SIGCHLD handler
}
procedure handle_sigchld(signal: longint; info: psiginfo; context: psigcontext); cdecl;
var
Status : cint;
begin
while (fpWaitPid(-1, Status, WNOHANG) > 0) do;
end;
{
Reap zombie processes using a SIGCHLD handler
}
procedure RegisterHandler;
var
sa: sigactionrec;
begin
FillChar(sa, SizeOf(sa), #0);
sa.sa_handler := @handle_sigchld;
sa.sa_flags := SA_RESTART or SA_NOCLDSTOP;
if (fpSigAction(SIGCHLD, @sa, nil) = -1) then
begin
WriteLn(SysErrorMessage(GetLastOSError));
end;
end;
initialization
RegisterHandler;
{$IF NOT DEFINED(DARWIN)}
DesktopEnv := GetDesktopEnvironment;
{$IFDEF LINUX}
CheckPMount;
CheckUDisksCtl;
{$ENDIF}
{$ENDIF}
{$ENDIF}
end.