Source file src/internal/runtime/cgroup/line_reader.go
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