blob: 9a7213327c04ecd253989c0653413580b6409c6c [file] [log] [blame]
// 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]
}