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  }