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