1 // Copyright 2025 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4 5 package cgroup
6 7 import (
8 "internal/bytealg"
9 )
10 11 // stringError is a trival implementation of error, equivalent to errors.New,
12 // which cannot be imported from a runtime package.
13 type stringError string
14 15 func (e stringError) Error() string {
16 return string(e)
17 }
18 19 // All errors are explicit converted to type error in global initialization to
20 // ensure that the linker allocates a static interface value. This is necessary
21 // because these errors may be used before the allocator is available.
22 23 var (
24 // The entire line did not fit into the scratch buffer.
25 errIncompleteLine error = stringError("incomplete line")
26 27 // A system call failed.
28 errSyscallFailed error = stringError("syscall failed")
29 30 // Reached EOF.
31 errEOF error = stringError("end of file")
32 )
33 34 // lineReader reads line-by-line using only a single fixed scratch buffer.
35 //
36 // When a single line is too long for the scratch buffer, the remainder of the
37 // line will be skipped.
38 type lineReader struct {
39 read func(fd int, b []byte) (int, uintptr)
40 fd int
41 scratch []byte
42 43 n int // bytes of scratch in use.
44 newline int // index of the first newline in scratch.
45 46 eof bool // read reached EOF.
47 }
48 49 // newLineReader returns a lineReader which reads lines from fd.
50 //
51 // fd is the file descriptor to read from.
52 //
53 // scratch is the scratch buffer to read into. Note that len(scratch) is the
54 // longest line that can be read. Lines longer than len(scratch) will have the
55 // remainder of the line skipped. See next for more details.
56 //
57 // read is the function used to read more bytes from fd. This is usually
58 // internal/runtime/syscall.Read. Note that this follows syscall semantics (not
59 // io.Reader), so EOF is indicated with n=0, errno=0.
60 func newLineReader(fd int, scratch []byte, read func(fd int, b []byte) (n int, errno uintptr)) *lineReader {
61 return &lineReader{
62 read: read,
63 fd: fd,
64 scratch: scratch,
65 n: 0,
66 newline: -1,
67 }
68 }
69 70 // next advances to the next line.
71 //
72 // May return errIncompleteLine if the scratch buffer is too small to hold the
73 // entire line, in which case [r.line] will return the beginning of the line. A
74 // subsequent call to next will skip the remainder of the incomplete line.
75 //
76 // N.B. this behavior is important for /proc/self/mountinfo. Some lines
77 // (mounts), such as overlayfs, may be extremely long due to long super-block
78 // options, but we don't care about those. The mount type will appear early in
79 // the line.
80 //
81 // Returns errEOF when there are no more lines.
82 func (r *lineReader) next() error {
83 // Three cases:
84 //
85 // 1. First call, no data read.
86 // 2. Previous call had a complete line. Drop it and look for the end
87 // of the next line.
88 // 3. Previous call had an incomplete line. Find the end of that line
89 // (start of the next line), and the end of the next line.
90 91 prevComplete := r.newline >= 0
92 firstCall := r.n == 0
93 94 for {
95 if prevComplete {
96 // Drop the previous line.
97 copy(r.scratch, r.scratch[r.newline+1:r.n])
98 r.n -= r.newline + 1
99 100 r.newline = bytealg.IndexByte(r.scratch[:r.n], '\n')
101 if r.newline >= 0 {
102 // We have another line already in scratch. Done.
103 return nil
104 }
105 }
106 107 // No newline available.
108 109 if !prevComplete {
110 // If the previous line was incomplete, we are
111 // searching for the end of that line and have no need
112 // for any buffered data.
113 r.n = 0
114 }
115 116 n, errno := r.read(r.fd, r.scratch[r.n:len(r.scratch)])
117 if errno != 0 {
118 return errSyscallFailed
119 }
120 r.n += n
121 122 if r.n == 0 {
123 // Nothing left.
124 //
125 // N.B. we can't immediately return EOF when read
126 // returns 0 as we may still need to return an
127 // incomplete line.
128 return errEOF
129 }
130 131 r.newline = bytealg.IndexByte(r.scratch[:r.n], '\n')
132 if prevComplete || firstCall {
133 // Already have the start of the line, just need to find the end.
134 135 if r.newline < 0 {
136 // We filled the entire buffer or hit EOF, but
137 // still no newline.
138 return errIncompleteLine
139 }
140 141 // Found the end of the line. Done.
142 return nil
143 } else {
144 // Don't have the start of the line. We are currently
145 // looking for the end of the previous line.
146 147 if r.newline < 0 {
148 // Not there yet.
149 if n == 0 {
150 // No more to read.
151 return errEOF
152 }
153 continue
154 }
155 156 // Found the end of the previous line. The next
157 // iteration will drop the remainder of the previous
158 // line and look for the next line.
159 prevComplete = true
160 }
161 }
162 }
163 164 // line returns a view of the current line, excluding the trailing newline.
165 //
166 // If [r.next] returned errIncompleteLine, then this returns only the beginning
167 // of the line.
168 //
169 // Preconditions: [r.next] is called prior to the first call to line.
170 //
171 // Postconditions: The caller must not keep a reference to the returned slice.
172 func (r *lineReader) line() []byte {
173 if r.newline < 0 {
174 // Incomplete line
175 return r.scratch[:r.n]
176 }
177 // Complete line.
178 return r.scratch[:r.newline]
179 }
180