| // 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] | |
| } | |