github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/lang/process_structs.go (about) 1 package lang 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "time" 8 9 "github.com/lmorg/murex/builtins/pipes/streams" 10 "github.com/lmorg/murex/config" 11 "github.com/lmorg/murex/lang/parameters" 12 "github.com/lmorg/murex/lang/process" 13 "github.com/lmorg/murex/lang/ref" 14 "github.com/lmorg/murex/lang/runmode" 15 "github.com/lmorg/murex/lang/state" 16 "github.com/lmorg/murex/lang/stdio" 17 ) 18 19 // Process - Each process running inside the murex shell will be one of these objects. 20 // It is equivalent to the /proc directory on Linux, albeit queried through murex as JSON. 21 // External processes will also appear in the host OS's process list. 22 type Process struct { 23 Id uint32 24 cache *cacheT 25 raw []rune 26 Name process.Name 27 Parameters parameters.Parameters 28 namedPipes []string 29 Context context.Context 30 Stdin stdio.Io 31 Stdout stdio.Io 32 stdoutOldPtr stdio.Io // only used when stdout is a tmp named pipe 33 Stderr stdio.Io 34 ExitNum int 35 Forks *ForkManagement 36 WaitForTermination chan bool `json:"-"` 37 WaitForStopped chan bool `json:"-"` 38 HasStopped chan bool `json:"-"` 39 Done func() `json:"-"` 40 Kill func() `json:"-"` 41 Exec process.Exec 42 Scope *Process `json:"-"` 43 Parent *Process `json:"-"` 44 Previous *Process `json:"-"` 45 Next *Process `json:"-"` 46 IsNot bool 47 IsMethod bool 48 OperatorLogicAnd bool 49 OperatorLogicOr bool 50 NamedPipeOut string 51 NamedPipeErr string 52 NamedPipeTest string 53 hasTerminatedM sync.Mutex `json:"-"` 54 hasTerminatedV bool 55 State state.State 56 Background process.Background 57 RunMode runmode.RunMode 58 Config *config.Config 59 Tests *Tests 60 testState []string 61 Variables *Variables 62 CreationTime time.Time 63 StartTime time.Time 64 FileRef *ref.File 65 CCEvent func(string, *Process) `json:"-"` 66 CCExists func(string) bool `json:"-"` 67 CCOut *streams.Stdin `json:"-"` 68 CCErr *streams.Stdin `json:"-"` 69 } 70 71 func (p *Process) Dump() interface{} { 72 dump := make(map[string]interface{}) 73 74 dump["Id"] = p.Id 75 dump["Name"] = p.Name.String() 76 dump["Parameters"] = p.Parameters.Dump() 77 dump["Context_Set"] = p.Context != nil 78 dump["Stdin_Set"] = p.Stdin != nil 79 dump["Stdout_Set"] = p.Stdout != nil 80 dump["StdoutOldPtr_Set"] = p.stdoutOldPtr != nil 81 dump["Stderr_Set"] = p.Stderr != nil 82 dump["ExitNum"] = p.ExitNum 83 dump["Done_Set"] = p.Done != nil 84 dump["Kill_Set"] = p.Kill != nil 85 dump["Exec"] = &p.Exec 86 dump["Scope.Id"] = p.Scope.Id 87 dump["Parent.Id"] = p.Parent.Id 88 dump["Previous.Id"] = p.Previous.Id 89 dump["IsNot"] = p.IsNot 90 dump["IsMethod"] = p.IsMethod 91 dump["OperatorLogicAnd"] = p.OperatorLogicAnd 92 dump["OperatorLogicOr"] = p.OperatorLogicOr 93 dump["NamedPipeOut"] = p.NamedPipeOut 94 dump["NamedPipeErr"] = p.NamedPipeErr 95 dump["NamedPipeTest"] = p.NamedPipeTest 96 dump["HasTerminated"] = p.HasTerminated() 97 dump["HasCancelled"] = p.HasCancelled() 98 dump["State"] = p.State.String() 99 dump["Background"] = p.Background.String() 100 dump["RunMode"] = p.RunMode.String() 101 dump["RunMode.IsStrict"] = p.RunMode.IsStrict() 102 dump["Config_Set"] = p.Config != nil 103 dump["Tests_Set"] = p.Tests != nil 104 dump["testState"] = p.testState 105 dump["Variables_Set"] = p.Variables != nil 106 dump["CreationTime"] = p.CreationTime 107 dump["StartTime"] = p.StartTime 108 dump["FileRef"] = p.FileRef 109 dump["CCEvent_Set"] = p.CCEvent != nil 110 dump["CCExists_Set"] = p.CCExists != nil 111 dump["CCOut_Set"] = p.CCOut != nil 112 dump["CCErr_Set"] = p.CCErr != nil 113 114 return dump 115 } 116 117 // HasTerminated checks if process has terminated. 118 // This is a function because terminated state can be subject to race conditions 119 // so we need a mutex to make the state thread safe. 120 func (p *Process) HasTerminated() (state bool) { 121 p.hasTerminatedM.Lock() 122 state = p.hasTerminatedV 123 p.hasTerminatedM.Unlock() 124 return 125 } 126 127 // HasCancelled is a wrapper function around context because it's a pretty ugly API 128 func (p *Process) HasCancelled() bool { 129 select { 130 case <-p.Context.Done(): 131 return true 132 133 default: 134 if p.State.Get() == state.Stopped { 135 return p.hasCancelledStopped() 136 } 137 return false 138 } 139 } 140 141 func (p *Process) hasCancelledStopped() bool { 142 select { 143 case <-p.Context.Done(): 144 return true 145 case <-p.WaitForStopped: 146 return false 147 } 148 } 149 150 // SetTerminatedState sets the process terminated state. 151 // This is a function because terminated state can be subject to race conditions 152 // so we need a mutex to make the state thread safe. 153 func (p *Process) SetTerminatedState(state bool) { 154 p.hasTerminatedM.Lock() 155 p.hasTerminatedV = state 156 p.hasTerminatedM.Unlock() 157 } 158 159 // ErrIfNotAMethod returns a standard error message for builtins not run as methods 160 func (p *Process) ErrIfNotAMethod() error { 161 if !p.IsMethod { 162 return fmt.Errorf("`%s` expects to be pipelined", p.Name.String()) 163 } 164 return nil 165 } 166 167 func (p *Process) KillForks(exitNum int) { 168 forks := p.Forks.GetForks() 169 for _, procs := range forks { 170 for i := range *procs { 171 (*procs)[i].ExitNum = exitNum 172 (*procs)[i].Done() 173 } 174 } 175 } 176 177 // Args returns a normalised function name and parameters 178 func (p *Process) Args() (string, []string) { 179 return args(p.Name.String(), p.Parameters.StringArray()) 180 } 181 182 func args(name string, params []string) (string, []string) { 183 if len(params) == 0 { 184 return name, []string{} 185 } 186 187 switch name { 188 case "exec": 189 return params[0], params[1:] 190 191 default: 192 return name, params 193 } 194 } 195 196 type foregroundProc struct { 197 mutex sync.Mutex 198 p *Process 199 } 200 201 func newForegroundProc() *foregroundProc { 202 return &foregroundProc{p: ShellProcess} 203 } 204 205 func (fp *foregroundProc) Get() *Process { 206 fp.mutex.Lock() 207 p := fp.p 208 //if p == nil { 209 // panic("Get() retrieved p") 210 //} 211 fp.mutex.Unlock() 212 213 return p 214 } 215 216 func (fp *foregroundProc) Set(p *Process) { 217 fp.mutex.Lock() 218 if p == nil { 219 panic("nil p in (fp *foregroundProc) Set(p *Process)") 220 } 221 fp.p = p 222 //debug.Json("fp.Set", p) 223 //debug.Json("fp.p", fp.p) 224 fp.mutex.Unlock() 225 }