UNSAFE MODULE UnixRun; (***************************************************************************) (* Copyright (C) Olivetti 1989 *) (* All Rights reserved *) (* *) (* Use and copy of this software and preparation of derivative works based *) (* upon this software are permitted to any person, provided this same *) (* copyright notice and the following Olivetti warranty disclaimer are *) (* included in any copy of the software or any modification thereof or *) (* derivative work therefrom made by any person. *) (* *) (* This software is made available AS IS and Olivetti disclaims all *) (* warranties with respect to this software, whether expressed or implied *) (* under any law, including all implied warranties of merchantibility and *) (* fitness for any purpose. In no event shall Olivetti be liable for any *) (* damages whatsoever resulting from loss of use, data or profits or *) (* otherwise arising out of or in connection with the use or performance *) (* of this software. *) (***************************************************************************) IMPORT Thread, Fmt, EnvVar; IMPORT OSError, OSError_ux; IMPORT Ctypes, M3toC; IMPORT Unix, UnixMutex, Uexec, Uerror, UnixWait; TYPE ChildErrorClass = {Stopped, NonZeroExitCode, Terminated}; REVEAL ChildError = BRANDED REF RECORD class: ChildErrorClass; rc: INTEGER; END; PROCEDURE NewChildError( class: ChildErrorClass; rc: INTEGER) : ChildError RAISES {}= BEGIN RETURN NEW(ChildError, class := class, rc := rc); END NewChildError; PROCEDURE ChildErrorToText(c: ChildError): TEXT RAISES {}= VAR fmt: TEXT; BEGIN CASE c.class OF | ChildErrorClass.Stopped => fmt := "Child stopped by signal %s"; | ChildErrorClass.NonZeroExitCode => fmt := "Child exited with code %s"; | ChildErrorClass.Terminated => fmt := "Child terminated by signal %s"; END; RETURN Fmt.F(fmt, Fmt.Int(c.rc)); END ChildErrorToText; PROCEDURE RaiseOSError(error: INTEGER) RAISES {OSError.E}= VAR e := NEW(OSError.T); BEGIN e^ := error; RAISE OSError.E(e); END RaiseOSError; PROCEDURE Wait(): ChildError RAISES {OSError.E}= VAR status: INTEGER; BEGIN LOCK UnixMutex.errno DO IF Uexec.wait(ADR(status)) < 0 THEN OSError_ux.Raise() END; END; IF UnixWait.WIFSTOPPED(status) THEN RETURN NewChildError(ChildErrorClass.Stopped, UnixWait.Stopsig(status)); ELSE VAR exitCode := UnixWait.Retcode(status); terminationSignal := UnixWait.Termsig(status); BEGIN IF exitCode # 0 THEN IF exitCode > 127 THEN (* We assume it is negative; a negated 'errno' value set up after a failed 'exec' (see 'RawCommand') *) RaiseOSError(256 - exitCode); ELSE RETURN NewChildError(ChildErrorClass.NonZeroExitCode, exitCode); END; ELSIF terminationSignal # 0 THEN RETURN NewChildError(ChildErrorClass.Terminated, terminationSignal); ELSE (* all is well *) RETURN NIL; END; END; END; END Wait; PROCEDURE RawCommand( READONLY command: ARRAY OF Ctypes.CharStar) : ChildError RAISES {OSError.E}= VAR pid: INTEGER; BEGIN Thread.Acquire(UnixMutex.errno); pid := Unix.vfork(); IF pid = 0 THEN (* This code executed by child *) EVAL Uexec.execvp(command[0], ADR(command[0])); (* We only get here if the 'execvp' fails (a successful 'execvp' blows us away!). So if we get here there has been an error and we use a negative errorcode to let parent know what happened *) Unix.exit(-Uerror.errno); ELSE (* This code executed by parent *) VAR saveErrno := Uerror.errno; BEGIN Thread.Release(UnixMutex.errno); IF pid > 0 THEN (* All is well *) RETURN Wait(); ELSE RaiseOSError(saveErrno); END; END; END; END RawCommand; PROCEDURE Command( command: TEXT; READONLY args: ARRAY OF TEXT) : ChildError RAISES {OSError.E}= VAR stringArgs := NEW(REF ARRAY OF Ctypes.CharStar, NUMBER(args) + 2); BEGIN stringArgs[0] := M3toC.CopyTtoS(command); FOR i := 0 TO LAST(args) DO stringArgs[i+1] := M3toC.CopyTtoS(args[i]); END; stringArgs[LAST(stringArgs^)] := NIL; RETURN RawCommand(stringArgs^); END Command; EXCEPTION SHELLEnvironmentVarNotFound; PROCEDURE Shell(shell: TEXT): TEXT RAISES {}= BEGIN IF shell # NIL OR EnvVar.Get("SHELL", shell) THEN RETURN shell END; RAISE SHELLEnvironmentVarNotFound; END Shell; PROCEDURE ShellCommand( command: TEXT; shell: TEXT := NIL) RAISES {OSError.E}= BEGIN EVAL Command(Shell(shell), ARRAY OF TEXT{"-c", command}); END ShellCommand; BEGIN END UnixRun.