github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/process.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package containerd 18 19 import ( 20 "context" 21 "strings" 22 "syscall" 23 "time" 24 25 "github.com/containerd/containerd/api/services/tasks/v1" 26 "github.com/containerd/containerd/cio" 27 "github.com/containerd/containerd/errdefs" 28 "github.com/pkg/errors" 29 ) 30 31 // Process represents a system process 32 type Process interface { 33 // ID of the process 34 ID() string 35 // Pid is the system specific process id 36 Pid() uint32 37 // Start starts the process executing the user's defined binary 38 Start(context.Context) error 39 // Delete removes the process and any resources allocated returning the exit status 40 Delete(context.Context, ...ProcessDeleteOpts) (*ExitStatus, error) 41 // Kill sends the provided signal to the process 42 Kill(context.Context, syscall.Signal, ...KillOpts) error 43 // Wait asynchronously waits for the process to exit, and sends the exit code to the returned channel 44 Wait(context.Context) (<-chan ExitStatus, error) 45 // CloseIO allows various pipes to be closed on the process 46 CloseIO(context.Context, ...IOCloserOpts) error 47 // Resize changes the width and height of the process's terminal 48 Resize(ctx context.Context, w, h uint32) error 49 // IO returns the io set for the process 50 IO() cio.IO 51 // Status returns the executing status of the process 52 Status(context.Context) (Status, error) 53 } 54 55 // NewExitStatus populates an ExitStatus 56 func NewExitStatus(code uint32, t time.Time, err error) *ExitStatus { 57 return &ExitStatus{ 58 code: code, 59 exitedAt: t, 60 err: err, 61 } 62 } 63 64 // ExitStatus encapsulates a process's exit status. 65 // It is used by `Wait()` to return either a process exit code or an error 66 type ExitStatus struct { 67 code uint32 68 exitedAt time.Time 69 err error 70 } 71 72 // Result returns the exit code and time of the exit status. 73 // An error may be returned here to which indicates there was an error 74 // at some point while waiting for the exit status. It does not signify 75 // an error with the process itself. 76 // If an error is returned, the process may still be running. 77 func (s ExitStatus) Result() (uint32, time.Time, error) { 78 return s.code, s.exitedAt, s.err 79 } 80 81 // ExitCode returns the exit code of the process. 82 // This is only valid is Error() returns nil 83 func (s ExitStatus) ExitCode() uint32 { 84 return s.code 85 } 86 87 // ExitTime returns the exit time of the process 88 // This is only valid is Error() returns nil 89 func (s ExitStatus) ExitTime() time.Time { 90 return s.exitedAt 91 } 92 93 // Error returns the error, if any, that occurred while waiting for the 94 // process. 95 func (s ExitStatus) Error() error { 96 return s.err 97 } 98 99 type process struct { 100 id string 101 task *task 102 pid uint32 103 io cio.IO 104 } 105 106 func (p *process) ID() string { 107 return p.id 108 } 109 110 // Pid returns the pid of the process 111 // The pid is not set until start is called and returns 112 func (p *process) Pid() uint32 { 113 return p.pid 114 } 115 116 // Start starts the exec process 117 func (p *process) Start(ctx context.Context) error { 118 r, err := p.task.client.TaskService().Start(ctx, &tasks.StartRequest{ 119 ContainerID: p.task.id, 120 ExecID: p.id, 121 }) 122 if err != nil { 123 if p.io != nil { 124 p.io.Cancel() 125 p.io.Wait() 126 p.io.Close() 127 } 128 return errdefs.FromGRPC(err) 129 } 130 p.pid = r.Pid 131 return nil 132 } 133 134 func (p *process) Kill(ctx context.Context, s syscall.Signal, opts ...KillOpts) error { 135 var i KillInfo 136 for _, o := range opts { 137 if err := o(ctx, &i); err != nil { 138 return err 139 } 140 } 141 _, err := p.task.client.TaskService().Kill(ctx, &tasks.KillRequest{ 142 Signal: uint32(s), 143 ContainerID: p.task.id, 144 ExecID: p.id, 145 All: i.All, 146 }) 147 return errdefs.FromGRPC(err) 148 } 149 150 func (p *process) Wait(ctx context.Context) (<-chan ExitStatus, error) { 151 c := make(chan ExitStatus, 1) 152 go func() { 153 defer close(c) 154 r, err := p.task.client.TaskService().Wait(ctx, &tasks.WaitRequest{ 155 ContainerID: p.task.id, 156 ExecID: p.id, 157 }) 158 if err != nil { 159 c <- ExitStatus{ 160 code: UnknownExitStatus, 161 err: err, 162 } 163 return 164 } 165 c <- ExitStatus{ 166 code: r.ExitStatus, 167 exitedAt: r.ExitedAt, 168 } 169 }() 170 return c, nil 171 } 172 173 func (p *process) CloseIO(ctx context.Context, opts ...IOCloserOpts) error { 174 r := &tasks.CloseIORequest{ 175 ContainerID: p.task.id, 176 ExecID: p.id, 177 } 178 var i IOCloseInfo 179 for _, o := range opts { 180 o(&i) 181 } 182 r.Stdin = i.Stdin 183 _, err := p.task.client.TaskService().CloseIO(ctx, r) 184 return errdefs.FromGRPC(err) 185 } 186 187 func (p *process) IO() cio.IO { 188 return p.io 189 } 190 191 func (p *process) Resize(ctx context.Context, w, h uint32) error { 192 _, err := p.task.client.TaskService().ResizePty(ctx, &tasks.ResizePtyRequest{ 193 ContainerID: p.task.id, 194 Width: w, 195 Height: h, 196 ExecID: p.id, 197 }) 198 return errdefs.FromGRPC(err) 199 } 200 201 func (p *process) Delete(ctx context.Context, opts ...ProcessDeleteOpts) (*ExitStatus, error) { 202 for _, o := range opts { 203 if err := o(ctx, p); err != nil { 204 return nil, err 205 } 206 } 207 status, err := p.Status(ctx) 208 if err != nil { 209 return nil, err 210 } 211 switch status.Status { 212 case Running, Paused, Pausing: 213 return nil, errors.Wrapf(errdefs.ErrFailedPrecondition, "process must be stopped before deletion") 214 } 215 r, err := p.task.client.TaskService().DeleteProcess(ctx, &tasks.DeleteProcessRequest{ 216 ContainerID: p.task.id, 217 ExecID: p.id, 218 }) 219 if err != nil { 220 return nil, errdefs.FromGRPC(err) 221 } 222 if p.io != nil { 223 p.io.Cancel() 224 p.io.Wait() 225 p.io.Close() 226 } 227 return &ExitStatus{code: r.ExitStatus, exitedAt: r.ExitedAt}, nil 228 } 229 230 func (p *process) Status(ctx context.Context) (Status, error) { 231 r, err := p.task.client.TaskService().Get(ctx, &tasks.GetRequest{ 232 ContainerID: p.task.id, 233 ExecID: p.id, 234 }) 235 if err != nil { 236 return Status{}, errdefs.FromGRPC(err) 237 } 238 return Status{ 239 Status: ProcessStatus(strings.ToLower(r.Process.Status.String())), 240 ExitStatus: r.Process.ExitStatus, 241 }, nil 242 }