github.com/moby/docker@v26.1.3+incompatible/daemon/daemon_windows.go (about)

     1  package daemon // import "github.com/docker/docker/daemon"
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math"
     7  	"path/filepath"
     8  	"runtime"
     9  	"strings"
    10  
    11  	"github.com/Microsoft/hcsshim"
    12  	"github.com/Microsoft/hcsshim/osversion"
    13  	"github.com/containerd/log"
    14  	containertypes "github.com/docker/docker/api/types/container"
    15  	networktypes "github.com/docker/docker/api/types/network"
    16  	"github.com/docker/docker/container"
    17  	"github.com/docker/docker/daemon/config"
    18  	"github.com/docker/docker/libcontainerd/local"
    19  	"github.com/docker/docker/libcontainerd/remote"
    20  	"github.com/docker/docker/libnetwork"
    21  	nwconfig "github.com/docker/docker/libnetwork/config"
    22  	winlibnetwork "github.com/docker/docker/libnetwork/drivers/windows"
    23  	"github.com/docker/docker/libnetwork/netlabel"
    24  	"github.com/docker/docker/libnetwork/options"
    25  	"github.com/docker/docker/libnetwork/scope"
    26  	"github.com/docker/docker/pkg/idtools"
    27  	"github.com/docker/docker/pkg/parsers"
    28  	"github.com/docker/docker/pkg/parsers/operatingsystem"
    29  	"github.com/docker/docker/pkg/sysinfo"
    30  	"github.com/docker/docker/pkg/system"
    31  	"github.com/docker/docker/runconfig"
    32  	"github.com/pkg/errors"
    33  	"golang.org/x/sys/windows"
    34  	"golang.org/x/sys/windows/svc/mgr"
    35  )
    36  
    37  const (
    38  	isWindows            = true
    39  	windowsMinCPUShares  = 1
    40  	windowsMaxCPUShares  = 10000
    41  	windowsMinCPUPercent = 1
    42  	windowsMaxCPUPercent = 100
    43  
    44  	windowsV1RuntimeName = "com.docker.hcsshim.v1"
    45  	windowsV2RuntimeName = "io.containerd.runhcs.v1"
    46  )
    47  
    48  // Windows containers are much larger than Linux containers and each of them
    49  // have > 20 system processes which why we use much smaller parallelism value.
    50  func adjustParallelLimit(n int, limit int) int {
    51  	return int(math.Max(1, math.Floor(float64(runtime.NumCPU())*.8)))
    52  }
    53  
    54  // Windows has no concept of an execution state directory. So use config.Root here.
    55  func getPluginExecRoot(cfg *config.Config) string {
    56  	return filepath.Join(cfg.Root, "plugins")
    57  }
    58  
    59  func (daemon *Daemon) parseSecurityOpt(daemonCfg *config.Config, securityOptions *container.SecurityOptions, hostConfig *containertypes.HostConfig) error {
    60  	return nil
    61  }
    62  
    63  func setupInitLayer(idMapping idtools.IdentityMapping) func(string) error {
    64  	return nil
    65  }
    66  
    67  // adaptContainerSettings is called during container creation to modify any
    68  // settings necessary in the HostConfig structure.
    69  func (daemon *Daemon) adaptContainerSettings(daemonCfg *config.Config, hostConfig *containertypes.HostConfig) error {
    70  	return nil
    71  }
    72  
    73  // verifyPlatformContainerResources performs platform-specific validation of the container's resource-configuration
    74  func verifyPlatformContainerResources(resources *containertypes.Resources, isHyperv bool) (warnings []string, err error) {
    75  	fixMemorySwappiness(resources)
    76  	if !isHyperv {
    77  		// The processor resource controls are mutually exclusive on
    78  		// Windows Server Containers, the order of precedence is
    79  		// CPUCount first, then CPUShares, and CPUPercent last.
    80  		if resources.CPUCount > 0 {
    81  			if resources.CPUShares > 0 {
    82  				warnings = append(warnings, "Conflicting options: CPU count takes priority over CPU shares on Windows Server Containers. CPU shares discarded")
    83  				resources.CPUShares = 0
    84  			}
    85  			if resources.CPUPercent > 0 {
    86  				warnings = append(warnings, "Conflicting options: CPU count takes priority over CPU percent on Windows Server Containers. CPU percent discarded")
    87  				resources.CPUPercent = 0
    88  			}
    89  		} else if resources.CPUShares > 0 {
    90  			if resources.CPUPercent > 0 {
    91  				warnings = append(warnings, "Conflicting options: CPU shares takes priority over CPU percent on Windows Server Containers. CPU percent discarded")
    92  				resources.CPUPercent = 0
    93  			}
    94  		}
    95  	}
    96  
    97  	if resources.CPUShares < 0 || resources.CPUShares > windowsMaxCPUShares {
    98  		return warnings, fmt.Errorf("range of CPUShares is from %d to %d", windowsMinCPUShares, windowsMaxCPUShares)
    99  	}
   100  	if resources.CPUPercent < 0 || resources.CPUPercent > windowsMaxCPUPercent {
   101  		return warnings, fmt.Errorf("range of CPUPercent is from %d to %d", windowsMinCPUPercent, windowsMaxCPUPercent)
   102  	}
   103  	if resources.CPUCount < 0 {
   104  		return warnings, fmt.Errorf("invalid CPUCount: CPUCount cannot be negative")
   105  	}
   106  
   107  	if resources.NanoCPUs > 0 && resources.CPUPercent > 0 {
   108  		return warnings, fmt.Errorf("conflicting options: Nano CPUs and CPU Percent cannot both be set")
   109  	}
   110  	if resources.NanoCPUs > 0 && resources.CPUShares > 0 {
   111  		return warnings, fmt.Errorf("conflicting options: Nano CPUs and CPU Shares cannot both be set")
   112  	}
   113  	// The precision we could get is 0.01, because on Windows we have to convert to CPUPercent.
   114  	// We don't set the lower limit here and it is up to the underlying platform (e.g., Windows) to return an error.
   115  	if resources.NanoCPUs < 0 || resources.NanoCPUs > int64(sysinfo.NumCPU())*1e9 {
   116  		return warnings, fmt.Errorf("range of CPUs is from 0.01 to %d.00, as there are only %d CPUs available", sysinfo.NumCPU(), sysinfo.NumCPU())
   117  	}
   118  
   119  	if len(resources.BlkioDeviceReadBps) > 0 {
   120  		return warnings, fmt.Errorf("invalid option: Windows does not support BlkioDeviceReadBps")
   121  	}
   122  	if len(resources.BlkioDeviceReadIOps) > 0 {
   123  		return warnings, fmt.Errorf("invalid option: Windows does not support BlkioDeviceReadIOps")
   124  	}
   125  	if len(resources.BlkioDeviceWriteBps) > 0 {
   126  		return warnings, fmt.Errorf("invalid option: Windows does not support BlkioDeviceWriteBps")
   127  	}
   128  	if len(resources.BlkioDeviceWriteIOps) > 0 {
   129  		return warnings, fmt.Errorf("invalid option: Windows does not support BlkioDeviceWriteIOps")
   130  	}
   131  	if resources.BlkioWeight > 0 {
   132  		return warnings, fmt.Errorf("invalid option: Windows does not support BlkioWeight")
   133  	}
   134  	if len(resources.BlkioWeightDevice) > 0 {
   135  		return warnings, fmt.Errorf("invalid option: Windows does not support BlkioWeightDevice")
   136  	}
   137  	if resources.CgroupParent != "" {
   138  		return warnings, fmt.Errorf("invalid option: Windows does not support CgroupParent")
   139  	}
   140  	if resources.CPUPeriod != 0 {
   141  		return warnings, fmt.Errorf("invalid option: Windows does not support CPUPeriod")
   142  	}
   143  	if resources.CpusetCpus != "" {
   144  		return warnings, fmt.Errorf("invalid option: Windows does not support CpusetCpus")
   145  	}
   146  	if resources.CpusetMems != "" {
   147  		return warnings, fmt.Errorf("invalid option: Windows does not support CpusetMems")
   148  	}
   149  	if resources.KernelMemory != 0 {
   150  		return warnings, fmt.Errorf("invalid option: Windows does not support KernelMemory")
   151  	}
   152  	if resources.MemoryReservation != 0 {
   153  		return warnings, fmt.Errorf("invalid option: Windows does not support MemoryReservation")
   154  	}
   155  	if resources.MemorySwap != 0 {
   156  		return warnings, fmt.Errorf("invalid option: Windows does not support MemorySwap")
   157  	}
   158  	if resources.MemorySwappiness != nil {
   159  		return warnings, fmt.Errorf("invalid option: Windows does not support MemorySwappiness")
   160  	}
   161  	if resources.OomKillDisable != nil && *resources.OomKillDisable {
   162  		return warnings, fmt.Errorf("invalid option: Windows does not support OomKillDisable")
   163  	}
   164  	if resources.PidsLimit != nil && *resources.PidsLimit != 0 {
   165  		return warnings, fmt.Errorf("invalid option: Windows does not support PidsLimit")
   166  	}
   167  	if len(resources.Ulimits) != 0 {
   168  		return warnings, fmt.Errorf("invalid option: Windows does not support Ulimits")
   169  	}
   170  	return warnings, nil
   171  }
   172  
   173  // verifyPlatformContainerSettings performs platform-specific validation of the
   174  // hostconfig and config structures.
   175  func verifyPlatformContainerSettings(daemon *Daemon, daemonCfg *configStore, hostConfig *containertypes.HostConfig, update bool) (warnings []string, err error) {
   176  	if hostConfig == nil {
   177  		return nil, nil
   178  	}
   179  	return verifyPlatformContainerResources(&hostConfig.Resources, daemon.runAsHyperVContainer(hostConfig))
   180  }
   181  
   182  // verifyDaemonSettings performs validation of daemon config struct
   183  func verifyDaemonSettings(config *config.Config) error {
   184  	return nil
   185  }
   186  
   187  // checkSystem validates platform-specific requirements
   188  func checkSystem() error {
   189  	// Validate the OS version. Note that dockerd.exe must be manifested for this
   190  	// call to return the correct version.
   191  	if osversion.Get().MajorVersion < 10 || osversion.Build() < osversion.RS5 {
   192  		return fmt.Errorf("this version of Windows does not support the docker daemon (Windows build %d or higher is required)", osversion.RS5)
   193  	}
   194  
   195  	vmcompute := windows.NewLazySystemDLL("vmcompute.dll")
   196  	if vmcompute.Load() != nil {
   197  		return fmt.Errorf("failed to load vmcompute.dll, ensure that the Containers feature is installed")
   198  	}
   199  
   200  	// Ensure that the required Host Network Service and vmcompute services
   201  	// are running. Docker will fail in unexpected ways if this is not present.
   202  	requiredServices := []string{"hns", "vmcompute"}
   203  	if err := ensureServicesInstalled(requiredServices); err != nil {
   204  		return errors.Wrap(err, "a required service is not installed, ensure the Containers feature is installed")
   205  	}
   206  
   207  	return nil
   208  }
   209  
   210  func ensureServicesInstalled(services []string) error {
   211  	m, err := mgr.Connect()
   212  	if err != nil {
   213  		return err
   214  	}
   215  	defer m.Disconnect()
   216  	for _, service := range services {
   217  		s, err := m.OpenService(service)
   218  		if err != nil {
   219  			return errors.Wrapf(err, "failed to open service %s", service)
   220  		}
   221  		s.Close()
   222  	}
   223  	return nil
   224  }
   225  
   226  // configureKernelSecuritySupport configures and validate security support for the kernel
   227  func configureKernelSecuritySupport(config *config.Config, driverName string) error {
   228  	return nil
   229  }
   230  
   231  // configureMaxThreads sets the Go runtime max threads threshold
   232  func configureMaxThreads(config *config.Config) error {
   233  	return nil
   234  }
   235  
   236  func (daemon *Daemon) initNetworkController(daemonCfg *config.Config, activeSandboxes map[string]interface{}) error {
   237  	netOptions, err := daemon.networkOptions(daemonCfg, nil, nil)
   238  	if err != nil {
   239  		return err
   240  	}
   241  	daemon.netController, err = libnetwork.New(netOptions...)
   242  	if err != nil {
   243  		return errors.Wrap(err, "error obtaining controller instance")
   244  	}
   245  
   246  	hnsresponse, err := hcsshim.HNSListNetworkRequest("GET", "", "")
   247  	if err != nil {
   248  		return err
   249  	}
   250  
   251  	ctx := context.TODO()
   252  
   253  	// Remove networks not present in HNS
   254  	for _, v := range daemon.netController.Networks(ctx) {
   255  		hnsid := v.DriverOptions()[winlibnetwork.HNSID]
   256  		found := false
   257  
   258  		for _, v := range hnsresponse {
   259  			if v.Id == hnsid {
   260  				found = true
   261  				break
   262  			}
   263  		}
   264  
   265  		if !found {
   266  			// non-default nat networks should be re-created if missing from HNS
   267  			if v.Type() == "nat" && v.Name() != networktypes.NetworkNat {
   268  				_, _, v4Conf, v6Conf := v.IpamConfig()
   269  				netOption := map[string]string{}
   270  				for k, v := range v.DriverOptions() {
   271  					if k != winlibnetwork.NetworkName && k != winlibnetwork.HNSID {
   272  						netOption[k] = v
   273  					}
   274  				}
   275  				name := v.Name()
   276  				id := v.ID()
   277  
   278  				err = v.Delete()
   279  				if err != nil {
   280  					log.G(context.TODO()).Errorf("Error occurred when removing network %v", err)
   281  				}
   282  
   283  				_, err := daemon.netController.NewNetwork("nat", name, id,
   284  					libnetwork.NetworkOptionGeneric(options.Generic{
   285  						netlabel.GenericData: netOption,
   286  					}),
   287  					libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil),
   288  				)
   289  				if err != nil {
   290  					log.G(context.TODO()).Errorf("Error occurred when creating network %v", err)
   291  				}
   292  				continue
   293  			}
   294  
   295  			// global networks should not be deleted by local HNS
   296  			if v.Scope() != scope.Global {
   297  				err = v.Delete()
   298  				if err != nil {
   299  					log.G(context.TODO()).Errorf("Error occurred when removing network %v", err)
   300  				}
   301  			}
   302  		}
   303  	}
   304  
   305  	_, err = daemon.netController.NewNetwork("null", "none", "", libnetwork.NetworkOptionPersist(false))
   306  	if err != nil {
   307  		return err
   308  	}
   309  
   310  	defaultNetworkExists := false
   311  
   312  	if network, err := daemon.netController.NetworkByName(runconfig.DefaultDaemonNetworkMode().NetworkName()); err == nil {
   313  		hnsid := network.DriverOptions()[winlibnetwork.HNSID]
   314  		for _, v := range hnsresponse {
   315  			if hnsid == v.Id {
   316  				defaultNetworkExists = true
   317  				break
   318  			}
   319  		}
   320  	}
   321  
   322  	// discover and add HNS networks to windows
   323  	// network that exist are removed and added again
   324  	for _, v := range hnsresponse {
   325  		networkTypeNorm := strings.ToLower(v.Type)
   326  		if networkTypeNorm == "private" || networkTypeNorm == "internal" {
   327  			continue // workaround for HNS reporting unsupported networks
   328  		}
   329  		var n *libnetwork.Network
   330  		daemon.netController.WalkNetworks(func(current *libnetwork.Network) bool {
   331  			hnsid := current.DriverOptions()[winlibnetwork.HNSID]
   332  			if hnsid == v.Id {
   333  				n = current
   334  				return true
   335  			}
   336  			return false
   337  		})
   338  
   339  		drvOptions := make(map[string]string)
   340  		nid := ""
   341  		if n != nil {
   342  			nid = n.ID()
   343  
   344  			// global networks should not be deleted by local HNS
   345  			if n.Scope() == scope.Global {
   346  				continue
   347  			}
   348  			v.Name = n.Name()
   349  			// This will not cause network delete from HNS as the network
   350  			// is not yet populated in the libnetwork windows driver
   351  
   352  			// restore option if it existed before
   353  			drvOptions = n.DriverOptions()
   354  			n.Delete()
   355  		}
   356  		netOption := map[string]string{
   357  			winlibnetwork.NetworkName: v.Name,
   358  			winlibnetwork.HNSID:       v.Id,
   359  		}
   360  
   361  		// add persisted driver options
   362  		for k, v := range drvOptions {
   363  			if k != winlibnetwork.NetworkName && k != winlibnetwork.HNSID {
   364  				netOption[k] = v
   365  			}
   366  		}
   367  
   368  		v4Conf := []*libnetwork.IpamConf{}
   369  		for _, subnet := range v.Subnets {
   370  			ipamV4Conf := libnetwork.IpamConf{}
   371  			ipamV4Conf.PreferredPool = subnet.AddressPrefix
   372  			ipamV4Conf.Gateway = subnet.GatewayAddress
   373  			v4Conf = append(v4Conf, &ipamV4Conf)
   374  		}
   375  
   376  		name := v.Name
   377  
   378  		// If there is no nat network create one from the first NAT network
   379  		// encountered if it doesn't already exist
   380  		if !defaultNetworkExists &&
   381  			runconfig.DefaultDaemonNetworkMode() == containertypes.NetworkMode(strings.ToLower(v.Type)) &&
   382  			n == nil {
   383  			name = runconfig.DefaultDaemonNetworkMode().NetworkName()
   384  			defaultNetworkExists = true
   385  		}
   386  
   387  		v6Conf := []*libnetwork.IpamConf{}
   388  		_, err := daemon.netController.NewNetwork(strings.ToLower(v.Type), name, nid,
   389  			libnetwork.NetworkOptionGeneric(options.Generic{
   390  				netlabel.GenericData: netOption,
   391  			}),
   392  			libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil),
   393  		)
   394  		if err != nil {
   395  			log.G(context.TODO()).Errorf("Error occurred when creating network %v", err)
   396  		}
   397  	}
   398  
   399  	if !daemonCfg.DisableBridge {
   400  		// Initialize default driver "bridge"
   401  		if err := initBridgeDriver(daemon.netController, daemonCfg.BridgeConfig); err != nil {
   402  			return err
   403  		}
   404  	}
   405  
   406  	return nil
   407  }
   408  
   409  func initBridgeDriver(controller *libnetwork.Controller, config config.BridgeConfig) error {
   410  	if _, err := controller.NetworkByName(runconfig.DefaultDaemonNetworkMode().NetworkName()); err == nil {
   411  		return nil
   412  	}
   413  
   414  	netOption := map[string]string{
   415  		winlibnetwork.NetworkName: runconfig.DefaultDaemonNetworkMode().NetworkName(),
   416  	}
   417  
   418  	var ipamOption libnetwork.NetworkOption
   419  	var subnetPrefix string
   420  
   421  	if config.FixedCIDR != "" {
   422  		subnetPrefix = config.FixedCIDR
   423  	}
   424  
   425  	if subnetPrefix != "" {
   426  		ipamV4Conf := libnetwork.IpamConf{PreferredPool: subnetPrefix}
   427  		v4Conf := []*libnetwork.IpamConf{&ipamV4Conf}
   428  		v6Conf := []*libnetwork.IpamConf{}
   429  		ipamOption = libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil)
   430  	}
   431  
   432  	_, err := controller.NewNetwork(string(runconfig.DefaultDaemonNetworkMode()), runconfig.DefaultDaemonNetworkMode().NetworkName(), "",
   433  		libnetwork.NetworkOptionGeneric(options.Generic{
   434  			netlabel.GenericData: netOption,
   435  		}),
   436  		ipamOption,
   437  	)
   438  	if err != nil {
   439  		return errors.Wrap(err, "error creating default network")
   440  	}
   441  
   442  	return nil
   443  }
   444  
   445  // registerLinks sets up links between containers and writes the
   446  // configuration out for persistence. As of Windows TP4, links are not supported.
   447  func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *containertypes.HostConfig) error {
   448  	return nil
   449  }
   450  
   451  func (daemon *Daemon) cleanupMountsByID(in string) error {
   452  	return nil
   453  }
   454  
   455  func (daemon *Daemon) cleanupMounts(*config.Config) error {
   456  	return nil
   457  }
   458  
   459  func recursiveUnmount(_ string) error {
   460  	return nil
   461  }
   462  
   463  func setupRemappedRoot(config *config.Config) (idtools.IdentityMapping, error) {
   464  	return idtools.IdentityMapping{}, nil
   465  }
   466  
   467  func setupDaemonRoot(config *config.Config, rootDir string, rootIdentity idtools.Identity) error {
   468  	config.Root = rootDir
   469  	// Create the root directory if it doesn't exists
   470  	if err := system.MkdirAllWithACL(config.Root, 0, system.SddlAdministratorsLocalSystem); err != nil {
   471  		return err
   472  	}
   473  	return nil
   474  }
   475  
   476  // runasHyperVContainer returns true if we are going to run as a Hyper-V container
   477  func (daemon *Daemon) runAsHyperVContainer(hostConfig *containertypes.HostConfig) bool {
   478  	if hostConfig.Isolation.IsDefault() {
   479  		// Container is set to use the default, so take the default from the daemon configuration
   480  		return daemon.defaultIsolation.IsHyperV()
   481  	}
   482  
   483  	// Container is requesting an isolation mode. Honour it.
   484  	return hostConfig.Isolation.IsHyperV()
   485  }
   486  
   487  // conditionalMountOnStart is a platform specific helper function during the
   488  // container start to call mount.
   489  func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error {
   490  	if daemon.runAsHyperVContainer(container.HostConfig) {
   491  		// We do not mount if a Hyper-V container as it needs to be mounted inside the
   492  		// utility VM, not the host.
   493  		return nil
   494  	}
   495  	return daemon.Mount(container)
   496  }
   497  
   498  // conditionalUnmountOnCleanup is a platform specific helper function called
   499  // during the cleanup of a container to unmount.
   500  func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) error {
   501  	if daemon.runAsHyperVContainer(container.HostConfig) {
   502  		// We do not unmount if a Hyper-V container
   503  		return nil
   504  	}
   505  	return daemon.Unmount(container)
   506  }
   507  
   508  func driverOptions(_ *config.Config) nwconfig.Option {
   509  	return nil
   510  }
   511  
   512  // setDefaultIsolation determine the default isolation mode for the
   513  // daemon to run in. This is only applicable on Windows
   514  func (daemon *Daemon) setDefaultIsolation(config *config.Config) error {
   515  	// On client SKUs, default to Hyper-V. @engine maintainers. This
   516  	// should not be removed. Ping Microsoft folks is there are PRs to
   517  	// to change this.
   518  	if operatingsystem.IsWindowsClient() {
   519  		daemon.defaultIsolation = containertypes.IsolationHyperV
   520  	} else {
   521  		daemon.defaultIsolation = containertypes.IsolationProcess
   522  	}
   523  	for _, option := range config.ExecOptions {
   524  		key, val, err := parsers.ParseKeyValueOpt(option)
   525  		if err != nil {
   526  			return err
   527  		}
   528  		key = strings.ToLower(key)
   529  		switch key {
   530  
   531  		case "isolation":
   532  			if !containertypes.Isolation(val).IsValid() {
   533  				return fmt.Errorf("Invalid exec-opt value for 'isolation':'%s'", val)
   534  			}
   535  			if containertypes.Isolation(val).IsHyperV() {
   536  				daemon.defaultIsolation = containertypes.IsolationHyperV
   537  			}
   538  			if containertypes.Isolation(val).IsProcess() {
   539  				daemon.defaultIsolation = containertypes.IsolationProcess
   540  			}
   541  		default:
   542  			return fmt.Errorf("Unrecognised exec-opt '%s'\n", key)
   543  		}
   544  	}
   545  
   546  	log.G(context.TODO()).Infof("Windows default isolation mode: %s", daemon.defaultIsolation)
   547  	return nil
   548  }
   549  
   550  func setMayDetachMounts() error {
   551  	return nil
   552  }
   553  
   554  func (daemon *Daemon) setupSeccompProfile(*config.Config) error {
   555  	return nil
   556  }
   557  
   558  func setupResolvConf(config *config.Config) {}
   559  
   560  func getSysInfo(*config.Config) *sysinfo.SysInfo {
   561  	return sysinfo.New()
   562  }
   563  
   564  func (daemon *Daemon) initLibcontainerd(ctx context.Context, cfg *config.Config) error {
   565  	var err error
   566  
   567  	rt := cfg.DefaultRuntime
   568  	if rt == "" {
   569  		if cfg.ContainerdAddr == "" {
   570  			rt = windowsV1RuntimeName
   571  		} else {
   572  			rt = windowsV2RuntimeName
   573  		}
   574  	}
   575  
   576  	switch rt {
   577  	case windowsV1RuntimeName:
   578  		daemon.containerd, err = local.NewClient(
   579  			ctx,
   580  			daemon.containerdClient,
   581  			filepath.Join(cfg.ExecRoot, "containerd"),
   582  			cfg.ContainerdNamespace,
   583  			daemon,
   584  		)
   585  	case windowsV2RuntimeName:
   586  		if cfg.ContainerdAddr == "" {
   587  			return fmt.Errorf("cannot use the specified runtime %q without containerd", rt)
   588  		}
   589  		daemon.containerd, err = remote.NewClient(
   590  			ctx,
   591  			daemon.containerdClient,
   592  			filepath.Join(cfg.ExecRoot, "containerd"),
   593  			cfg.ContainerdNamespace,
   594  			daemon,
   595  		)
   596  	default:
   597  		return fmt.Errorf("unknown windows runtime %s", rt)
   598  	}
   599  
   600  	return err
   601  }