line_reader.mx raw

   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