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  

View as plain text