]> Cypherpunks repositories - gostls13.git/commitdiff
internal/runtime/cgroup: allow more tests to run on all OSes
author胡玮文 <huweiwen.hww@alibaba-inc.com>
Fri, 21 Nov 2025 15:26:50 +0000 (23:26 +0800)
committerMichael Pratt <mpratt@google.com>
Tue, 25 Nov 2025 18:51:12 +0000 (10:51 -0800)
Move non-Linux specific part out of _linux.go. The parsing code itself
doesn't depend anything Linux specific, even if it the format is Linux
specific.

This should benefit developers working on non-Linux OSes.

Change-Id: I1692978d583c3dd9a57ff269c97e8fca53a7a057
Reviewed-on: https://go-review.googlesource.com/c/go/+/723240
Reviewed-by: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Carrillo Rodriguez <carrillorodriguez672@gmail.com>
src/internal/runtime/cgroup/cgroup.go [new file with mode: 0644]
src/internal/runtime/cgroup/cgroup_linux.go
src/internal/runtime/cgroup/cgroup_test.go [moved from src/internal/runtime/cgroup/cgroup_linux_test.go with 100% similarity]
src/internal/runtime/cgroup/export_linux_test.go [deleted file]
src/internal/runtime/cgroup/export_test.go

diff --git a/src/internal/runtime/cgroup/cgroup.go b/src/internal/runtime/cgroup/cgroup.go
new file mode 100644 (file)
index 0000000..68c31fc
--- /dev/null
@@ -0,0 +1,418 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package cgroup
+
+import (
+       "internal/bytealg"
+       "internal/strconv"
+)
+
+var (
+       ErrNoCgroup error = stringError("not in a cgroup")
+
+       errMalformedFile error = stringError("malformed file")
+)
+
+const _PATH_MAX = 4096
+
+const (
+       // Required amount of scratch space for CPULimit.
+       //
+       // TODO(prattmic): This is shockingly large (~70KiB) due to the (very
+       // unlikely) combination of extremely long paths consisting mostly
+       // escaped characters. The scratch buffer ends up in .bss in package
+       // runtime, so it doesn't contribute to binary size and generally won't
+       // be faulted in, but it would still be nice to shrink this. A more
+       // complex parser that did not need to keep entire lines in memory
+       // could get away with much less. Alternatively, we could do a one-off
+       // mmap allocation for this buffer, which is only mapped larger if we
+       // actually need the extra space.
+       ScratchSize = PathSize + ParseSize
+
+       // Required space to store a path of the cgroup in the filesystem.
+       PathSize = _PATH_MAX
+
+       // /proc/self/mountinfo path escape sequences are 4 characters long, so
+       // a path consisting entirely of escaped characters could be 4 times
+       // larger.
+       escapedPathMax = 4 * _PATH_MAX
+
+       // Required space to parse /proc/self/mountinfo and /proc/self/cgroup.
+       // See findCPUMount and findCPURelativePath.
+       ParseSize = 4 * escapedPathMax
+)
+
+// Version indicates the cgroup version.
+type Version int
+
+const (
+       VersionUnknown Version = iota
+       V1
+       V2
+)
+
+func parseV1Number(buf []byte) (int64, error) {
+       // Ignore trailing newline.
+       i := bytealg.IndexByte(buf, '\n')
+       if i < 0 {
+               return 0, errMalformedFile
+       }
+       buf = buf[:i]
+
+       val, err := strconv.ParseInt(string(buf), 10, 64)
+       if err != nil {
+               return 0, errMalformedFile
+       }
+
+       return val, nil
+}
+
+func parseV2Limit(buf []byte) (float64, bool, error) {
+       i := bytealg.IndexByte(buf, ' ')
+       if i < 0 {
+               return 0, false, errMalformedFile
+       }
+
+       quotaStr := buf[:i]
+       if bytealg.Compare(quotaStr, []byte("max")) == 0 {
+               // No limit.
+               return 0, false, nil
+       }
+
+       periodStr := buf[i+1:]
+       // Ignore trailing newline, if any.
+       i = bytealg.IndexByte(periodStr, '\n')
+       if i < 0 {
+               return 0, false, errMalformedFile
+       }
+       periodStr = periodStr[:i]
+
+       quota, err := strconv.ParseInt(string(quotaStr), 10, 64)
+       if err != nil {
+               return 0, false, errMalformedFile
+       }
+
+       period, err := strconv.ParseInt(string(periodStr), 10, 64)
+       if err != nil {
+               return 0, false, errMalformedFile
+       }
+
+       return float64(quota) / float64(period), true, nil
+}
+
+// Finds the path of the current process's CPU cgroup relative to the cgroup
+// mount and writes it to out.
+//
+// Returns the number of bytes written and the cgroup version (1 or 2).
+func parseCPURelativePath(fd int, read func(fd int, b []byte) (int, uintptr), out []byte, scratch []byte) (int, Version, error) {
+       // The format of each line is
+       //
+       //   hierarchy-ID:controller-list:cgroup-path
+       //
+       // controller-list is comma-separated.
+       // See man 5 cgroup for more details.
+       //
+       // cgroup v2 has hierarchy-ID 0. If a v1 hierarchy contains "cpu", that
+       // is the CPU controller. Otherwise the v2 hierarchy (if any) is the
+       // CPU controller.
+       //
+       // hierarchy-ID and controller-list have relatively small maximum
+       // sizes, and the path can be up to _PATH_MAX, so we need a bit more
+       // than 1 _PATH_MAX of scratch space.
+
+       l := newLineReader(fd, scratch, read)
+
+       // Bytes written to out.
+       n := 0
+
+       for {
+               err := l.next()
+               if err == errIncompleteLine {
+                       // Don't allow incomplete lines. While in theory the
+                       // incomplete line may be for a controller we don't
+                       // care about, in practice all lines should be of
+                       // similar length, so we should just have a buffer big
+                       // enough for any.
+                       return 0, 0, err
+               } else if err == errEOF {
+                       break
+               } else if err != nil {
+                       return 0, 0, err
+               }
+
+               line := l.line()
+
+               // The format of each line is
+               //
+               //   hierarchy-ID:controller-list:cgroup-path
+               //
+               // controller-list is comma-separated.
+               // See man 5 cgroup for more details.
+               i := bytealg.IndexByte(line, ':')
+               if i < 0 {
+                       return 0, 0, errMalformedFile
+               }
+
+               hierarchy := line[:i]
+               line = line[i+1:]
+
+               i = bytealg.IndexByte(line, ':')
+               if i < 0 {
+                       return 0, 0, errMalformedFile
+               }
+
+               controllers := line[:i]
+               line = line[i+1:]
+
+               path := line
+
+               if string(hierarchy) == "0" {
+                       // v2 hierarchy.
+                       n = copy(out, path)
+                       // Keep searching, we might find a v1 hierarchy with a
+                       // CPU controller, which takes precedence.
+               } else {
+                       // v1 hierarchy
+                       if containsCPU(controllers) {
+                               // Found a v1 CPU controller. This must be the
+                               // only one, so we're done.
+                               return copy(out, path), V1, nil
+                       }
+               }
+       }
+
+       if n == 0 {
+               // Found nothing.
+               return 0, 0, ErrNoCgroup
+       }
+
+       // Must be v2, v1 returns above.
+       return n, V2, nil
+}
+
+// Returns true if comma-separated list b contains "cpu".
+func containsCPU(b []byte) bool {
+       for len(b) > 0 {
+               i := bytealg.IndexByte(b, ',')
+               if i < 0 {
+                       // Neither cmd/compile nor gccgo allocates for these string conversions.
+                       return string(b) == "cpu"
+               }
+
+               curr := b[:i]
+               rest := b[i+1:]
+
+               if string(curr) == "cpu" {
+                       return true
+               }
+
+               b = rest
+       }
+
+       return false
+}
+
+// Returns the mount point for the cpu cgroup controller (v1 or v2) from
+// /proc/self/mountinfo.
+func parseCPUMount(fd int, read func(fd int, b []byte) (int, uintptr), out []byte, scratch []byte) (int, error) {
+       // The format of each line is:
+       //
+       // 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
+       // (1)(2)(3)   (4)   (5)      (6)      (7)   (8) (9)   (10)         (11)
+       //
+       // (1) mount ID:  unique identifier of the mount (may be reused after umount)
+       // (2) parent ID:  ID of parent (or of self for the top of the mount tree)
+       // (3) major:minor:  value of st_dev for files on filesystem
+       // (4) root:  root of the mount within the filesystem
+       // (5) mount point:  mount point relative to the process's root
+       // (6) mount options:  per mount options
+       // (7) optional fields:  zero or more fields of the form "tag[:value]"
+       // (8) separator:  marks the end of the optional fields
+       // (9) filesystem type:  name of filesystem of the form "type[.subtype]"
+       // (10) mount source:  filesystem specific information or "none"
+       // (11) super options:  per super block options
+       //
+       // See man 5 proc_pid_mountinfo for more details.
+       //
+       // Note that emitted paths will not contain space, tab, newline, or
+       // carriage return. Those are escaped. See Linux show_mountinfo ->
+       // show_path. We must unescape before returning.
+       //
+       // We return the mount point (5) if the filesystem type (9) is cgroup2,
+       // or cgroup with "cpu" in the super options (11).
+       //
+       // (4), (5), and (10) are up to _PATH_MAX. The remaining fields have a
+       // small fixed maximum size, so 4*_PATH_MAX is plenty of scratch space.
+       // Note that non-cgroup mounts may have arbitrarily long (11), but we
+       // can skip those when parsing.
+
+       l := newLineReader(fd, scratch, read)
+
+       // Bytes written to out.
+       n := 0
+
+       for {
+               //incomplete := false
+               err := l.next()
+               if err == errIncompleteLine {
+                       // An incomplete line is fine as long as it doesn't
+                       // impede parsing the fields we need. It shouldn't be
+                       // possible for any mount to use more than 3*PATH_MAX
+                       // before (9) because there are two paths and all other
+                       // earlier fields have bounded options. Only (11) has
+                       // unbounded options.
+               } else if err == errEOF {
+                       break
+               } else if err != nil {
+                       return 0, err
+               }
+
+               line := l.line()
+
+               // Skip first four fields.
+               for range 4 {
+                       i := bytealg.IndexByte(line, ' ')
+                       if i < 0 {
+                               return 0, errMalformedFile
+                       }
+                       line = line[i+1:]
+               }
+
+               // (5) mount point:  mount point relative to the process's root
+               i := bytealg.IndexByte(line, ' ')
+               if i < 0 {
+                       return 0, errMalformedFile
+               }
+               mnt := line[:i]
+               line = line[i+1:]
+
+               // Skip ahead past optional fields, delimited by " - ".
+               for {
+                       i = bytealg.IndexByte(line, ' ')
+                       if i < 0 {
+                               return 0, errMalformedFile
+                       }
+                       if i+3 >= len(line) {
+                               return 0, errMalformedFile
+                       }
+                       delim := line[i : i+3]
+                       if string(delim) == " - " {
+                               line = line[i+3:]
+                               break
+                       }
+                       line = line[i+1:]
+               }
+
+               // (9) filesystem type:  name of filesystem of the form "type[.subtype]"
+               i = bytealg.IndexByte(line, ' ')
+               if i < 0 {
+                       return 0, errMalformedFile
+               }
+               ftype := line[:i]
+               line = line[i+1:]
+
+               if string(ftype) != "cgroup" && string(ftype) != "cgroup2" {
+                       continue
+               }
+
+               // As in findCPUPath, cgroup v1 with a CPU controller takes
+               // precendence over cgroup v2.
+               if string(ftype) == "cgroup2" {
+                       // v2 hierarchy.
+                       n, err = unescapePath(out, mnt)
+                       if err != nil {
+                               // Don't keep searching on error. The kernel
+                               // should never produce broken escaping.
+                               return n, err
+                       }
+                       // Keep searching, we might find a v1 hierarchy with a
+                       // CPU controller, which takes precedence.
+                       continue
+               }
+
+               // (10) mount source:  filesystem specific information or "none"
+               i = bytealg.IndexByte(line, ' ')
+               if i < 0 {
+                       return 0, errMalformedFile
+               }
+               // Don't care about mount source.
+               line = line[i+1:]
+
+               // (11) super options:  per super block options
+               superOpt := line
+
+               // v1 hierarchy
+               if containsCPU(superOpt) {
+                       // Found a v1 CPU controller. This must be the
+                       // only one, so we're done.
+                       return unescapePath(out, mnt)
+               }
+       }
+
+       if n == 0 {
+               // Found nothing.
+               return 0, ErrNoCgroup
+       }
+
+       return n, nil
+}
+
+var errInvalidEscape error = stringError("invalid path escape sequence")
+
+// unescapePath copies in to out, unescaping escape sequences generated by
+// Linux's show_path.
+//
+// That is, '\', ' ', '\t', and '\n' are converted to octal escape sequences,
+// like '\040' for space.
+//
+// out must be at least as large as in.
+//
+// Returns the number of bytes written to out.
+//
+// Also see escapePath in cgroup_linux_test.go.
+func unescapePath(out []byte, in []byte) (int, error) {
+       // Not strictly necessary, but simplifies the implementation and will
+       // always hold in users.
+       if len(out) < len(in) {
+               throw("output too small")
+       }
+
+       var outi, ini int
+       for ini < len(in) {
+               c := in[ini]
+               if c != '\\' {
+                       out[outi] = c
+                       outi++
+                       ini++
+                       continue
+               }
+
+               // Start of escape sequence.
+
+               // Escape sequence is always 4 characters: one slash and three
+               // digits.
+               if ini+3 >= len(in) {
+                       return outi, errInvalidEscape
+               }
+
+               var outc byte
+               for i := range 3 {
+                       c := in[ini+1+i]
+                       if c < '0' || c > '9' {
+                               return outi, errInvalidEscape
+                       }
+
+                       outc *= 8
+                       outc += c - '0'
+               }
+
+               out[outi] = outc
+               outi++
+
+               ini += 4
+       }
+
+       return outi, nil
+}
index 7b35a9bc18773204cb74a50869af31d1899cc86c..5e3ee0d2c2cddf1af1a12b940683a98424c8b062 100644 (file)
@@ -5,44 +5,7 @@
 package cgroup
 
 import (
-       "internal/bytealg"
        "internal/runtime/syscall/linux"
-       "internal/strconv"
-)
-
-var (
-       ErrNoCgroup error = stringError("not in a cgroup")
-
-       errMalformedFile error = stringError("malformed file")
-)
-
-const _PATH_MAX = 4096
-
-const (
-       // Required amount of scratch space for CPULimit.
-       //
-       // TODO(prattmic): This is shockingly large (~70KiB) due to the (very
-       // unlikely) combination of extremely long paths consisting mostly
-       // escaped characters. The scratch buffer ends up in .bss in package
-       // runtime, so it doesn't contribute to binary size and generally won't
-       // be faulted in, but it would still be nice to shrink this. A more
-       // complex parser that did not need to keep entire lines in memory
-       // could get away with much less. Alternatively, we could do a one-off
-       // mmap allocation for this buffer, which is only mapped larger if we
-       // actually need the extra space.
-       ScratchSize = PathSize + ParseSize
-
-       // Required space to store a path of the cgroup in the filesystem.
-       PathSize = _PATH_MAX
-
-       // /proc/self/mountinfo path escape sequences are 4 characters long, so
-       // a path consisting entirely of escaped characters could be 4 times
-       // larger.
-       escapedPathMax = 4 * _PATH_MAX
-
-       // Required space to parse /proc/self/mountinfo and /proc/self/cgroup.
-       // See findCPUMount and findCPURelativePath.
-       ParseSize = 4 * escapedPathMax
 )
 
 // Include explicit NUL to be sure we include it in the slice.
@@ -52,15 +15,6 @@ const (
        v1PeriodFile = "/cpu.cfs_period_us\x00"
 )
 
-// Version indicates the cgroup version.
-type Version int
-
-const (
-       VersionUnknown Version = iota
-       V1
-       V2
-)
-
 // CPU owns the FDs required to read the CPU limit from a cgroup.
 type CPU struct {
        version Version
@@ -212,22 +166,6 @@ func readV1Number(fd int) (int64, error) {
        return parseV1Number(buf)
 }
 
-func parseV1Number(buf []byte) (int64, error) {
-       // Ignore trailing newline.
-       i := bytealg.IndexByte(buf, '\n')
-       if i < 0 {
-               return 0, errMalformedFile
-       }
-       buf = buf[:i]
-
-       val, err := strconv.ParseInt(string(buf), 10, 64)
-       if err != nil {
-               return 0, errMalformedFile
-       }
-
-       return val, nil
-}
-
 // Returns CPU throughput limit, or ok false if there is no limit.
 func readV2Limit(fd int) (float64, bool, error) {
        // The format of the file is "<quota> <period>\n" where quota and
@@ -260,39 +198,6 @@ func readV2Limit(fd int) (float64, bool, error) {
        return parseV2Limit(buf)
 }
 
-func parseV2Limit(buf []byte) (float64, bool, error) {
-       i := bytealg.IndexByte(buf, ' ')
-       if i < 0 {
-               return 0, false, errMalformedFile
-       }
-
-       quotaStr := buf[:i]
-       if bytealg.Compare(quotaStr, []byte("max")) == 0 {
-               // No limit.
-               return 0, false, nil
-       }
-
-       periodStr := buf[i+1:]
-       // Ignore trailing newline, if any.
-       i = bytealg.IndexByte(periodStr, '\n')
-       if i < 0 {
-               return 0, false, errMalformedFile
-       }
-       periodStr = periodStr[:i]
-
-       quota, err := strconv.ParseInt(string(quotaStr), 10, 64)
-       if err != nil {
-               return 0, false, errMalformedFile
-       }
-
-       period, err := strconv.ParseInt(string(periodStr), 10, 64)
-       if err != nil {
-               return 0, false, errMalformedFile
-       }
-
-       return float64(quota) / float64(period), true, nil
-}
-
 // FindCPU finds the path to the CPU cgroup that this process is a member of
 // and places it in out. scratch is a scratch buffer for internal use.
 //
@@ -364,118 +269,6 @@ func FindCPURelativePath(out []byte, scratch []byte) (int, Version, error) {
        return n, version, nil
 }
 
-// Finds the path of the current process's CPU cgroup relative to the cgroup
-// mount and writes it to out.
-//
-// Returns the number of bytes written and the cgroup version (1 or 2).
-func parseCPURelativePath(fd int, read func(fd int, b []byte) (int, uintptr), out []byte, scratch []byte) (int, Version, error) {
-       // The format of each line is
-       //
-       //   hierarchy-ID:controller-list:cgroup-path
-       //
-       // controller-list is comma-separated.
-       // See man 5 cgroup for more details.
-       //
-       // cgroup v2 has hierarchy-ID 0. If a v1 hierarchy contains "cpu", that
-       // is the CPU controller. Otherwise the v2 hierarchy (if any) is the
-       // CPU controller.
-       //
-       // hierarchy-ID and controller-list have relatively small maximum
-       // sizes, and the path can be up to _PATH_MAX, so we need a bit more
-       // than 1 _PATH_MAX of scratch space.
-
-       l := newLineReader(fd, scratch, read)
-
-       // Bytes written to out.
-       n := 0
-
-       for {
-               err := l.next()
-               if err == errIncompleteLine {
-                       // Don't allow incomplete lines. While in theory the
-                       // incomplete line may be for a controller we don't
-                       // care about, in practice all lines should be of
-                       // similar length, so we should just have a buffer big
-                       // enough for any.
-                       return 0, 0, err
-               } else if err == errEOF {
-                       break
-               } else if err != nil {
-                       return 0, 0, err
-               }
-
-               line := l.line()
-
-               // The format of each line is
-               //
-               //   hierarchy-ID:controller-list:cgroup-path
-               //
-               // controller-list is comma-separated.
-               // See man 5 cgroup for more details.
-               i := bytealg.IndexByte(line, ':')
-               if i < 0 {
-                       return 0, 0, errMalformedFile
-               }
-
-               hierarchy := line[:i]
-               line = line[i+1:]
-
-               i = bytealg.IndexByte(line, ':')
-               if i < 0 {
-                       return 0, 0, errMalformedFile
-               }
-
-               controllers := line[:i]
-               line = line[i+1:]
-
-               path := line
-
-               if string(hierarchy) == "0" {
-                       // v2 hierarchy.
-                       n = copy(out, path)
-                       // Keep searching, we might find a v1 hierarchy with a
-                       // CPU controller, which takes precedence.
-               } else {
-                       // v1 hierarchy
-                       if containsCPU(controllers) {
-                               // Found a v1 CPU controller. This must be the
-                               // only one, so we're done.
-                               return copy(out, path), V1, nil
-                       }
-               }
-       }
-
-       if n == 0 {
-               // Found nothing.
-               return 0, 0, ErrNoCgroup
-       }
-
-       // Must be v2, v1 returns above.
-       return n, V2, nil
-}
-
-// Returns true if comma-separated list b contains "cpu".
-func containsCPU(b []byte) bool {
-       for len(b) > 0 {
-               i := bytealg.IndexByte(b, ',')
-               if i < 0 {
-                       // Neither cmd/compile nor gccgo allocates for these string conversions.
-                       return string(b) == "cpu"
-               }
-
-               curr := b[:i]
-               rest := b[i+1:]
-
-               if string(curr) == "cpu" {
-                       return true
-               }
-
-               b = rest
-       }
-
-       return false
-}
-
 // FindCPUMountPoint finds the root of the CPU cgroup mount places it in out.
 // scratch is a scratch buffer for internal use.
 //
@@ -505,206 +298,3 @@ func FindCPUMountPoint(out []byte, scratch []byte) (int, error) {
 
        return n, nil
 }
-
-// Returns the mount point for the cpu cgroup controller (v1 or v2) from
-// /proc/self/mountinfo.
-func parseCPUMount(fd int, read func(fd int, b []byte) (int, uintptr), out []byte, scratch []byte) (int, error) {
-       // The format of each line is:
-       //
-       // 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
-       // (1)(2)(3)   (4)   (5)      (6)      (7)   (8) (9)   (10)         (11)
-       //
-       // (1) mount ID:  unique identifier of the mount (may be reused after umount)
-       // (2) parent ID:  ID of parent (or of self for the top of the mount tree)
-       // (3) major:minor:  value of st_dev for files on filesystem
-       // (4) root:  root of the mount within the filesystem
-       // (5) mount point:  mount point relative to the process's root
-       // (6) mount options:  per mount options
-       // (7) optional fields:  zero or more fields of the form "tag[:value]"
-       // (8) separator:  marks the end of the optional fields
-       // (9) filesystem type:  name of filesystem of the form "type[.subtype]"
-       // (10) mount source:  filesystem specific information or "none"
-       // (11) super options:  per super block options
-       //
-       // See man 5 proc_pid_mountinfo for more details.
-       //
-       // Note that emitted paths will not contain space, tab, newline, or
-       // carriage return. Those are escaped. See Linux show_mountinfo ->
-       // show_path. We must unescape before returning.
-       //
-       // We return the mount point (5) if the filesystem type (9) is cgroup2,
-       // or cgroup with "cpu" in the super options (11).
-       //
-       // (4), (5), and (10) are up to _PATH_MAX. The remaining fields have a
-       // small fixed maximum size, so 4*_PATH_MAX is plenty of scratch space.
-       // Note that non-cgroup mounts may have arbitrarily long (11), but we
-       // can skip those when parsing.
-
-       l := newLineReader(fd, scratch, read)
-
-       // Bytes written to out.
-       n := 0
-
-       for {
-               //incomplete := false
-               err := l.next()
-               if err == errIncompleteLine {
-                       // An incomplete line is fine as long as it doesn't
-                       // impede parsing the fields we need. It shouldn't be
-                       // possible for any mount to use more than 3*PATH_MAX
-                       // before (9) because there are two paths and all other
-                       // earlier fields have bounded options. Only (11) has
-                       // unbounded options.
-               } else if err == errEOF {
-                       break
-               } else if err != nil {
-                       return 0, err
-               }
-
-               line := l.line()
-
-               // Skip first four fields.
-               for range 4 {
-                       i := bytealg.IndexByte(line, ' ')
-                       if i < 0 {
-                               return 0, errMalformedFile
-                       }
-                       line = line[i+1:]
-               }
-
-               // (5) mount point:  mount point relative to the process's root
-               i := bytealg.IndexByte(line, ' ')
-               if i < 0 {
-                       return 0, errMalformedFile
-               }
-               mnt := line[:i]
-               line = line[i+1:]
-
-               // Skip ahead past optional fields, delimited by " - ".
-               for {
-                       i = bytealg.IndexByte(line, ' ')
-                       if i < 0 {
-                               return 0, errMalformedFile
-                       }
-                       if i+3 >= len(line) {
-                               return 0, errMalformedFile
-                       }
-                       delim := line[i : i+3]
-                       if string(delim) == " - " {
-                               line = line[i+3:]
-                               break
-                       }
-                       line = line[i+1:]
-               }
-
-               // (9) filesystem type:  name of filesystem of the form "type[.subtype]"
-               i = bytealg.IndexByte(line, ' ')
-               if i < 0 {
-                       return 0, errMalformedFile
-               }
-               ftype := line[:i]
-               line = line[i+1:]
-
-               if string(ftype) != "cgroup" && string(ftype) != "cgroup2" {
-                       continue
-               }
-
-               // As in findCPUPath, cgroup v1 with a CPU controller takes
-               // precendence over cgroup v2.
-               if string(ftype) == "cgroup2" {
-                       // v2 hierarchy.
-                       n, err = unescapePath(out, mnt)
-                       if err != nil {
-                               // Don't keep searching on error. The kernel
-                               // should never produce broken escaping.
-                               return n, err
-                       }
-                       // Keep searching, we might find a v1 hierarchy with a
-                       // CPU controller, which takes precedence.
-                       continue
-               }
-
-               // (10) mount source:  filesystem specific information or "none"
-               i = bytealg.IndexByte(line, ' ')
-               if i < 0 {
-                       return 0, errMalformedFile
-               }
-               // Don't care about mount source.
-               line = line[i+1:]
-
-               // (11) super options:  per super block options
-               superOpt := line
-
-               // v1 hierarchy
-               if containsCPU(superOpt) {
-                       // Found a v1 CPU controller. This must be the
-                       // only one, so we're done.
-                       return unescapePath(out, mnt)
-               }
-       }
-
-       if n == 0 {
-               // Found nothing.
-               return 0, ErrNoCgroup
-       }
-
-       return n, nil
-}
-
-var errInvalidEscape error = stringError("invalid path escape sequence")
-
-// unescapePath copies in to out, unescaping escape sequences generated by
-// Linux's show_path.
-//
-// That is, '\', ' ', '\t', and '\n' are converted to octal escape sequences,
-// like '\040' for space.
-//
-// out must be at least as large as in.
-//
-// Returns the number of bytes written to out.
-//
-// Also see escapePath in cgroup_linux_test.go.
-func unescapePath(out []byte, in []byte) (int, error) {
-       // Not strictly necessary, but simplifies the implementation and will
-       // always hold in users.
-       if len(out) < len(in) {
-               throw("output too small")
-       }
-
-       var outi, ini int
-       for ini < len(in) {
-               c := in[ini]
-               if c != '\\' {
-                       out[outi] = c
-                       outi++
-                       ini++
-                       continue
-               }
-
-               // Start of escape sequence.
-
-               // Escape sequence is always 4 characters: one slash and three
-               // digits.
-               if ini+3 >= len(in) {
-                       return outi, errInvalidEscape
-               }
-
-               var outc byte
-               for i := range 3 {
-                       c := in[ini+1+i]
-                       if c < '0' || c > '9' {
-                               return outi, errInvalidEscape
-                       }
-
-                       outc *= 8
-                       outc += c - '0'
-               }
-
-               out[outi] = outc
-               outi++
-
-               ini += 4
-       }
-
-       return outi, nil
-}
diff --git a/src/internal/runtime/cgroup/export_linux_test.go b/src/internal/runtime/cgroup/export_linux_test.go
deleted file mode 100644 (file)
index 653fcd1..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2025 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package cgroup
-
-var ContainsCPU = containsCPU
-
-var ParseV1Number = parseV1Number
-var ParseV2Limit = parseV2Limit
-
-var ParseCPURelativePath = parseCPURelativePath
-var ParseCPUMount = parseCPUMount
-
-var UnescapePath = unescapePath
index 200e5aee1210947fa9b7772f29a1b06253118931..55acdc0877ef2f64551373c77f00e40ff956d8fa 100644 (file)
@@ -22,3 +22,13 @@ var (
        ErrEOF            = errEOF
        ErrIncompleteLine = errIncompleteLine
 )
+
+var ContainsCPU = containsCPU
+
+var ParseV1Number = parseV1Number
+var ParseV2Limit = parseV2Limit
+
+var ParseCPURelativePath = parseCPURelativePath
+var ParseCPUMount = parseCPUMount
+
+var UnescapePath = unescapePath