Procházet zdrojové kódy

Merge pull request #44381 from thaJeztah/strings_cut

Replace uses of `strings.Split(N)` with `strings.Cut()`
Tianon Gravi před 2 roky
rodič
revize
3a5598affa
49 změnil soubory, kde provedl 323 přidání a 405 odebrání
  1. 3 3
      api/types/filters/parse.go
  2. 11 11
      api/types/time/timestamp.go
  3. 7 8
      api/types/types.go
  4. 4 5
      builder/dockerfile/dispatchers.go
  5. 6 15
      builder/remotecontext/git/gitutils.go
  6. 2 3
      client/client.go
  7. 3 3
      client/ping.go
  8. 3 5
      cmd/dockerd/daemon.go
  9. 5 5
      daemon/cluster/executor/container/controller.go
  10. 3 3
      daemon/cluster/executor/container/executor.go
  11. 6 6
      daemon/commit.go
  12. 4 4
      daemon/container_operations.go
  13. 13 12
      daemon/daemon_unix.go
  14. 3 3
      daemon/events/testutils/testutils.go
  15. 8 7
      daemon/graphdriver/windows/windows.go
  16. 9 11
      daemon/graphdriver/zfs/zfs_freebsd.go
  17. 1 3
      daemon/info.go
  18. 4 4
      daemon/links/links.go
  19. 2 2
      daemon/logger/journald/internal/sdjournal/sdjournal.go
  20. 2 2
      daemon/logger/loginfo.go
  21. 1 2
      daemon/oci_linux.go
  22. 34 35
      daemon/oci_windows.go
  23. 5 5
      daemon/oci_windows_test.go
  24. 10 10
      integration-cli/docker_cli_build_test.go
  25. 14 17
      integration-cli/docker_cli_links_test.go
  26. 6 10
      integration/internal/container/ops.go
  27. 2 3
      libcontainerd/local/utils_windows.go
  28. 2 2
      libnetwork/datastore/datastore.go
  29. 6 7
      libnetwork/etchosts/etchosts.go
  30. 7 16
      libnetwork/netlabel/labels.go
  31. 5 6
      libnetwork/sandbox_dns_unix.go
  32. 5 7
      opts/address_pools.go
  33. 5 5
      opts/env.go
  34. 26 19
      opts/hosts.go
  35. 3 13
      opts/hosts_test.go
  36. 2 6
      opts/opts.go
  37. 12 10
      opts/runtime.go
  38. 9 12
      pkg/parsers/kernel/kernel_darwin.go
  39. 5 7
      pkg/parsers/operatingsystem/operatingsystem_linux.go
  40. 10 9
      pkg/parsers/parsers.go
  41. 2 2
      pkg/parsers/parsers_test.go
  42. 2 4
      pkg/tarsum/tarsum.go
  43. 5 9
      pkg/tarsum/versioning.go
  44. 4 7
      plugin/defs.go
  45. 3 3
      plugin/v2/settable.go
  46. 2 6
      runconfig/opts/parse.go
  47. 6 10
      volume/local/local.go
  48. 20 35
      volume/mounts/linux_parser.go
  49. 11 13
      volume/mounts/windows_parser.go

+ 3 - 3
api/types/filters/parse.go

@@ -166,13 +166,13 @@ func (args Args) MatchKVList(key string, sources map[string]string) bool {
 	}
 
 	for value := range fieldValues {
-		testKV := strings.SplitN(value, "=", 2)
+		testK, testV, hasValue := strings.Cut(value, "=")
 
-		v, ok := sources[testKV[0]]
+		v, ok := sources[testK]
 		if !ok {
 			return false
 		}
-		if len(testKV) == 2 && testKV[1] != v {
+		if hasValue && testV != v {
 			return false
 		}
 	}

+ 11 - 11
api/types/time/timestamp.go

@@ -105,27 +105,27 @@ func GetTimestamp(value string, reference time.Time) (string, error) {
 //	since := time.Unix(seconds, nanoseconds)
 //
 // returns seconds as defaultSeconds if value == ""
-func ParseTimestamps(value string, defaultSeconds int64) (int64, int64, error) {
+func ParseTimestamps(value string, defaultSeconds int64) (seconds int64, nanoseconds int64, err error) {
 	if value == "" {
 		return defaultSeconds, 0, nil
 	}
 	return parseTimestamp(value)
 }
 
-func parseTimestamp(value string) (int64, int64, error) {
-	sa := strings.SplitN(value, ".", 2)
-	s, err := strconv.ParseInt(sa[0], 10, 64)
+func parseTimestamp(value string) (sec int64, nsec int64, err error) {
+	s, n, ok := strings.Cut(value, ".")
+	sec, err = strconv.ParseInt(s, 10, 64)
 	if err != nil {
-		return s, 0, err
+		return sec, 0, err
 	}
-	if len(sa) != 2 {
-		return s, 0, nil
+	if !ok {
+		return sec, 0, nil
 	}
-	n, err := strconv.ParseInt(sa[1], 10, 64)
+	nsec, err = strconv.ParseInt(n, 10, 64)
 	if err != nil {
-		return s, n, err
+		return sec, nsec, err
 	}
 	// should already be in nanoseconds but just in case convert n to nanoseconds
-	n = int64(float64(n) * math.Pow(float64(10), float64(9-len(sa[1]))))
-	return s, n, nil
+	nsec = int64(float64(nsec) * math.Pow(float64(10), float64(9-len(n))))
+	return sec, nsec, nil
 }

+ 7 - 8
api/types/types.go

@@ -348,20 +348,19 @@ func DecodeSecurityOptions(opts []string) ([]SecurityOpt, error) {
 			continue
 		}
 		secopt := SecurityOpt{}
-		split := strings.Split(opt, ",")
-		for _, s := range split {
-			kv := strings.SplitN(s, "=", 2)
-			if len(kv) != 2 {
+		for _, s := range strings.Split(opt, ",") {
+			k, v, ok := strings.Cut(s, "=")
+			if !ok {
 				return nil, fmt.Errorf("invalid security option %q", s)
 			}
-			if kv[0] == "" || kv[1] == "" {
+			if k == "" || v == "" {
 				return nil, errors.New("invalid empty security option")
 			}
-			if kv[0] == "name" {
-				secopt.Name = kv[1]
+			if k == "name" {
+				secopt.Name = v
 				continue
 			}
-			secopt.Options = append(secopt.Options, KeyValue{Key: kv[0], Value: kv[1]})
+			secopt.Options = append(secopt.Options, KeyValue{Key: k, Value: v})
 		}
 		so = append(so, secopt)
 	}

+ 4 - 5
builder/dockerfile/dispatchers.go

@@ -46,8 +46,7 @@ func dispatchEnv(ctx context.Context, d dispatchRequest, c *instructions.EnvComm
 		commitMessage.WriteString(" " + newVar)
 		gotOne := false
 		for i, envVar := range runConfig.Env {
-			envParts := strings.SplitN(envVar, "=", 2)
-			compareFrom := envParts[0]
+			compareFrom, _, _ := strings.Cut(envVar, "=")
 			if shell.EqualEnvKeys(compareFrom, name) {
 				runConfig.Env[i] = newVar
 				gotOne = true
@@ -408,9 +407,9 @@ func dispatchRun(ctx context.Context, d dispatchRequest, c *instructions.RunComm
 // These args are transparent so resulting image should be the same regardless
 // of the value.
 func prependEnvOnCmd(buildArgs *BuildArgs, buildArgVars []string, cmd strslice.StrSlice) strslice.StrSlice {
-	var tmpBuildEnv []string
+	tmpBuildEnv := make([]string, 0, len(buildArgVars))
 	for _, env := range buildArgVars {
-		key := strings.SplitN(env, "=", 2)[0]
+		key, _, _ := strings.Cut(env, "=")
 		if buildArgs.IsReferencedOrNotBuiltin(key) {
 			tmpBuildEnv = append(tmpBuildEnv, env)
 		}
@@ -418,7 +417,7 @@ func prependEnvOnCmd(buildArgs *BuildArgs, buildArgVars []string, cmd strslice.S
 
 	sort.Strings(tmpBuildEnv)
 	tmpEnv := append([]string{fmt.Sprintf("|%d", len(tmpBuildEnv))}, tmpBuildEnv...)
-	return strslice.StrSlice(append(tmpEnv, cmd...))
+	return append(tmpEnv, cmd...)
 }
 
 // CMD foo

+ 6 - 15
builder/remotecontext/git/gitutils.go

@@ -97,15 +97,10 @@ func parseRemoteURL(remoteURL string) (gitRepo, error) {
 		remoteURL = "https://" + remoteURL
 	}
 
-	var fragment string
 	if strings.HasPrefix(remoteURL, "git@") {
 		// git@.. is not an URL, so cannot be parsed as URL
-		parts := strings.SplitN(remoteURL, "#", 2)
-
-		repo.remote = parts[0]
-		if len(parts) == 2 {
-			fragment = parts[1]
-		}
+		var fragment string
+		repo.remote, fragment, _ = strings.Cut(remoteURL, "#")
 		repo.ref, repo.subdir = getRefAndSubdir(fragment)
 	} else {
 		u, err := url.Parse(remoteURL)
@@ -126,15 +121,11 @@ func parseRemoteURL(remoteURL string) (gitRepo, error) {
 }
 
 func getRefAndSubdir(fragment string) (ref string, subdir string) {
-	refAndDir := strings.SplitN(fragment, ":", 2)
-	ref = "master"
-	if len(refAndDir[0]) != 0 {
-		ref = refAndDir[0]
-	}
-	if len(refAndDir) > 1 && len(refAndDir[1]) != 0 {
-		subdir = refAndDir[1]
+	ref, subdir, _ = strings.Cut(fragment, ":")
+	if ref == "" {
+		ref = "master"
 	}
-	return
+	return ref, subdir
 }
 
 func fetchArgs(remoteURL string, ref string) []string {

+ 2 - 3
client/client.go

@@ -282,13 +282,12 @@ func (cli *Client) HTTPClient() *http.Client {
 // ParseHostURL parses a url string, validates the string is a host url, and
 // returns the parsed URL
 func ParseHostURL(host string) (*url.URL, error) {
-	protoAddrParts := strings.SplitN(host, "://", 2)
-	if len(protoAddrParts) == 1 {
+	proto, addr, ok := strings.Cut(host, "://")
+	if !ok || addr == "" {
 		return nil, errors.Errorf("unable to parse docker host `%s`", host)
 	}
 
 	var basePath string
-	proto, addr := protoAddrParts[0], protoAddrParts[1]
 	if proto == "tcp" {
 		parsed, err := url.Parse("tcp://" + addr)
 		if err != nil {

+ 3 - 3
client/ping.go

@@ -64,10 +64,10 @@ func parsePingResponse(cli *Client, resp serverResponse) (types.Ping, error) {
 		ping.BuilderVersion = types.BuilderVersion(bv)
 	}
 	if si := resp.header.Get("Swarm"); si != "" {
-		parts := strings.SplitN(si, "/", 2)
+		state, role, _ := strings.Cut(si, "/")
 		ping.SwarmStatus = &swarm.Status{
-			NodeState:        swarm.LocalNodeState(parts[0]),
-			ControlAvailable: len(parts) == 2 && parts[1] == "manager",
+			NodeState:        swarm.LocalNodeState(state),
+			ControlAvailable: role == "manager",
 		}
 	}
 	err := cli.checkResponseErr(resp)

+ 3 - 5
cmd/dockerd/daemon.go

@@ -662,13 +662,11 @@ func loadListeners(cli *DaemonCli, serverConfig *apiserver.Config) ([]string, er
 
 	for i := 0; i < len(serverConfig.Hosts); i++ {
 		protoAddr := serverConfig.Hosts[i]
-		protoAddrParts := strings.SplitN(serverConfig.Hosts[i], "://", 2)
-		if len(protoAddrParts) != 2 {
+		proto, addr, ok := strings.Cut(protoAddr, "://")
+		if !ok || addr == "" {
 			return nil, fmt.Errorf("bad format %s, expected PROTO://ADDR", protoAddr)
 		}
 
-		proto, addr := protoAddrParts[0], protoAddrParts[1]
-
 		// It's a bad idea to bind to TCP without tlsverify.
 		authEnabled := serverConfig.TLSConfig != nil && serverConfig.TLSConfig.ClientAuth == tls.RequireAndVerifyClientCert
 		if proto == "tcp" && !authEnabled {
@@ -719,7 +717,7 @@ func loadListeners(cli *DaemonCli, serverConfig *apiserver.Config) ([]string, er
 			return nil, err
 		}
 		logrus.Debugf("Listener created for HTTP on %s (%s)", proto, addr)
-		hosts = append(hosts, protoAddrParts[1])
+		hosts = append(hosts, addr)
 		cli.api.Accept(addr, ls...)
 	}
 

+ 5 - 5
daemon/cluster/executor/container/controller.go

@@ -637,18 +637,18 @@ func parsePortMap(portMap nat.PortMap) ([]*api.PortConfig, error) {
 	exposedPorts := make([]*api.PortConfig, 0, len(portMap))
 
 	for portProtocol, mapping := range portMap {
-		parts := strings.SplitN(string(portProtocol), "/", 2)
-		if len(parts) != 2 {
+		p, proto, ok := strings.Cut(string(portProtocol), "/")
+		if !ok {
 			return nil, fmt.Errorf("invalid port mapping: %s", portProtocol)
 		}
 
-		port, err := strconv.ParseUint(parts[0], 10, 16)
+		port, err := strconv.ParseUint(p, 10, 16)
 		if err != nil {
 			return nil, err
 		}
 
 		var protocol api.PortConfig_Protocol
-		switch strings.ToLower(parts[1]) {
+		switch strings.ToLower(proto) {
 		case "tcp":
 			protocol = api.ProtocolTCP
 		case "udp":
@@ -656,7 +656,7 @@ func parsePortMap(portMap nat.PortMap) ([]*api.PortConfig, error) {
 		case "sctp":
 			protocol = api.ProtocolSCTP
 		default:
-			return nil, fmt.Errorf("invalid protocol: %s", parts[1])
+			return nil, fmt.Errorf("invalid protocol: %s", proto)
 		}
 
 		for _, binding := range mapping {

+ 3 - 3
daemon/cluster/executor/container/executor.go

@@ -114,11 +114,11 @@ func (e *executor) Describe(ctx context.Context) (*api.NodeDescription, error) {
 	// parse []string labels into a map[string]string
 	labels := map[string]string{}
 	for _, l := range info.Labels {
-		stringSlice := strings.SplitN(l, "=", 2)
+		k, v, ok := strings.Cut(l, "=")
 		// this will take the last value in the list for a given key
 		// ideally, one shouldn't assign multiple values to the same key
-		if len(stringSlice) > 1 {
-			labels[stringSlice[0]] = stringSlice[1]
+		if ok {
+			labels[k] = v
 		}
 	}
 

+ 6 - 6
daemon/commit.go

@@ -38,16 +38,16 @@ func merge(userConf, imageConf *containertypes.Config) error {
 	} else {
 		for _, imageEnv := range imageConf.Env {
 			found := false
-			imageEnvKey := strings.Split(imageEnv, "=")[0]
+			imageEnvKey, _, _ := strings.Cut(imageEnv, "=")
 			for _, userEnv := range userConf.Env {
-				userEnvKey := strings.Split(userEnv, "=")[0]
+				userEnvKey, _, _ := strings.Cut(userEnv, "=")
 				if isWindows {
 					// Case insensitive environment variables on Windows
-					imageEnvKey = strings.ToUpper(imageEnvKey)
-					userEnvKey = strings.ToUpper(userEnvKey)
+					found = strings.EqualFold(imageEnvKey, userEnvKey)
+				} else {
+					found = imageEnvKey == userEnvKey
 				}
-				if imageEnvKey == userEnvKey {
-					found = true
+				if found {
 					break
 				}
 			}

+ 4 - 4
daemon/container_operations.go

@@ -107,18 +107,18 @@ func (daemon *Daemon) buildSandboxOptions(container *container.Container) ([]lib
 		if _, err := opts.ValidateExtraHost(extraHost); err != nil {
 			return nil, err
 		}
-		parts := strings.SplitN(extraHost, ":", 2)
+		host, ip, _ := strings.Cut(extraHost, ":")
 		// If the IP Address is a string called "host-gateway", replace this
 		// value with the IP address stored in the daemon level HostGatewayIP
 		// config variable
-		if parts[1] == opts.HostGatewayName {
+		if ip == opts.HostGatewayName {
 			gateway := daemon.configStore.HostGatewayIP.String()
 			if gateway == "" {
 				return nil, fmt.Errorf("unable to derive the IP value for host-gateway")
 			}
-			parts[1] = gateway
+			ip = gateway
 		}
-		sboxOptions = append(sboxOptions, libnetwork.OptionExtraHost(parts[0], parts[1]))
+		sboxOptions = append(sboxOptions, libnetwork.OptionExtraHost(host, ip))
 	}
 
 	if container.HostConfig.PortBindings != nil {

+ 13 - 12
daemon/daemon_unix.go

@@ -215,26 +215,27 @@ func parseSecurityOpt(container *container.Container, config *containertypes.Hos
 			continue
 		}
 
-		var con []string
+		var k, v string
+		var ok bool
 		if strings.Contains(opt, "=") {
-			con = strings.SplitN(opt, "=", 2)
+			k, v, ok = strings.Cut(opt, "=")
 		} else if strings.Contains(opt, ":") {
-			con = strings.SplitN(opt, ":", 2)
+			k, v, ok = strings.Cut(opt, ":")
 			logrus.Warn("Security options with `:` as a separator are deprecated and will be completely unsupported in 17.04, use `=` instead.")
 		}
-		if len(con) != 2 {
+		if !ok {
 			return fmt.Errorf("invalid --security-opt 1: %q", opt)
 		}
 
-		switch con[0] {
+		switch k {
 		case "label":
-			labelOpts = append(labelOpts, con[1])
+			labelOpts = append(labelOpts, v)
 		case "apparmor":
-			container.AppArmorProfile = con[1]
+			container.AppArmorProfile = v
 		case "seccomp":
-			container.SeccompProfile = con[1]
+			container.SeccompProfile = v
 		case "no-new-privileges":
-			noNewPrivileges, err := strconv.ParseBool(con[1])
+			noNewPrivileges, err := strconv.ParseBool(v)
 			if err != nil {
 				return fmt.Errorf("invalid --security-opt 2: %q", opt)
 			}
@@ -1360,8 +1361,8 @@ func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *
 			return errors.Wrapf(err, "could not get container for %s", name)
 		}
 		for child.HostConfig.NetworkMode.IsContainer() {
-			parts := strings.SplitN(string(child.HostConfig.NetworkMode), ":", 2)
-			child, err = daemon.GetContainer(parts[1])
+			cid := child.HostConfig.NetworkMode.ConnectedContainer()
+			child, err = daemon.GetContainer(cid)
 			if err != nil {
 				if errdefs.IsNotFound(err) {
 					// Trying to link to a non-existing container is not valid, and
@@ -1370,7 +1371,7 @@ func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *
 					// image could not be found (see moby/moby#39823)
 					err = errdefs.InvalidParameter(err)
 				}
-				return errors.Wrapf(err, "Could not get container for %s", parts[1])
+				return errors.Wrapf(err, "could not get container for %s", cid)
 			}
 		}
 		if child.HostConfig.NetworkMode.IsHost() {

+ 3 - 3
daemon/events/testutils/testutils.go

@@ -57,9 +57,9 @@ func Scan(text string) (*events.Message, error) {
 	}
 
 	attrs := make(map[string]string)
-	for _, a := range strings.SplitN(md["attributes"], ", ", -1) {
-		kv := strings.SplitN(a, "=", 2)
-		attrs[kv[0]] = kv[1]
+	for _, a := range strings.Split(md["attributes"], ", ") {
+		k, v, _ := strings.Cut(a, "=")
+		attrs[k] = v
 	}
 
 	return &events.Message{

+ 8 - 7
daemon/graphdriver/windows/windows.go

@@ -110,15 +110,16 @@ func InitFilter(home string, options []string, _ idtools.IdentityMapping) (graph
 		return nil, fmt.Errorf("windowsfilter failed to create '%s': %v", home, err)
 	}
 
-	storageOpt := make(map[string]string)
-	storageOpt["size"] = defaultSandboxSize
+	storageOpt := map[string]string{
+		"size": defaultSandboxSize,
+	}
 
-	for _, v := range options {
-		opt := strings.SplitN(v, "=", 2)
-		storageOpt[strings.ToLower(opt[0])] = opt[1]
+	for _, o := range options {
+		k, v, _ := strings.Cut(o, "=")
+		storageOpt[strings.ToLower(k)] = v
 	}
 
-	storageOptions, err := parseStorageOpt(storageOpt)
+	opts, err := parseStorageOpt(storageOpt)
 	if err != nil {
 		return nil, fmt.Errorf("windowsfilter failed to parse default storage options - %s", err)
 	}
@@ -130,7 +131,7 @@ func InitFilter(home string, options []string, _ idtools.IdentityMapping) (graph
 		},
 		cache:              make(map[string]string),
 		ctr:                graphdriver.NewRefCounter(&checker{}),
-		defaultStorageOpts: storageOptions,
+		defaultStorageOpts: opts,
 	}
 	return d, nil
 }

+ 9 - 11
daemon/graphdriver/zfs/zfs_freebsd.go

@@ -1,7 +1,6 @@
 package zfs // import "github.com/docker/docker/daemon/graphdriver/zfs"
 
 import (
-	"fmt"
 	"strings"
 
 	"github.com/docker/docker/daemon/graphdriver"
@@ -12,7 +11,7 @@ import (
 func checkRootdirFs(rootdir string) error {
 	var buf unix.Statfs_t
 	if err := unix.Statfs(rootdir, &buf); err != nil {
-		return fmt.Errorf("Failed to access '%s': %s", rootdir, err)
+		return err
 	}
 
 	// on FreeBSD buf.Fstypename contains ['z', 'f', 's', 0 ... ]
@@ -24,15 +23,14 @@ func checkRootdirFs(rootdir string) error {
 	return nil
 }
 
-func getMountpoint(id string) string {
-	maxlen := 12
-
-	// we need to preserve filesystem suffix
-	suffix := strings.SplitN(id, "-", 2)
+const maxlen = 12
 
-	if len(suffix) > 1 {
-		return id[:maxlen] + "-" + suffix[1]
+func getMountpoint(id string) string {
+	id, suffix, _ := strings.Cut(id, "-")
+	id = id[:maxlen]
+	if suffix != "" {
+		// preserve filesystem suffix.
+		id += "-" + suffix
 	}
-
-	return id[:maxlen]
+	return id
 }

+ 1 - 3
daemon/info.go

@@ -206,9 +206,7 @@ func (daemon *Daemon) fillAPIInfo(v *types.Info) {
 	cfg := daemon.configStore
 	for _, host := range cfg.Hosts {
 		// cnf.Hosts is normalized during startup, so should always have a scheme/proto
-		h := strings.SplitN(host, "://", 2)
-		proto := h[0]
-		addr := h[1]
+		proto, addr, _ := strings.Cut(host, "://")
 		if proto != "tcp" {
 			continue
 		}

+ 4 - 4
daemon/links/links.go

@@ -94,15 +94,15 @@ func (l *Link) ToEnv() []string {
 
 	if l.ChildEnvironment != nil {
 		for _, v := range l.ChildEnvironment {
-			parts := strings.SplitN(v, "=", 2)
-			if len(parts) < 2 {
+			name, val, ok := strings.Cut(v, "=")
+			if !ok {
 				continue
 			}
 			// Ignore a few variables that are added during docker build (and not really relevant to linked containers)
-			if parts[0] == "HOME" || parts[0] == "PATH" {
+			if name == "HOME" || name == "PATH" {
 				continue
 			}
-			env = append(env, fmt.Sprintf("%s_ENV_%s=%s", alias, parts[0], parts[1]))
+			env = append(env, fmt.Sprintf("%s_ENV_%s=%s", alias, name, val))
 		}
 	}
 	return env

+ 2 - 2
daemon/logger/journald/internal/sdjournal/sdjournal.go

@@ -234,8 +234,8 @@ func (j *Journal) Data() (map[string]string, error) {
 			return m, fmt.Errorf("journald: error enumerating entry data: %w", syscall.Errno(-rc))
 		}
 
-		kv := strings.SplitN(C.GoStringN((*C.char)(data), C.int(len)), "=", 2)
-		m[kv[0]] = kv[1]
+		k, v, _ := strings.Cut(C.GoStringN((*C.char)(data), C.int(len)), "=")
+		m[k] = v
 	}
 }
 

+ 2 - 2
daemon/logger/loginfo.go

@@ -59,8 +59,8 @@ func (info *Info) ExtraAttributes(keyMod func(string) string) (map[string]string
 
 	envMapping := make(map[string]string)
 	for _, e := range info.ContainerEnv {
-		if kv := strings.SplitN(e, "=", 2); len(kv) == 2 {
-			envMapping[kv[0]] = kv[1]
+		if k, v, ok := strings.Cut(e, "="); ok {
+			envMapping[k] = v
 		}
 	}
 

+ 1 - 2
daemon/oci_linux.go

@@ -241,8 +241,7 @@ func WithNamespaces(daemon *Daemon, c *container.Container) coci.SpecOpts {
 		// network
 		if !c.Config.NetworkDisabled {
 			ns := specs.LinuxNamespace{Type: "network"}
-			parts := strings.SplitN(string(c.HostConfig.NetworkMode), ":", 2)
-			if parts[0] == "container" {
+			if c.HostConfig.NetworkMode.IsContainer() {
 				nc, err := daemon.getNetworkedContainer(c.ID, c.HostConfig.NetworkMode.ConnectedContainer())
 				if err != nil {
 					return err

+ 34 - 35
daemon/oci_windows.go

@@ -279,29 +279,31 @@ func (daemon *Daemon) setWindowsCredentialSpec(c *container.Container, s *specs.
 	// this doesn't seem like a great idea?
 	credentialSpec := ""
 
+	// TODO(thaJeztah): extract validating and parsing SecurityOpt to a reusable function.
 	for _, secOpt := range c.HostConfig.SecurityOpt {
-		optSplits := strings.SplitN(secOpt, "=", 2)
-		if len(optSplits) != 2 {
+		k, v, ok := strings.Cut(secOpt, "=")
+		if !ok {
 			return errdefs.InvalidParameter(fmt.Errorf("invalid security option: no equals sign in supplied value %s", secOpt))
 		}
-		if !strings.EqualFold(optSplits[0], "credentialspec") {
-			return errdefs.InvalidParameter(fmt.Errorf("security option not supported: %s", optSplits[0]))
+		// FIXME(thaJeztah): options should not be case-insensitive
+		if !strings.EqualFold(k, "credentialspec") {
+			return errdefs.InvalidParameter(fmt.Errorf("security option not supported: %s", k))
 		}
 
-		credSpecSplits := strings.SplitN(optSplits[1], "://", 2)
-		if len(credSpecSplits) != 2 || credSpecSplits[1] == "" {
+		scheme, value, ok := strings.Cut(v, "://")
+		if !ok || value == "" {
 			return errInvalidCredentialSpecSecOpt
 		}
-		value := credSpecSplits[1]
-
 		var err error
-		switch strings.ToLower(credSpecSplits[0]) {
+		switch strings.ToLower(scheme) {
 		case "file":
-			if credentialSpec, err = readCredentialSpecFile(c.ID, daemon.root, filepath.Clean(value)); err != nil {
+			credentialSpec, err = readCredentialSpecFile(c.ID, daemon.root, filepath.Clean(value))
+			if err != nil {
 				return errdefs.InvalidParameter(err)
 			}
 		case "registry":
-			if credentialSpec, err = readCredentialSpecRegistry(c.ID, value); err != nil {
+			credentialSpec, err = readCredentialSpecRegistry(c.ID, value)
+			if err != nil {
 				return errdefs.InvalidParameter(err)
 			}
 		case "config":
@@ -439,44 +441,41 @@ func readCredentialSpecRegistry(id, name string) (string, error) {
 // This allows for staging on machines which do not have the necessary components.
 func readCredentialSpecFile(id, root, location string) (string, error) {
 	if filepath.IsAbs(location) {
-		return "", fmt.Errorf("invalid credential spec - file:// path cannot be absolute")
+		return "", fmt.Errorf("invalid credential spec: file:// path cannot be absolute")
 	}
 	base := filepath.Join(root, credentialSpecFileLocation)
 	full := filepath.Join(base, location)
 	if !strings.HasPrefix(full, base) {
-		return "", fmt.Errorf("invalid credential spec - file:// path must be under %s", base)
+		return "", fmt.Errorf("invalid credential spec: file:// path must be under %s", base)
 	}
 	bcontents, err := os.ReadFile(full)
 	if err != nil {
-		return "", errors.Wrapf(err, "credential spec for container %s could not be read from file %q", id, full)
+		return "", errors.Wrapf(err, "failed to load credential spec for container %s", id)
 	}
 	return string(bcontents[:]), nil
 }
 
 func setupWindowsDevices(devices []containertypes.DeviceMapping) (specDevices []specs.WindowsDevice, err error) {
-	if len(devices) == 0 {
-		return
-	}
-
 	for _, deviceMapping := range devices {
-		devicePath := deviceMapping.PathOnHost
-		if strings.HasPrefix(devicePath, "class/") {
-			devicePath = strings.Replace(devicePath, "class/", "class://", 1)
-		}
-
-		srcParts := strings.SplitN(devicePath, "://", 2)
-		if len(srcParts) != 2 {
-			return nil, errors.Errorf("invalid device assignment path: '%s', must be 'class/ID' or 'IDType://ID'", deviceMapping.PathOnHost)
-		}
-		if srcParts[0] == "" {
-			return nil, errors.Errorf("invalid device assignment path: '%s', IDType cannot be empty", deviceMapping.PathOnHost)
-		}
-		wd := specs.WindowsDevice{
-			ID:     srcParts[1],
-			IDType: srcParts[0],
+		if strings.HasPrefix(deviceMapping.PathOnHost, "class/") {
+			specDevices = append(specDevices, specs.WindowsDevice{
+				ID:     strings.TrimPrefix(deviceMapping.PathOnHost, "class/"),
+				IDType: "class",
+			})
+		} else {
+			idType, id, ok := strings.Cut(deviceMapping.PathOnHost, "://")
+			if !ok {
+				return nil, errors.Errorf("invalid device assignment path: '%s', must be 'class/ID' or 'IDType://ID'", deviceMapping.PathOnHost)
+			}
+			if idType == "" {
+				return nil, errors.Errorf("invalid device assignment path: '%s', IDType cannot be empty", deviceMapping.PathOnHost)
+			}
+			specDevices = append(specDevices, specs.WindowsDevice{
+				ID:     id,
+				IDType: idType,
+			})
 		}
-		specDevices = append(specDevices, wd)
 	}
 
-	return
+	return specDevices, nil
 }

+ 5 - 5
daemon/oci_windows_test.go

@@ -7,6 +7,7 @@ import (
 	"strings"
 	"testing"
 
+	is "gotest.tools/v3/assert/cmp"
 	"gotest.tools/v3/fs"
 
 	containertypes "github.com/docker/docker/api/types/container"
@@ -87,7 +88,7 @@ func TestSetWindowsCredentialSpecInSpec(t *testing.T) {
 		spec := &specs.Spec{}
 
 		err := daemon.setWindowsCredentialSpec(containerFactory(`file://C:\path\to\my\credspec.json`), spec)
-		assert.ErrorContains(t, err, "invalid credential spec - file:// path cannot be absolute")
+		assert.ErrorContains(t, err, "invalid credential spec: file:// path cannot be absolute")
 
 		assert.Check(t, spec.Windows == nil)
 	})
@@ -96,7 +97,7 @@ func TestSetWindowsCredentialSpecInSpec(t *testing.T) {
 		spec := &specs.Spec{}
 
 		err := daemon.setWindowsCredentialSpec(containerFactory(`file://..\credspec.json`), spec)
-		assert.ErrorContains(t, err, fmt.Sprintf("invalid credential spec - file:// path must be under %s", credSpecsDir))
+		assert.ErrorContains(t, err, fmt.Sprintf("invalid credential spec: file:// path must be under %s", credSpecsDir))
 
 		assert.Check(t, spec.Windows == nil)
 	})
@@ -105,9 +106,8 @@ func TestSetWindowsCredentialSpecInSpec(t *testing.T) {
 		spec := &specs.Spec{}
 
 		err := daemon.setWindowsCredentialSpec(containerFactory("file://i-dont-exist.json"), spec)
-		assert.ErrorContains(t, err, fmt.Sprintf("credential spec for container %s could not be read from file", dummyContainerID))
-		assert.ErrorContains(t, err, "The system cannot find")
-
+		assert.Check(t, is.ErrorContains(err, fmt.Sprintf("failed to load credential spec for container %s", dummyContainerID)))
+		assert.Check(t, is.ErrorIs(err, os.ErrNotExist))
 		assert.Check(t, spec.Windows == nil)
 	})
 

+ 10 - 10
integration-cli/docker_cli_build_test.go

@@ -211,21 +211,21 @@ func (s *DockerCLIBuildSuite) TestBuildEnvironmentReplacementEnv(c *testing.T) {
 	envCount := 0
 
 	for _, env := range envResult {
-		parts := strings.SplitN(env, "=", 2)
-		if parts[0] == "bar" {
+		k, v, _ := strings.Cut(env, "=")
+		if k == "bar" {
 			found = true
-			if parts[1] != "zzz" {
-				c.Fatalf("Could not find replaced var for env `bar`: got %q instead of `zzz`", parts[1])
+			if v != "zzz" {
+				c.Fatalf("Could not find replaced var for env `bar`: got %q instead of `zzz`", v)
 			}
-		} else if strings.HasPrefix(parts[0], "env") {
+		} else if strings.HasPrefix(k, "env") {
 			envCount++
-			if parts[1] != "zzz" {
-				c.Fatalf("%s should be 'zzz' but instead its %q", parts[0], parts[1])
+			if v != "zzz" {
+				c.Fatalf("%s should be 'zzz' but instead its %q", k, v)
 			}
-		} else if strings.HasPrefix(parts[0], "env") {
+		} else if strings.HasPrefix(k, "env") {
 			envCount++
-			if parts[1] != "foo" {
-				c.Fatalf("%s should be 'foo' but instead its %q", parts[0], parts[1])
+			if v != "foo" {
+				c.Fatalf("%s should be 'foo' but instead its %q", k, v)
 			}
 		}
 	}

+ 14 - 17
integration-cli/docker_cli_links_test.go

@@ -10,7 +10,7 @@ import (
 
 	"github.com/docker/docker/runconfig"
 	"gotest.tools/v3/assert"
-	"gotest.tools/v3/assert/cmp"
+	is "gotest.tools/v3/assert/cmp"
 )
 
 type DockerCLILinksSuite struct {
@@ -39,11 +39,8 @@ func (s *DockerCLILinksSuite) TestLinksInvalidContainerTarget(c *testing.T) {
 	out, _, err := dockerCmdWithError("run", "--link", "bogus:alias", "busybox", "true")
 
 	// an invalid container target should produce an error
-	assert.Assert(c, err != nil, "out: %s", out)
-	// an invalid container target should produce an error
-	// note: convert the output to lowercase first as the error string
-	// capitalization was changed after API version 1.32
-	assert.Assert(c, strings.Contains(strings.ToLower(out), "could not get container"))
+	assert.Check(c, is.ErrorContains(err, "could not get container for bogus"))
+	assert.Check(c, is.Contains(out, "could not get container"))
 }
 
 func (s *DockerCLILinksSuite) TestLinksPingLinkedContainers(c *testing.T) {
@@ -163,7 +160,7 @@ func (s *DockerCLILinksSuite) TestLinksHostsFilesInject(c *testing.T) {
 	readContainerFileWithExec(c, idOne, "/etc/hosts")
 	contentTwo := readContainerFileWithExec(c, idTwo, "/etc/hosts")
 	// Host is not present in updated hosts file
-	assert.Assert(c, strings.Contains(string(contentTwo), "onetwo"))
+	assert.Assert(c, is.Contains(string(contentTwo), "onetwo"))
 }
 
 func (s *DockerCLILinksSuite) TestLinksUpdateOnRestart(c *testing.T) {
@@ -183,29 +180,29 @@ func (s *DockerCLILinksSuite) TestLinksUpdateOnRestart(c *testing.T) {
 		return string(matches[1])
 	}
 	ip := getIP(content, "one")
-	assert.Equal(c, ip, realIP)
+	assert.Check(c, is.Equal(ip, realIP))
 
 	ip = getIP(content, "onetwo")
-	assert.Equal(c, ip, realIP)
+	assert.Check(c, is.Equal(ip, realIP))
 
 	dockerCmd(c, "restart", "one")
 	realIP = inspectField(c, "one", "NetworkSettings.Networks.bridge.IPAddress")
 
 	content = readContainerFileWithExec(c, id, "/etc/hosts")
 	ip = getIP(content, "one")
-	assert.Equal(c, ip, realIP)
+	assert.Check(c, is.Equal(ip, realIP))
 
 	ip = getIP(content, "onetwo")
-	assert.Equal(c, ip, realIP)
+	assert.Check(c, is.Equal(ip, realIP))
 }
 
 func (s *DockerCLILinksSuite) TestLinksEnvs(c *testing.T) {
 	testRequires(c, DaemonIsLinux)
 	dockerCmd(c, "run", "-d", "-e", "e1=", "-e", "e2=v2", "-e", "e3=v3=v3", "--name=first", "busybox", "top")
 	out, _ := dockerCmd(c, "run", "--name=second", "--link=first:first", "busybox", "env")
-	assert.Assert(c, strings.Contains(out, "FIRST_ENV_e1=\n"))
-	assert.Assert(c, strings.Contains(out, "FIRST_ENV_e2=v2"))
-	assert.Assert(c, strings.Contains(out, "FIRST_ENV_e3=v3=v3"))
+	assert.Assert(c, is.Contains(out, "FIRST_ENV_e1=\n"))
+	assert.Assert(c, is.Contains(out, "FIRST_ENV_e2=v2"))
+	assert.Assert(c, is.Contains(out, "FIRST_ENV_e3=v3=v3"))
 }
 
 func (s *DockerCLILinksSuite) TestLinkShortDefinition(c *testing.T) {
@@ -230,16 +227,16 @@ func (s *DockerCLILinksSuite) TestLinksNetworkHostContainer(c *testing.T) {
 	out, _, err := dockerCmdWithError("run", "--name", "should_fail", "--link", "host_container:tester", "busybox", "true")
 
 	// Running container linking to a container with --net host should have failed
-	assert.Assert(c, err != nil, "out: %s", out)
+	assert.Check(c, err != nil, "out: %s", out)
 	// Running container linking to a container with --net host should have failed
-	assert.Assert(c, strings.Contains(out, runconfig.ErrConflictHostNetworkAndLinks.Error()))
+	assert.Check(c, is.Contains(out, runconfig.ErrConflictHostNetworkAndLinks.Error()))
 }
 
 func (s *DockerCLILinksSuite) TestLinksEtcHostsRegularFile(c *testing.T) {
 	testRequires(c, DaemonIsLinux, NotUserNamespace)
 	out, _ := dockerCmd(c, "run", "--net=host", "busybox", "ls", "-la", "/etc/hosts")
 	// /etc/hosts should be a regular file
-	assert.Assert(c, cmp.Regexp("^-.+\n$", out))
+	assert.Assert(c, is.Regexp("^-.+\n$", out))
 }
 
 func (s *DockerCLILinksSuite) TestLinksMultipleWithSameName(c *testing.T) {

+ 6 - 10
integration/internal/container/ops.go

@@ -1,7 +1,6 @@
 package container
 
 import (
-	"fmt"
 	"strings"
 
 	containertypes "github.com/docker/docker/api/types/container"
@@ -91,23 +90,20 @@ func WithVolume(target string) func(*TestContainerConfig) {
 // WithBind sets the bind mount of the container
 func WithBind(src, target string) func(*TestContainerConfig) {
 	return func(c *TestContainerConfig) {
-		c.HostConfig.Binds = append(c.HostConfig.Binds, fmt.Sprintf("%s:%s", src, target))
+		c.HostConfig.Binds = append(c.HostConfig.Binds, src+":"+target)
 	}
 }
 
-// WithTmpfs sets a target path in the container to a tmpfs
-func WithTmpfs(target string) func(config *TestContainerConfig) {
+// WithTmpfs sets a target path in the container to a tmpfs, with optional options
+// (separated with a colon).
+func WithTmpfs(targetAndOpts string) func(config *TestContainerConfig) {
 	return func(c *TestContainerConfig) {
 		if c.HostConfig.Tmpfs == nil {
 			c.HostConfig.Tmpfs = make(map[string]string)
 		}
 
-		spec := strings.SplitN(target, ":", 2)
-		var opts string
-		if len(spec) > 1 {
-			opts = spec[1]
-		}
-		c.HostConfig.Tmpfs[spec[0]] = opts
+		target, opts, _ := strings.Cut(targetAndOpts, ":")
+		c.HostConfig.Tmpfs[target] = opts
 	}
 }
 

+ 2 - 3
libcontainerd/local/utils_windows.go

@@ -7,9 +7,8 @@ import "strings"
 func setupEnvironmentVariables(a []string) map[string]string {
 	r := make(map[string]string)
 	for _, s := range a {
-		arr := strings.SplitN(s, "=", 2)
-		if len(arr) == 2 {
-			r[arr[0]] = arr[1]
+		if k, v, ok := strings.Cut(s, "="); ok {
+			r[k] = v
 		}
 	}
 	return r

+ 2 - 2
libnetwork/datastore/datastore.go

@@ -222,12 +222,12 @@ func newClient(scope string, kv string, addr string, config *store.Config, cache
 		}
 	}
 
-	store, err := libkv.NewStore(store.Backend(kv), addrs, config)
+	s, err := libkv.NewStore(store.Backend(kv), addrs, config)
 	if err != nil {
 		return nil, err
 	}
 
-	ds := &datastore{scope: scope, store: store, active: true, watchCh: make(chan struct{}), sequential: sequential}
+	ds := &datastore{scope: scope, store: s, active: true, watchCh: make(chan struct{}), sequential: sequential}
 	if cached {
 		ds.cache = newCache(ds)
 	}

+ 6 - 7
libnetwork/etchosts/etchosts.go

@@ -75,20 +75,19 @@ func Build(path, IP, hostname, domainname string, extraContent []Record) error {
 
 	content := bytes.NewBuffer(nil)
 	if IP != "" {
-		//set main record
+		// set main record
 		var mainRec Record
 		mainRec.IP = IP
 		// User might have provided a FQDN in hostname or split it across hostname
 		// and domainname.  We want the FQDN and the bare hostname.
 		fqdn := hostname
 		if domainname != "" {
-			fqdn = fmt.Sprintf("%s.%s", fqdn, domainname)
+			fqdn += "." + domainname
 		}
-		parts := strings.SplitN(fqdn, ".", 2)
-		if len(parts) == 2 {
-			mainRec.Hosts = fmt.Sprintf("%s %s", fqdn, parts[0])
-		} else {
-			mainRec.Hosts = fqdn
+		mainRec.Hosts = fqdn
+
+		if hostName, _, ok := strings.Cut(fqdn, "."); ok {
+			mainRec.Hosts += " " + hostName
 		}
 		if _, err := mainRec.WriteTo(content); err != nil {
 			return err

+ 7 - 16
libnetwork/netlabel/labels.go

@@ -30,7 +30,7 @@ const (
 	// DNSServers A list of DNS servers associated with the endpoint
 	DNSServers = Prefix + ".endpoint.dnsservers"
 
-	//EnableIPv6 constant represents enabling IPV6 at network level
+	// EnableIPv6 constant represents enabling IPV6 at network level
 	EnableIPv6 = Prefix + ".enable_ipv6"
 
 	// DriverMTU constant represents the MTU size for the network driver
@@ -106,27 +106,18 @@ func MakeKVClient(scope string) string {
 
 // Key extracts the key portion of the label
 func Key(label string) (key string) {
-	if kv := strings.SplitN(label, "=", 2); len(kv) > 0 {
-		key = kv[0]
-	}
-	return
+	key, _, _ = strings.Cut(label, "=")
+	return key
 }
 
 // Value extracts the value portion of the label
 func Value(label string) (value string) {
-	if kv := strings.SplitN(label, "=", 2); len(kv) > 1 {
-		value = kv[1]
-	}
-	return
+	_, value, _ = strings.Cut(label, "=")
+	return value
 }
 
 // KeyValue decomposes the label in the (key,value) pair
 func KeyValue(label string) (key string, value string) {
-	if kv := strings.SplitN(label, "=", 2); len(kv) > 0 {
-		key = kv[0]
-		if len(kv) > 1 {
-			value = kv[1]
-		}
-	}
-	return
+	key, value, _ = strings.Cut(label, "=")
+	return key, value
 }

+ 5 - 6
libnetwork/sandbox_dns_unix.go

@@ -110,19 +110,18 @@ func (sb *sandbox) updateHostsFile(ifaceIPs []string) error {
 	// User might have provided a FQDN in hostname or split it across hostname
 	// and domainname.  We want the FQDN and the bare hostname.
 	fqdn := sb.config.hostName
-	mhost := sb.config.hostName
 	if sb.config.domainName != "" {
-		fqdn = fmt.Sprintf("%s.%s", fqdn, sb.config.domainName)
+		fqdn += "." + sb.config.domainName
 	}
+	hosts := fqdn
 
-	parts := strings.SplitN(fqdn, ".", 2)
-	if len(parts) == 2 {
-		mhost = fmt.Sprintf("%s %s", fqdn, parts[0])
+	if hostName, _, ok := strings.Cut(fqdn, "."); ok {
+		hosts += " " + hostName
 	}
 
 	var extraContent []etchosts.Record
 	for _, ip := range ifaceIPs {
-		extraContent = append(extraContent, etchosts.Record{Hosts: mhost, IP: ip})
+		extraContent = append(extraContent, etchosts.Record{Hosts: hosts, IP: ip})
 	}
 
 	sb.addHostsEntries(extraContent)

+ 5 - 7
opts/address_pools.go

@@ -31,19 +31,17 @@ func (p *PoolsOpt) Set(value string) error {
 	poolsDef := types.NetworkToSplit{}
 
 	for _, field := range fields {
-		parts := strings.SplitN(field, "=", 2)
-		if len(parts) != 2 {
+		// TODO(thaJeztah): this should not be case-insensitive.
+		key, val, ok := strings.Cut(strings.ToLower(field), "=")
+		if !ok {
 			return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
 		}
 
-		key := strings.ToLower(parts[0])
-		value := strings.ToLower(parts[1])
-
 		switch key {
 		case "base":
-			poolsDef.Base = value
+			poolsDef.Base = val
 		case "size":
-			size, err := strconv.Atoi(value)
+			size, err := strconv.Atoi(val)
 			if err != nil {
 				return fmt.Errorf("invalid size value: %q (must be integer): %v", value, err)
 			}

+ 5 - 5
opts/env.go

@@ -16,15 +16,15 @@ import (
 //
 // The only validation here is to check if name is empty, per #25099
 func ValidateEnv(val string) (string, error) {
-	arr := strings.SplitN(val, "=", 2)
-	if arr[0] == "" {
+	k, _, ok := strings.Cut(val, "=")
+	if k == "" {
 		return "", errors.New("invalid environment variable: " + val)
 	}
-	if len(arr) > 1 {
+	if ok {
 		return val, nil
 	}
-	if envVal, ok := os.LookupEnv(arr[0]); ok {
-		return arr[0] + "=" + envVal, nil
+	if envVal, ok := os.LookupEnv(k); ok {
+		return k + "=" + envVal, nil
 	}
 	return val, nil
 }

+ 26 - 19
opts/hosts.go

@@ -60,8 +60,7 @@ func ParseHost(defaultToTLS, defaultToUnixXDG bool, val string) (string, error)
 			if err != nil {
 				return "", err
 			}
-			socket := filepath.Join(runtimeDir, "docker.sock")
-			host = "unix://" + socket
+			host = "unix://" + filepath.Join(runtimeDir, "docker.sock")
 		} else {
 			host = DefaultHost
 		}
@@ -77,23 +76,32 @@ func ParseHost(defaultToTLS, defaultToUnixXDG bool, val string) (string, error)
 
 // parseDaemonHost parses the specified address and returns an address that will be used as the host.
 // Depending on the address specified, this may return one of the global Default* strings defined in hosts.go.
-func parseDaemonHost(addr string) (string, error) {
-	addrParts := strings.SplitN(addr, "://", 2)
-	if len(addrParts) == 1 && addrParts[0] != "" {
-		addrParts = []string{"tcp", addrParts[0]}
+func parseDaemonHost(address string) (string, error) {
+	proto, addr, ok := strings.Cut(address, "://")
+	if !ok && proto != "" {
+		addr = proto
+		proto = "tcp"
 	}
 
-	switch addrParts[0] {
+	switch proto {
 	case "tcp":
-		return ParseTCPAddr(addr, DefaultTCPHost)
+		return ParseTCPAddr(address, DefaultTCPHost)
 	case "unix":
-		return parseSimpleProtoAddr("unix", addrParts[1], DefaultUnixSocket)
+		a, err := parseSimpleProtoAddr(proto, addr, DefaultUnixSocket)
+		if err != nil {
+			return "", errors.Wrapf(err, "invalid bind address (%s)", address)
+		}
+		return a, nil
 	case "npipe":
-		return parseSimpleProtoAddr("npipe", addrParts[1], DefaultNamedPipe)
+		a, err := parseSimpleProtoAddr(proto, addr, DefaultNamedPipe)
+		if err != nil {
+			return "", errors.Wrapf(err, "invalid bind address (%s)", address)
+		}
+		return a, nil
 	case "fd":
-		return addr, nil
+		return address, nil
 	default:
-		return "", errors.Errorf("invalid bind address (%s): unsupported proto '%s'", addr, addrParts[0])
+		return "", errors.Errorf("invalid bind address (%s): unsupported proto '%s'", address, proto)
 	}
 }
 
@@ -102,9 +110,8 @@ func parseDaemonHost(addr string) (string, error) {
 // socket address, either using the address parsed from addr, or the contents of
 // defaultAddr if addr is a blank string.
 func parseSimpleProtoAddr(proto, addr, defaultAddr string) (string, error) {
-	addr = strings.TrimPrefix(addr, proto+"://")
 	if strings.Contains(addr, "://") {
-		return "", errors.Errorf("invalid proto, expected %s: %s", proto, addr)
+		return "", errors.Errorf("invalid %s address: %s", proto, addr)
 	}
 	if addr == "" {
 		addr = defaultAddr
@@ -172,14 +179,14 @@ func parseTCPAddr(address string, strict bool) (*url.URL, error) {
 // ExtraHost is in the form of name:ip where the ip has to be a valid ip (IPv4 or IPv6).
 func ValidateExtraHost(val string) (string, error) {
 	// allow for IPv6 addresses in extra hosts by only splitting on first ":"
-	arr := strings.SplitN(val, ":", 2)
-	if len(arr) != 2 || len(arr[0]) == 0 {
+	name, ip, ok := strings.Cut(val, ":")
+	if !ok || name == "" {
 		return "", errors.Errorf("bad format for add-host: %q", val)
 	}
 	// Skip IPaddr validation for special "host-gateway" string
-	if arr[1] != HostGatewayName {
-		if _, err := ValidateIPAddress(arr[1]); err != nil {
-			return "", errors.Errorf("invalid IP address in add-host: %q", arr[1])
+	if ip != HostGatewayName {
+		if _, err := ValidateIPAddress(ip); err != nil {
+			return "", errors.Errorf("invalid IP address in add-host: %q", ip)
 		}
 	}
 	return val, nil

+ 3 - 13
opts/hosts_test.go

@@ -85,6 +85,8 @@ func TestParseDockerDaemonHost(t *testing.T) {
 		"[0:0:0:0:0:0:0:1]:5555/path":   "invalid bind address ([0:0:0:0:0:0:0:1]:5555/path): should not contain a path element",
 		"tcp://:5555/path":              "invalid bind address (tcp://:5555/path): should not contain a path element",
 		"localhost:5555/path":           "invalid bind address (localhost:5555/path): should not contain a path element",
+		"unix://tcp://127.0.0.1":        "invalid bind address (unix://tcp://127.0.0.1): invalid unix address: tcp://127.0.0.1",
+		"unix://unix://tcp://127.0.0.1": "invalid bind address (unix://unix://tcp://127.0.0.1): invalid unix address: unix://tcp://127.0.0.1",
 	}
 	valids := map[string]string{
 		":":                       DefaultTCPHost,
@@ -130,7 +132,7 @@ func TestParseDockerDaemonHost(t *testing.T) {
 				t.Errorf(`unexpected error: "%v"`, err)
 			}
 			if addr != expectedAddr {
-				t.Errorf(`expected "%s", got "%s""`, expectedAddr, addr)
+				t.Errorf(`expected "%s", got "%s"`, expectedAddr, addr)
 			}
 		})
 	}
@@ -210,18 +212,6 @@ func TestParseTCP(t *testing.T) {
 	}
 }
 
-func TestParseInvalidUnixAddrInvalid(t *testing.T) {
-	if _, err := parseSimpleProtoAddr("unix", "tcp://127.0.0.1", "unix:///var/run/docker.sock"); err == nil || err.Error() != "invalid proto, expected unix: tcp://127.0.0.1" {
-		t.Fatalf("Expected an error, got %v", err)
-	}
-	if _, err := parseSimpleProtoAddr("unix", "unix://tcp://127.0.0.1", "/var/run/docker.sock"); err == nil || err.Error() != "invalid proto, expected unix: tcp://127.0.0.1" {
-		t.Fatalf("Expected an error, got %v", err)
-	}
-	if v, err := parseSimpleProtoAddr("unix", "", "/var/run/docker.sock"); err != nil || v != "unix:///var/run/docker.sock" {
-		t.Fatalf("Expected an %v, got %v", v, "unix:///var/run/docker.sock")
-	}
-}
-
 func TestValidateExtraHosts(t *testing.T) {
 	valid := []string{
 		`myhost:192.168.0.1`,

+ 2 - 6
opts/opts.go

@@ -162,12 +162,8 @@ func (opts *MapOpts) Set(value string) error {
 		}
 		value = v
 	}
-	vals := strings.SplitN(value, "=", 2)
-	if len(vals) == 1 {
-		(opts.values)[vals[0]] = ""
-	} else {
-		(opts.values)[vals[0]] = vals[1]
-	}
+	k, v, _ := strings.Cut(value, "=")
+	(opts.values)[k] = v
 	return nil
 }
 

+ 12 - 10
opts/runtime.go

@@ -29,27 +29,29 @@ func (o *RuntimeOpt) Name() string {
 
 // Set validates and updates the list of Runtimes
 func (o *RuntimeOpt) Set(val string) error {
-	parts := strings.SplitN(val, "=", 2)
-	if len(parts) != 2 {
+	k, v, ok := strings.Cut(val, "=")
+	if !ok {
 		return fmt.Errorf("invalid runtime argument: %s", val)
 	}
 
-	parts[0] = strings.TrimSpace(parts[0])
-	parts[1] = strings.TrimSpace(parts[1])
-	if parts[0] == "" || parts[1] == "" {
+	// TODO(thaJeztah): this should not accept spaces.
+	k = strings.TrimSpace(k)
+	v = strings.TrimSpace(v)
+	if k == "" || v == "" {
 		return fmt.Errorf("invalid runtime argument: %s", val)
 	}
 
-	parts[0] = strings.ToLower(parts[0])
-	if parts[0] == o.stockRuntimeName {
+	// TODO(thaJeztah): this should not be case-insensitive.
+	k = strings.ToLower(k)
+	if k == o.stockRuntimeName {
 		return fmt.Errorf("runtime name '%s' is reserved", o.stockRuntimeName)
 	}
 
-	if _, ok := (*o.values)[parts[0]]; ok {
-		return fmt.Errorf("runtime '%s' was already defined", parts[0])
+	if _, ok := (*o.values)[k]; ok {
+		return fmt.Errorf("runtime '%s' was already defined", k)
 	}
 
-	(*o.values)[parts[0]] = types.Runtime{Path: parts[1]}
+	(*o.values)[k] = types.Runtime{Path: v}
 
 	return nil
 }

+ 9 - 12
pkg/parsers/kernel/kernel_darwin.go

@@ -26,27 +26,24 @@ func GetKernelVersion() (*VersionInfo, error) {
 
 // getRelease uses `system_profiler SPSoftwareDataType` to get OSX kernel version
 func getRelease(osName string) (string, error) {
-	var release string
-	data := strings.Split(osName, "\n")
-	for _, line := range data {
+	for _, line := range strings.Split(osName, "\n") {
 		if !strings.Contains(line, "Kernel Version") {
 			continue
 		}
 		// It has the format like '      Kernel Version: Darwin 14.5.0'
-		content := strings.SplitN(line, ":", 2)
-		if len(content) != 2 {
-			return "", fmt.Errorf("Kernel Version is invalid")
+		_, ver, ok := strings.Cut(line, ":")
+		if !ok {
+			return "", fmt.Errorf("kernel Version is invalid")
 		}
 
-		prettyNames := strings.SplitN(strings.TrimSpace(content[1]), " ", 2)
-
-		if len(prettyNames) != 2 {
-			return "", fmt.Errorf("Kernel Version needs to be 'Darwin x.x.x' ")
+		_, release, ok := strings.Cut(strings.TrimSpace(ver), " ")
+		if !ok {
+			return "", fmt.Errorf("kernel version needs to be 'Darwin x.x.x'")
 		}
-		release = prettyNames[1]
+		return release, nil
 	}
 
-	return release, nil
+	return "", nil
 }
 
 func getSPSoftwareDataType() (string, error) {

+ 5 - 7
pkg/parsers/operatingsystem/operatingsystem_linux.go

@@ -5,7 +5,6 @@ package operatingsystem // import "github.com/docker/docker/pkg/parsers/operatin
 import (
 	"bufio"
 	"bytes"
-	"fmt"
 	"os"
 	"strings"
 )
@@ -44,23 +43,22 @@ func getValueFromOsRelease(key string) (string, error) {
 	osReleaseFile, err := os.Open(etcOsRelease)
 	if err != nil {
 		if !os.IsNotExist(err) {
-			return "", fmt.Errorf("Error opening %s: %v", etcOsRelease, err)
+			return "", err
 		}
 		osReleaseFile, err = os.Open(altOsRelease)
 		if err != nil {
-			return "", fmt.Errorf("Error opening %s: %v", altOsRelease, err)
+			return "", err
 		}
 	}
 	defer osReleaseFile.Close()
 
 	var value string
-	keyWithTrailingEqual := key + "="
 	scanner := bufio.NewScanner(osReleaseFile)
 	for scanner.Scan() {
 		line := scanner.Text()
-		if strings.HasPrefix(line, keyWithTrailingEqual) {
-			data := strings.SplitN(line, "=", 2)
-			value = strings.Trim(data[1], `"' `) // remove leading/trailing quotes and whitespace
+		if strings.HasPrefix(line, key+"=") {
+			value = strings.TrimPrefix(line, key+"=")
+			value = strings.Trim(value, `"' `) // remove leading/trailing quotes and whitespace
 		}
 	}
 

+ 10 - 9
pkg/parsers/parsers.go

@@ -9,13 +9,14 @@ import (
 	"strings"
 )
 
-// ParseKeyValueOpt parses and validates the specified string as a key/value pair (key=value)
-func ParseKeyValueOpt(opt string) (string, string, error) {
-	parts := strings.SplitN(opt, "=", 2)
-	if len(parts) != 2 {
-		return "", "", fmt.Errorf("Unable to parse key/value option: %s", opt)
+// ParseKeyValueOpt parses and validates the specified string as a key/value
+// pair (key=value).
+func ParseKeyValueOpt(opt string) (key string, value string, err error) {
+	k, v, ok := strings.Cut(opt, "=")
+	if !ok {
+		return "", "", fmt.Errorf("unable to parse key/value option: %s", opt)
 	}
-	return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil
+	return strings.TrimSpace(k), strings.TrimSpace(v), nil
 }
 
 // ParseUintListMaximum parses and validates the specified string as the value
@@ -75,12 +76,12 @@ func parseUintList(val string, maximum int) (map[int]bool, error) {
 			}
 			availableInts[v] = true
 		} else {
-			split := strings.SplitN(r, "-", 2)
-			min, err := strconv.Atoi(split[0])
+			minS, maxS, _ := strings.Cut(r, "-")
+			min, err := strconv.Atoi(minS)
 			if err != nil {
 				return nil, errInvalidFormat
 			}
-			max, err := strconv.Atoi(split[1])
+			max, err := strconv.Atoi(maxS)
 			if err != nil {
 				return nil, errInvalidFormat
 			}

+ 2 - 2
pkg/parsers/parsers_test.go

@@ -7,8 +7,8 @@ import (
 
 func TestParseKeyValueOpt(t *testing.T) {
 	invalids := map[string]string{
-		"":    "Unable to parse key/value option: ",
-		"key": "Unable to parse key/value option: key",
+		"":    "unable to parse key/value option: ",
+		"key": "unable to parse key/value option: key",
 	}
 	for invalid, expectedError := range invalids {
 		if _, _, err := ParseKeyValueOpt(invalid); err == nil || err.Error() != expectedError {

+ 2 - 4
pkg/tarsum/tarsum.go

@@ -62,13 +62,11 @@ func NewTarSumHash(r io.Reader, dc bool, v Version, tHash THash) (TarSum, error)
 
 // NewTarSumForLabel creates a new TarSum using the provided TarSum version+hash label.
 func NewTarSumForLabel(r io.Reader, disableCompression bool, label string) (TarSum, error) {
-	parts := strings.SplitN(label, "+", 2)
-	if len(parts) != 2 {
+	versionName, hashName, ok := strings.Cut(label, "+")
+	if !ok {
 		return nil, errors.New("tarsum label string should be of the form: {tarsum_version}+{hash_name}")
 	}
 
-	versionName, hashName := parts[0], parts[1]
-
 	version, ok := tarSumVersionsByName[versionName]
 	if !ok {
 		return nil, fmt.Errorf("unknown TarSum version name: %q", versionName)

+ 5 - 9
pkg/tarsum/versioning.go

@@ -69,16 +69,12 @@ func (tsv Version) String() string {
 
 // GetVersionFromTarsum returns the Version from the provided string.
 func GetVersionFromTarsum(tarsum string) (Version, error) {
-	tsv := tarsum
-	if strings.Contains(tarsum, "+") {
-		tsv = strings.SplitN(tarsum, "+", 2)[0]
-	}
-	for v, s := range tarSumVersions {
-		if s == tsv {
-			return v, nil
-		}
+	versionName, _, _ := strings.Cut(tarsum, "+")
+	version, ok := tarSumVersionsByName[versionName]
+	if !ok {
+		return -1, ErrNotVersion
 	}
-	return -1, ErrNotVersion
+	return version, nil
 }
 
 // Errors that may be returned by functions in this package

+ 4 - 7
plugin/defs.go

@@ -1,7 +1,6 @@
 package plugin // import "github.com/docker/docker/plugin"
 
 import (
-	"fmt"
 	"strings"
 	"sync"
 
@@ -56,15 +55,13 @@ func WithEnv(env []string) CreateOpt {
 			}
 		}
 		for _, line := range env {
-			if pair := strings.SplitN(line, "=", 2); len(pair) > 1 {
-				effectiveEnv[pair[0]] = pair[1]
+			if k, v, ok := strings.Cut(line, "="); ok {
+				effectiveEnv[k] = v
 			}
 		}
-		p.PluginObj.Settings.Env = make([]string, len(effectiveEnv))
-		i := 0
+		p.PluginObj.Settings.Env = make([]string, 0, len(effectiveEnv))
 		for key, value := range effectiveEnv {
-			p.PluginObj.Settings.Env[i] = fmt.Sprintf("%s=%s", key, value)
-			i++
+			p.PluginObj.Settings.Env = append(p.PluginObj.Settings.Env, key+"="+value)
 		}
 	}
 }

+ 3 - 3
plugin/v2/settable.go

@@ -92,11 +92,11 @@ func (set *settable) isSettable(allowedSettableFields []string, settable []strin
 
 func updateSettingsEnv(env *[]string, set *settable) {
 	for i, e := range *env {
-		if parts := strings.SplitN(e, "=", 2); parts[0] == set.name {
-			(*env)[i] = fmt.Sprintf("%s=%s", set.name, set.value)
+		if name, _, _ := strings.Cut(e, "="); name == set.name {
+			(*env)[i] = set.name + "=" + set.value
 			return
 		}
 	}
 
-	*env = append(*env, fmt.Sprintf("%s=%s", set.name, set.value))
+	*env = append(*env, set.name+"="+set.value)
 }

+ 2 - 6
runconfig/opts/parse.go

@@ -8,12 +8,8 @@ import (
 func ConvertKVStringsToMap(values []string) map[string]string {
 	result := make(map[string]string, len(values))
 	for _, value := range values {
-		kv := strings.SplitN(value, "=", 2)
-		if len(kv) == 1 {
-			result[kv[0]] = ""
-		} else {
-			result[kv[0]] = kv[1]
-		}
+		k, v, _ := strings.Cut(value, "=")
+		result[k] = v
 	}
 
 	return result

+ 6 - 10
volume/local/local.go

@@ -371,11 +371,9 @@ func (v *localVolume) saveOpts() error {
 
 // getAddress finds out address/hostname from options
 func getAddress(opts string) string {
-	optsList := strings.Split(opts, ",")
-	for i := 0; i < len(optsList); i++ {
-		if strings.HasPrefix(optsList[i], "addr=") {
-			addr := strings.SplitN(optsList[i], "=", 2)[1]
-			return addr
+	for _, opt := range strings.Split(opts, ",") {
+		if strings.HasPrefix(opt, "addr=") {
+			return strings.TrimPrefix(opt, "addr=")
 		}
 	}
 	return ""
@@ -383,11 +381,9 @@ func getAddress(opts string) string {
 
 // getPassword finds out a password from options
 func getPassword(opts string) string {
-	optsList := strings.Split(opts, ",")
-	for i := 0; i < len(optsList); i++ {
-		if strings.HasPrefix(optsList[i], "password=") {
-			passwd := strings.SplitN(optsList[i], "=", 2)[1]
-			return passwd
+	for _, opt := range strings.Split(opts, ",") {
+		if strings.HasPrefix(opt, "password=") {
+			return strings.TrimPrefix(opt, "password=")
 		}
 	}
 	return ""

+ 20 - 35
volume/mounts/linux_parser.go

@@ -23,18 +23,6 @@ type linuxParser struct {
 	fi fileInfoProvider
 }
 
-func linuxSplitRawSpec(raw string) ([]string, error) {
-	if strings.Count(raw, ":") > 2 {
-		return nil, errInvalidSpec(raw)
-	}
-
-	arr := strings.SplitN(raw, ":", 3)
-	if arr[0] == "" {
-		return nil, errInvalidSpec(raw)
-	}
-	return arr, nil
-}
-
 func linuxValidateNotRoot(p string) error {
 	p = path.Clean(strings.ReplaceAll(p, `\`, `/`))
 	if p == "/" {
@@ -214,9 +202,9 @@ func (p *linuxParser) ReadWrite(mode string) bool {
 }
 
 func (p *linuxParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
-	arr, err := linuxSplitRawSpec(raw)
-	if err != nil {
-		return nil, err
+	arr := strings.SplitN(raw, ":", 4)
+	if arr[0] == "" {
+		return nil, errInvalidSpec(raw)
 	}
 
 	var spec mount.Mount
@@ -334,26 +322,23 @@ func (p *linuxParser) ParseVolumesFrom(spec string) (string, string, error) {
 		return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
 	}
 
-	specParts := strings.SplitN(spec, ":", 2)
-	id := specParts[0]
-	mode := "rw"
-
-	if len(specParts) == 2 {
-		mode = specParts[1]
-		if !linuxValidMountMode(mode) {
-			return "", "", errInvalidMode(mode)
-		}
-		// For now don't allow propagation properties while importing
-		// volumes from data container. These volumes will inherit
-		// the same propagation property as of the original volume
-		// in data container. This probably can be relaxed in future.
-		if linuxHasPropagation(mode) {
-			return "", "", errInvalidMode(mode)
-		}
-		// Do not allow copy modes on volumes-from
-		if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
-			return "", "", errInvalidMode(mode)
-		}
+	id, mode, _ := strings.Cut(spec, ":")
+	if mode == "" {
+		return id, "rw", nil
+	}
+	if !linuxValidMountMode(mode) {
+		return "", "", errInvalidMode(mode)
+	}
+	// For now don't allow propagation properties while importing
+	// volumes from data container. These volumes will inherit
+	// the same propagation property as of the original volume
+	// in data container. This probably can be relaxed in future.
+	if linuxHasPropagation(mode) {
+		return "", "", errInvalidMode(mode)
+	}
+	// Do not allow copy modes on volumes-from
+	if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
+		return "", "", errInvalidMode(mode)
 	}
 	return id, mode, nil
 }

+ 11 - 13
volume/mounts/windows_parser.go

@@ -415,20 +415,18 @@ func (p *windowsParser) ParseVolumesFrom(spec string) (string, string, error) {
 		return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
 	}
 
-	specParts := strings.SplitN(spec, ":", 2)
-	id := specParts[0]
-	mode := "rw"
-
-	if len(specParts) == 2 {
-		mode = specParts[1]
-		if !windowsValidMountMode(mode) {
-			return "", "", errInvalidMode(mode)
-		}
+	id, mode, _ := strings.Cut(spec, ":")
+	if mode == "" {
+		return id, "rw", nil
+	}
 
-		// Do not allow copy modes on volumes-from
-		if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
-			return "", "", errInvalidMode(mode)
-		}
+	if !windowsValidMountMode(mode) {
+		return "", "", errInvalidMode(mode)
+	}
+
+	// Do not allow copy modes on volumes-from
+	if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
+		return "", "", errInvalidMode(mode)
 	}
 	return id, mode, nil
 }