github.com/sijibomii/docker@v0.0.0-20231230191044-5cf6ca554647/libcontainerd/client_linux.go (about) 1 package libcontainerd 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "os" 7 "path/filepath" 8 "strings" 9 "sync" 10 "syscall" 11 12 "github.com/Sirupsen/logrus" 13 containerd "github.com/docker/containerd/api/grpc/types" 14 "github.com/docker/docker/pkg/idtools" 15 "github.com/docker/docker/pkg/mount" 16 "github.com/opencontainers/specs/specs-go" 17 "golang.org/x/net/context" 18 ) 19 20 type client struct { 21 clientCommon 22 23 // Platform specific properties below here. 24 remote *remote 25 q queue 26 exitNotifiers map[string]*exitNotifier 27 } 28 29 func (clnt *client) AddProcess(containerID, processFriendlyName string, specp Process) error { 30 clnt.lock(containerID) 31 defer clnt.unlock(containerID) 32 container, err := clnt.getContainer(containerID) 33 if err != nil { 34 return err 35 } 36 37 spec, err := container.spec() 38 if err != nil { 39 return err 40 } 41 sp := spec.Process 42 sp.Args = specp.Args 43 sp.Terminal = specp.Terminal 44 if specp.Env != nil { 45 sp.Env = specp.Env 46 } 47 if specp.Cwd != nil { 48 sp.Cwd = *specp.Cwd 49 } 50 if specp.User != nil { 51 sp.User = specs.User{ 52 UID: specp.User.UID, 53 GID: specp.User.GID, 54 AdditionalGids: specp.User.AdditionalGids, 55 } 56 } 57 if specp.Capabilities != nil { 58 sp.Capabilities = specp.Capabilities 59 } 60 61 p := container.newProcess(processFriendlyName) 62 63 r := &containerd.AddProcessRequest{ 64 Args: sp.Args, 65 Cwd: sp.Cwd, 66 Terminal: sp.Terminal, 67 Id: containerID, 68 Env: sp.Env, 69 User: &containerd.User{ 70 Uid: sp.User.UID, 71 Gid: sp.User.GID, 72 AdditionalGids: sp.User.AdditionalGids, 73 }, 74 Pid: processFriendlyName, 75 Stdin: p.fifo(syscall.Stdin), 76 Stdout: p.fifo(syscall.Stdout), 77 Stderr: p.fifo(syscall.Stderr), 78 Capabilities: sp.Capabilities, 79 ApparmorProfile: sp.ApparmorProfile, 80 SelinuxLabel: sp.SelinuxLabel, 81 NoNewPrivileges: sp.NoNewPrivileges, 82 Rlimits: convertRlimits(sp.Rlimits), 83 } 84 85 iopipe, err := p.openFifos(sp.Terminal) 86 if err != nil { 87 return err 88 } 89 90 if _, err := clnt.remote.apiClient.AddProcess(context.Background(), r); err != nil { 91 p.closeFifos(iopipe) 92 return err 93 } 94 95 container.processes[processFriendlyName] = p 96 97 clnt.unlock(containerID) 98 99 if err := clnt.backend.AttachStreams(processFriendlyName, *iopipe); err != nil { 100 return err 101 } 102 clnt.lock(containerID) 103 104 return nil 105 } 106 107 func (clnt *client) prepareBundleDir(uid, gid int) (string, error) { 108 root, err := filepath.Abs(clnt.remote.stateDir) 109 if err != nil { 110 return "", err 111 } 112 if uid == 0 && gid == 0 { 113 return root, nil 114 } 115 p := string(filepath.Separator) 116 for _, d := range strings.Split(root, string(filepath.Separator))[1:] { 117 p = filepath.Join(p, d) 118 fi, err := os.Stat(p) 119 if err != nil && !os.IsNotExist(err) { 120 return "", err 121 } 122 if os.IsNotExist(err) || fi.Mode()&1 == 0 { 123 p = fmt.Sprintf("%s.%d.%d", p, uid, gid) 124 if err := idtools.MkdirAs(p, 0700, uid, gid); err != nil && !os.IsExist(err) { 125 return "", err 126 } 127 } 128 } 129 return p, nil 130 } 131 132 func (clnt *client) Create(containerID string, spec Spec, options ...CreateOption) (err error) { 133 clnt.lock(containerID) 134 defer clnt.unlock(containerID) 135 136 if ctr, err := clnt.getContainer(containerID); err == nil { 137 if ctr.restarting { 138 ctr.restartManager.Cancel() 139 ctr.clean() 140 } else { 141 return fmt.Errorf("Container %s is already active", containerID) 142 } 143 } 144 145 uid, gid, err := getRootIDs(specs.Spec(spec)) 146 if err != nil { 147 return err 148 } 149 dir, err := clnt.prepareBundleDir(uid, gid) 150 if err != nil { 151 return err 152 } 153 154 container := clnt.newContainer(filepath.Join(dir, containerID), options...) 155 if err := container.clean(); err != nil { 156 return err 157 } 158 159 defer func() { 160 if err != nil { 161 container.clean() 162 clnt.deleteContainer(containerID) 163 } 164 }() 165 166 // uid/gid 167 rootfsDir := filepath.Join(container.dir, "rootfs") 168 if err := idtools.MkdirAllAs(rootfsDir, 0700, uid, gid); err != nil && !os.IsExist(err) { 169 return err 170 } 171 if err := syscall.Mount(spec.Root.Path, rootfsDir, "bind", syscall.MS_REC|syscall.MS_BIND, ""); err != nil { 172 return err 173 } 174 spec.Root.Path = "rootfs" 175 176 f, err := os.Create(filepath.Join(container.dir, configFilename)) 177 if err != nil { 178 return err 179 } 180 defer f.Close() 181 if err := json.NewEncoder(f).Encode(spec); err != nil { 182 return err 183 } 184 185 return container.start() 186 } 187 188 func (clnt *client) Signal(containerID string, sig int) error { 189 clnt.lock(containerID) 190 defer clnt.unlock(containerID) 191 _, err := clnt.remote.apiClient.Signal(context.Background(), &containerd.SignalRequest{ 192 Id: containerID, 193 Pid: InitFriendlyName, 194 Signal: uint32(sig), 195 }) 196 return err 197 } 198 199 func (clnt *client) Resize(containerID, processFriendlyName string, width, height int) error { 200 clnt.lock(containerID) 201 defer clnt.unlock(containerID) 202 if _, err := clnt.getContainer(containerID); err != nil { 203 return err 204 } 205 _, err := clnt.remote.apiClient.UpdateProcess(context.Background(), &containerd.UpdateProcessRequest{ 206 Id: containerID, 207 Pid: processFriendlyName, 208 Width: uint32(width), 209 Height: uint32(height), 210 }) 211 return err 212 } 213 214 func (clnt *client) Pause(containerID string) error { 215 return clnt.setState(containerID, StatePause) 216 } 217 218 func (clnt *client) setState(containerID, state string) error { 219 clnt.lock(containerID) 220 container, err := clnt.getContainer(containerID) 221 if err != nil { 222 clnt.unlock(containerID) 223 return err 224 } 225 if container.systemPid == 0 { 226 clnt.unlock(containerID) 227 return fmt.Errorf("No active process for container %s", containerID) 228 } 229 st := "running" 230 if state == StatePause { 231 st = "paused" 232 } 233 chstate := make(chan struct{}) 234 _, err = clnt.remote.apiClient.UpdateContainer(context.Background(), &containerd.UpdateContainerRequest{ 235 Id: containerID, 236 Pid: InitFriendlyName, 237 Status: st, 238 }) 239 if err != nil { 240 clnt.unlock(containerID) 241 return err 242 } 243 container.pauseMonitor.append(state, chstate) 244 clnt.unlock(containerID) 245 <-chstate 246 return nil 247 } 248 249 func (clnt *client) Resume(containerID string) error { 250 return clnt.setState(containerID, StateResume) 251 } 252 253 func (clnt *client) Stats(containerID string) (*Stats, error) { 254 resp, err := clnt.remote.apiClient.Stats(context.Background(), &containerd.StatsRequest{containerID}) 255 if err != nil { 256 return nil, err 257 } 258 return (*Stats)(resp), nil 259 } 260 261 func (clnt *client) setExited(containerID string) error { 262 clnt.lock(containerID) 263 defer clnt.unlock(containerID) 264 265 var exitCode uint32 266 if event, ok := clnt.remote.pastEvents[containerID]; ok { 267 exitCode = event.Status 268 delete(clnt.remote.pastEvents, containerID) 269 } 270 271 err := clnt.backend.StateChanged(containerID, StateInfo{ 272 CommonStateInfo: CommonStateInfo{ 273 State: StateExit, 274 ExitCode: exitCode, 275 }}) 276 277 // Unmount and delete the bundle folder 278 if mts, err := mount.GetMounts(); err == nil { 279 for _, mts := range mts { 280 if strings.HasSuffix(mts.Mountpoint, containerID+"/rootfs") { 281 if err := syscall.Unmount(mts.Mountpoint, syscall.MNT_DETACH); err == nil { 282 os.RemoveAll(strings.TrimSuffix(mts.Mountpoint, "/rootfs")) 283 } 284 break 285 } 286 } 287 } 288 289 return err 290 } 291 292 func (clnt *client) GetPidsForContainer(containerID string) ([]int, error) { 293 cont, err := clnt.getContainerdContainer(containerID) 294 if err != nil { 295 return nil, err 296 } 297 pids := make([]int, len(cont.Pids)) 298 for i, p := range cont.Pids { 299 pids[i] = int(p) 300 } 301 return pids, nil 302 } 303 304 // Summary returns a summary of the processes running in a container. 305 // This is a no-op on Linux. 306 func (clnt *client) Summary(containerID string) ([]Summary, error) { 307 return nil, nil 308 } 309 310 func (clnt *client) getContainerdContainer(containerID string) (*containerd.Container, error) { 311 resp, err := clnt.remote.apiClient.State(context.Background(), &containerd.StateRequest{Id: containerID}) 312 if err != nil { 313 return nil, err 314 } 315 for _, cont := range resp.Containers { 316 if cont.Id == containerID { 317 return cont, nil 318 } 319 } 320 return nil, fmt.Errorf("invalid state response") 321 } 322 323 func (clnt *client) newContainer(dir string, options ...CreateOption) *container { 324 container := &container{ 325 containerCommon: containerCommon{ 326 process: process{ 327 dir: dir, 328 processCommon: processCommon{ 329 containerID: filepath.Base(dir), 330 client: clnt, 331 friendlyName: InitFriendlyName, 332 }, 333 }, 334 processes: make(map[string]*process), 335 }, 336 } 337 for _, option := range options { 338 if err := option.Apply(container); err != nil { 339 logrus.Error(err) 340 } 341 } 342 return container 343 } 344 345 func (clnt *client) UpdateResources(containerID string, resources Resources) error { 346 clnt.lock(containerID) 347 defer clnt.unlock(containerID) 348 container, err := clnt.getContainer(containerID) 349 if err != nil { 350 return err 351 } 352 if container.systemPid == 0 { 353 return fmt.Errorf("No active process for container %s", containerID) 354 } 355 _, err = clnt.remote.apiClient.UpdateContainer(context.Background(), &containerd.UpdateContainerRequest{ 356 Id: containerID, 357 Pid: InitFriendlyName, 358 Resources: (*containerd.UpdateResource)(&resources), 359 }) 360 if err != nil { 361 return err 362 } 363 return nil 364 } 365 366 func (clnt *client) getExitNotifier(containerID string) *exitNotifier { 367 clnt.mapMutex.RLock() 368 defer clnt.mapMutex.RUnlock() 369 return clnt.exitNotifiers[containerID] 370 } 371 372 func (clnt *client) getOrCreateExitNotifier(containerID string) *exitNotifier { 373 clnt.mapMutex.Lock() 374 w, ok := clnt.exitNotifiers[containerID] 375 defer clnt.mapMutex.Unlock() 376 if !ok { 377 w = &exitNotifier{c: make(chan struct{}), client: clnt} 378 clnt.exitNotifiers[containerID] = w 379 } 380 return w 381 } 382 383 type exitNotifier struct { 384 id string 385 client *client 386 c chan struct{} 387 once sync.Once 388 } 389 390 func (en *exitNotifier) close() { 391 en.once.Do(func() { 392 close(en.c) 393 en.client.mapMutex.Lock() 394 if en == en.client.exitNotifiers[en.id] { 395 delete(en.client.exitNotifiers, en.id) 396 } 397 en.client.mapMutex.Unlock() 398 }) 399 } 400 func (en *exitNotifier) wait() <-chan struct{} { 401 return en.c 402 }