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