From e3797d95b18abfec25dd308c1893a4b7d5479c9b Mon Sep 17 00:00:00 2001 From: Vishnu Kothakapu Date: Fri, 14 Aug 2026 15:40:13 +0530 Subject: [PATCH] Performance: Use delayed regex compilation Replaces regexp.MustCompile with regexp.Delayed from go.podman.io/storage/pkg/regexp for global regular expressions. This avoids compiling regular expressions during the global init scope, improving startup performance for all commands. Tests are kept using regexp.MustCompile where applicable. Fixes: #29510 Signed-off-by: Vishnu Kothakapu --- cmd/podman-mac-helper/main.go | 5 ++--- cmd/podman/images/trust_set.go | 14 +++++++++----- cmd/podman/inspect/inspect.go | 8 +++++--- cmd/podman/machine/machine.go | 8 +++++--- libpod/oci_util.go | 15 +++++++++++---- pkg/annotations/validate.go | 7 ++++--- pkg/machine/vmconfigs/volumes_windows.go | 12 ++++++++---- pkg/ps/ps.go | 10 ++++++---- pkg/specgen/generate/kube/kube.go | 13 +++++++------ pkg/systemd/quadlet/unitdirs.go | 15 +++++++++++++-- 10 files changed, 70 insertions(+), 37 deletions(-) diff --git a/cmd/podman-mac-helper/main.go b/cmd/podman-mac-helper/main.go index bab90a1dab..9762a0f7e2 100644 --- a/cmd/podman-mac-helper/main.go +++ b/cmd/podman-mac-helper/main.go @@ -8,7 +8,6 @@ import ( "io" "os" "os/exec" - "regexp" "strconv" "strings" @@ -114,8 +113,8 @@ func runDetectErr(name string, args ...string) error { if err == nil { errString := readCapped(errReader) if len(errString) > 0 { - re := regexp.MustCompile(`\r?\n`) - err = errors.New(re.ReplaceAllString(errString, ": ")) + errString = strings.ReplaceAll(errString, "\r\n", ": ") + err = errors.New(strings.ReplaceAll(errString, "\n", ": ")) } } diff --git a/cmd/podman/images/trust_set.go b/cmd/podman/images/trust_set.go index abb7f7bc78..a4a1353e83 100644 --- a/cmd/podman/images/trust_set.go +++ b/cmd/podman/images/trust_set.go @@ -3,9 +3,10 @@ package images import ( "fmt" "net/url" - "regexp" "slices" + "go.podman.io/storage/pkg/regexp" + "github.com/spf13/cobra" "go.podman.io/common/pkg/completion" "go.podman.io/podman/v6/cmd/podman/common" @@ -65,6 +66,11 @@ func setTrust(_ *cobra.Command, args []string) error { return registry.ImageEngine().SetTrust(registry.Context(), args, setOptions) } +var ( + imageURIRegexHost = regexp.Delayed(`^[a-zA-Z0-9-_\.]+\/?:?[0-9]*[a-z0-9-\/:]*$`) + imageURIRegexFragment = regexp.Delayed(`^[a-z0-9-:\./]*$`) +) + // isValidImageURI checks if image name has valid format func isValidImageURI(imguri string) (bool, error) { uri := "http://" + imguri @@ -72,13 +78,11 @@ func isValidImageURI(imguri string) (bool, error) { if err != nil { return false, fmt.Errorf("invalid image uri: %s: %w", imguri, err) } - reg := regexp.MustCompile(`^[a-zA-Z0-9-_\.]+\/?:?[0-9]*[a-z0-9-\/:]*$`) - ret := reg.FindAllString(u.Host, -1) + ret := imageURIRegexHost.FindAllString(u.Host, -1) if len(ret) == 0 { return false, fmt.Errorf("invalid image uri: %s: %w", imguri, err) } - reg = regexp.MustCompile(`^[a-z0-9-:\./]*$`) - ret = reg.FindAllString(u.Fragment, -1) + ret = imageURIRegexFragment.FindAllString(u.Fragment, -1) if len(ret) == 0 { return false, fmt.Errorf("invalid image uri: %s: %w", imguri, err) } diff --git a/cmd/podman/inspect/inspect.go b/cmd/podman/inspect/inspect.go index 50bee0f476..7459967a57 100644 --- a/cmd/podman/inspect/inspect.go +++ b/cmd/podman/inspect/inspect.go @@ -5,9 +5,10 @@ import ( "errors" "fmt" "os" - "regexp" "strings" + "go.podman.io/storage/pkg/regexp" + "github.com/spf13/cobra" "go.podman.io/common/pkg/report" "go.podman.io/podman/v6/cmd/podman/common" @@ -258,6 +259,8 @@ func (i *inspector) inspectAll(ctx context.Context, namesOrIDs []string) ([]any, return data, allErrs, nil } +var idRegex = regexp.Delayed(`{{\s*\.Id\s*}}`) + // InspectNormalize modifies a given row string based on the specified inspect type. // It replaces specific field names within the row string for standardization. // For the `image` inspect type, it includes additional field replacements like `.Config.Healthcheck`. @@ -274,8 +277,7 @@ func (i *inspector) inspectAll(ctx context.Context, namesOrIDs []string) ([]any, // fetching it itself. // The reason why we did it in this way can be further read [here](https://github.com/containers/podman/pull/27182#issuecomment-3402465389). func InspectNormalize(row string, inspectType string) string { - m := regexp.MustCompile(`{{\s*\.Id\s*}}`) - row = m.ReplaceAllString(row, "{{.ID}}") + row = idRegex.ReplaceAllString(row, "{{.ID}}") r := strings.NewReplacer( ".Src", ".Source", diff --git a/cmd/podman/machine/machine.go b/cmd/podman/machine/machine.go index 257d78a882..71de533c9c 100644 --- a/cmd/podman/machine/machine.go +++ b/cmd/podman/machine/machine.go @@ -7,11 +7,12 @@ import ( "net" "os" "path/filepath" - "regexp" "strings" "sync" "time" + "go.podman.io/storage/pkg/regexp" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" "go.podman.io/podman/v6/cmd/podman/common" @@ -151,13 +152,14 @@ func initMachineEvents() { } } +var eventsSockRegex = regexp.Delayed(`machine_events.*\.sock`) + func resolveEventSock() ([]string, error) { // Used mostly for testing if sock, found := os.LookupEnv("PODMAN_MACHINE_EVENTS_SOCK"); found { return []string{sock}, nil } - re := regexp.MustCompile(`machine_events.*\.sock`) sockPaths := make([]string, 0) fn := func(path string, info os.DirEntry, err error) error { switch { @@ -167,7 +169,7 @@ func resolveEventSock() ([]string, error) { return nil case !isUnixSocket(info): return nil - case !re.MatchString(info.Name()): + case !eventsSockRegex.MatchString(info.Name()): return nil } diff --git a/libpod/oci_util.go b/libpod/oci_util.go index 8129c365bb..8b36892b62 100644 --- a/libpod/oci_util.go +++ b/libpod/oci_util.go @@ -7,11 +7,12 @@ import ( "fmt" "net" "os" - "regexp" "strconv" "strings" "time" + "go.podman.io/storage/pkg/regexp" + "github.com/sirupsen/logrus" "go.podman.io/common/libnetwork/types" "go.podman.io/podman/v6/libpod/define" @@ -159,24 +160,30 @@ func bindPortV4Fallback(protocol string, sockType int, port uint16) (*os.File, e return os.NewFile(uintptr(fd), fmt.Sprintf("reservation-%s-%d", protocol, port)), nil } +var ( + regexPermissionDenied = regexp.Delayed("(?i).*permission denied.*|.*operation not permitted.*") + regexNotFound = regexp.Delayed("(?i).*executable file not found in.*|.*no such file or directory.*|.*open executable.*") + regexProcAttr = regexp.Delayed("`/proc/[a-z0-9-].+/attr.*`") +) + func getOCIRuntimeError(name, runtimeMsg string) error { includeFullOutput := logrus.GetLevel() == logrus.DebugLevel - if match := regexp.MustCompile("(?i).*permission denied.*|.*operation not permitted.*").FindString(runtimeMsg); match != "" { + if match := regexPermissionDenied.FindString(runtimeMsg); match != "" { errStr := match if includeFullOutput { errStr = runtimeMsg } return fmt.Errorf("%s: %s: %w", name, strings.Trim(errStr, "\n"), define.ErrOCIRuntimePermissionDenied) } - if match := regexp.MustCompile("(?i).*executable file not found in.*|.*no such file or directory.*|.*open executable.*").FindString(runtimeMsg); match != "" { + if match := regexNotFound.FindString(runtimeMsg); match != "" { errStr := match if includeFullOutput { errStr = runtimeMsg } return fmt.Errorf("%s: %s: %w", name, strings.Trim(errStr, "\n"), define.ErrOCIRuntimeNotFound) } - if match := regexp.MustCompile("`/proc/[a-z0-9-].+/attr.*`").FindString(runtimeMsg); match != "" { + if match := regexProcAttr.FindString(runtimeMsg); match != "" { errStr := match if includeFullOutput { errStr = runtimeMsg diff --git a/pkg/annotations/validate.go b/pkg/annotations/validate.go index bfa058f42d..f0ab3ce57d 100644 --- a/pkg/annotations/validate.go +++ b/pkg/annotations/validate.go @@ -3,9 +3,10 @@ package annotations import ( "errors" "fmt" - "regexp" "strings" + "go.podman.io/storage/pkg/regexp" + "go.podman.io/podman/v6/libpod/define" ) @@ -34,7 +35,7 @@ const ( // DNS1123SubdomainMaxLength is a subdomain's max length in DNS (RFC 1123) const DNS1123SubdomainMaxLength int = 253 -var dns1123SubdomainRegexp = regexp.MustCompile("^" + dns1123SubdomainFmt + "$") +var dns1123SubdomainRegexp = regexp.Delayed("^" + dns1123SubdomainFmt + "$") // isDNS1123Subdomain tests for a string that conforms to the definition of a // subdomain in DNS (RFC 1123). @@ -58,7 +59,7 @@ const ( qualifiedNameMaxLength int = 63 ) -var qualifiedNameRegexp = regexp.MustCompile("^" + qualifiedNameFmt + "$") +var qualifiedNameRegexp = regexp.Delayed("^" + qualifiedNameFmt + "$") // isQualifiedName tests whether the value passed is what Kubernetes calls a // "qualified name". This is a format used in various places throughout the diff --git a/pkg/machine/vmconfigs/volumes_windows.go b/pkg/machine/vmconfigs/volumes_windows.go index 81c3a2c672..fc9f02c7b9 100644 --- a/pkg/machine/vmconfigs/volumes_windows.go +++ b/pkg/machine/vmconfigs/volumes_windows.go @@ -1,13 +1,18 @@ package vmconfigs import ( - "regexp" "strings" + + "go.podman.io/storage/pkg/regexp" +) + +var ( + driveLetterMatcher = regexp.Delayed(`^(?:\\\\[.?]\\)?[a-zA-Z]$`) + dedupRegex = regexp.Delayed(`//+`) ) func pathsFromVolume(volume string) []string { paths := strings.SplitN(volume, ":", 3) - driveLetterMatcher := regexp.MustCompile(`^(?:\\\\[.?]\\)?[a-zA-Z]$`) if len(paths) > 1 && driveLetterMatcher.MatchString(paths[0]) { paths = strings.SplitN(volume, ":", 4) paths = append([]string{paths[0] + ":" + paths[1]}, paths[2:]...) @@ -24,6 +29,5 @@ func extractTargetPath(paths []string) string { if strings.HasPrefix(target, "//./") || strings.HasPrefix(target, "//?/") { target = target[4:] } - dedup := regexp.MustCompile(`//+`) - return dedup.ReplaceAllLiteralString("/"+target, "/") + return dedupRegex.ReplaceAllLiteralString("/"+target, "/") } diff --git a/pkg/ps/ps.go b/pkg/ps/ps.go index 9a6b2c22ea..b258f46951 100644 --- a/pkg/ps/ps.go +++ b/pkg/ps/ps.go @@ -7,7 +7,6 @@ import ( "fmt" "os" "path/filepath" - "regexp" "sort" "strconv" "strings" @@ -376,9 +375,12 @@ func getNamespaceInfo(path string) (string, error) { // getStrFromSquareBrackets gets the string inside [] from a string. func getStrFromSquareBrackets(cmd string) string { - reg := regexp.MustCompile(`.*\[|\].*`) - arr := strings.Split(reg.ReplaceAllLiteralString(cmd, ""), ",") - return strings.Join(arr, ",") + start := strings.IndexByte(cmd, '[') + end := strings.IndexByte(cmd, ']') + if start != -1 && end != -1 && end > start { + return cmd[start+1 : end] + } + return cmd } // SortContainers helps us set-up ability to sort by createTime diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index 7093312079..2e2ebbb891 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -12,13 +12,14 @@ import ( "net" "os" "path/filepath" - "regexp" "runtime" "slices" "strconv" "strings" "time" + "go.podman.io/storage/pkg/regexp" + "github.com/docker/go-units" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" @@ -1210,14 +1211,14 @@ func envVarValue(env v1.EnvVar, opts *CtrSpecGenOptions) (*string, error) { return &env.Value, nil } +var ( + fieldPathLabelRegex = regexp.Delayed(`^metadata.labels\['(.+)'\]$`) + fieldPathAnnotationRegex = regexp.Delayed(`^metadata.annotations\['(.+)'\]$`) +) + func envVarValueFieldRef(env v1.EnvVar, opts *CtrSpecGenOptions) (*string, error) { fieldRef := env.ValueFrom.FieldRef - fieldPathLabelPattern := `^metadata.labels\['(.+)'\]$` - fieldPathLabelRegex := regexp.MustCompile(fieldPathLabelPattern) - fieldPathAnnotationPattern := `^metadata.annotations\['(.+)'\]$` - fieldPathAnnotationRegex := regexp.MustCompile(fieldPathAnnotationPattern) - fieldPath := fieldRef.FieldPath if fieldPath == "metadata.name" { diff --git a/pkg/systemd/quadlet/unitdirs.go b/pkg/systemd/quadlet/unitdirs.go index ee3b893278..180a124ff9 100644 --- a/pkg/systemd/quadlet/unitdirs.go +++ b/pkg/systemd/quadlet/unitdirs.go @@ -7,7 +7,6 @@ import ( "os/user" "path" "path/filepath" - "regexp" "strings" "go.podman.io/podman/v6/pkg/logiface" @@ -201,6 +200,18 @@ func GetUserLevelFilter(resolvedUnitDirAdminUser string) func(string, bool) bool } } +// isNumeric returns true if the string only contains digits. +// Note: It returns true for an empty string, matching the behavior +// of the original `^[0-9]*$` regular expression it replaced. +func isNumeric(s string) bool { + for _, c := range s { + if c < '0' || c > '9' { + return false + } + } + return true +} + func GetNonNumericFilter(resolvedUnitDirAdminUser string, systemUserDirLevel int) func(string, bool) bool { return func(path string, _ bool) bool { // when running in rootless, recursive walk directories that are non numeric @@ -212,7 +223,7 @@ func GetNonNumericFilter(resolvedUnitDirAdminUser string, systemUserDirLevel int return true } if len(listDirUserPathLevels) > systemUserDirLevel { - if !(regexp.MustCompile(`^[0-9]*$`).MatchString(listDirUserPathLevels[systemUserDirLevel])) { + if !isNumeric(listDirUserPathLevels[systemUserDirLevel]) { return true } }